package flashpackversion import ( "sort" "strconv" "github.com/fiskerinc/cloud-services/pkg/common" "github.com/fiskerinc/cloud-services/pkg/db/queries" "github.com/pkg/errors" "golang.org/x/exp/maps" ) // Any time we insert new CarECUs, we want to see if the flashpack number for the car has changed // If so, we save it on the car, send it to SAP, and update the Version Log. func InsertCarECUsAndUpdateFlashpackVersion(cars queries.CarsInterface, cvl queries.CarVersionsLogInterface, vin string, ecus []common.CarECU) error { // Convert the ecu names to cloud compatible ecus for _, e := range ecus { e.TransformECUName() } // Insert the new CarECUs err := cars.InsertCarECUs(ecus) if err != nil { return err } var previousFlashpackNumber, currentFlashpackNumber string // Find the previous flashpack version of the car car, err := cars.SelectByVIN(vin) if err != nil { return err } previousFlashpackNumber = car.Flashpack // Find the current flashpack number of the car currentFlashpackNumber, err = FindCurrentFlashpackVersionForCar(cars, *car) if err != nil { return err } // If the flashpack has changed, update the car and SAP return updateFlashpackNumber(cars, cvl, vin, currentFlashpackNumber, previousFlashpackNumber) } // Given the latest ECUs for a car, find out what the flashpack number for the car is func FindCurrentFlashpackVersionForCar(cars queries.CarsInterface, car common.Car) (currentFlashpackNumber string, err error) { currentFlashpackNumber = "" // Get a map of key-value pairs, latest updated ECUs for the car, ECU names as keys and ECU Supplier SW Versions as values latestCarECUsToVersionsMap, err := getLatestCarECUsMap(cars, car.VIN) if err != nil { return currentFlashpackNumber, err } // Get a map of all flashpack version mappings, and an array of the flashpack numbers sorted in descending numerical order flashpackMappings, flashpackNumbers, err := getAllFlashpackMappingsByModelTrimMap(cars, car.Model, car.Trim) if err != nil { return currentFlashpackNumber, err } // Here is the logic to calculate the current flashpack number for the car // More than one version for an ECU may map to the same flashpack number // A car may be several versions ahead on one ECU, and several versions behind on another ECU // Hence the following ugly code // For each flashpack number in descending order var ecusUpdated []string for _, flashpackNumber := range flashpackNumbers { var flashpackNumberComplete = true outer: // For each ECU version mapping for the flashpack number for _, flashpackMapping := range flashpackMappings[flashpackNumber] { var ecuUpdated = false inner: // Determine whether this ECU is updated on the car to this flashpack number or a higher one for _, e := range ecusUpdated { if e == flashpackMapping.ECU { ecuUpdated = true break inner } } if !ecuUpdated { ecuVersion, ok := latestCarECUsToVersionsMap[flashpackMapping.ECU] // If the ECU has not been updated at all, or it is not at this version, then the flashpack number is incomplete and the car has not achieved it if !ok || ecuVersion != flashpackMapping.SupplierSWVersion { flashpackNumberComplete = false break outer } else { // We have proved that this ECU is updated on the car to this flashpack number ecusUpdated = append(ecusUpdated, flashpackMapping.ECU) } } } if flashpackNumberComplete && flashpackNumber > currentFlashpackNumber { currentFlashpackNumber = flashpackNumber } } return currentFlashpackNumber, err } func FindCarECUsToUpdateForNextFlashpackNumber(cars queries.CarsInterface, car common.Car, nextFlashpackNumber string) ([]common.CarECUVersion, error) { ecusToUpdateForNextFlashpackNumber := []common.CarECUVersion{} // Get a map of key-value pairs, latest updated ECUs for the car, ECU names as keys and ECU Supplier SW Versions as values latestCarECUsToVersionsMap, err := getLatestCarECUsMap(cars, car.VIN) if err != nil { return nil, err } // Get a map of all flashpack version mappings, and an array of the flashpack numbers sorted in descending numerical order allFlashpackMappings, allFlashpackNumbers, err := getAllFlashpackMappingsByModelTrimMap(cars, car.Model, car.Trim) if err != nil { return nil, err } // Get the mappings specifically for the next flashpack version nextFlashpackVersionMappings := allFlashpackMappings[nextFlashpackNumber] // Here is the logic to find which ECUs on the car need to be updated in order to achieve nextFlashpackNumber // A car may be several versions ahead on one ECU, and several versions behind on another ECU // Hence the following ugly code // For each mapping for the next flashpack version for _, nextFlashpackVersionMapping := range nextFlashpackVersionMappings { ecuVersion, ok := latestCarECUsToVersionsMap[nextFlashpackVersionMapping.ECU] if !ok { // If the ECU has not been updated at all on this car, we know it needs to be updated to achieve the next flashpack version ecusToUpdateForNextFlashpackNumber = append(ecusToUpdateForNextFlashpackNumber, common.CarECUVersion{ CarECUName: nextFlashpackVersionMapping.ECU, CarECUVersion: nextFlashpackVersionMapping.SupplierSWVersion, }) } else { // If the ECU has been updated, we need to determine whether its version applies to this flashpack number or a higher one // That means we need to check all the mappings for the next flashpack number and all higher ones as well found := false outer: // For each flashpack number in descending order for _, flashpackNumber := range allFlashpackNumbers { inner: // For each ECU mapping for this flashpack number for _, carECU := range allFlashpackMappings[flashpackNumber] { if ecuVersion == carECU.SupplierSWVersion { found = true break inner } } // Don't check flashpack numbers lower than the next flashpack number if flashpackNumber == nextFlashpackNumber { break outer } } if !found { // We have proved that the ECU has not been updated for the next flashpack number ecusToUpdateForNextFlashpackNumber = append(ecusToUpdateForNextFlashpackNumber, common.CarECUVersion{ CarECUName: nextFlashpackVersionMapping.ECU, CarECUVersion: nextFlashpackVersionMapping.SupplierSWVersion, }) } } } return ecusToUpdateForNextFlashpackNumber, err } // Save the flashpack number in the database, display it in the version log, and send it to SAP func updateFlashpackNumber(cars queries.CarsInterface, cvl queries.CarVersionsLogInterface, vin, currentFlashpack string, previousFlashpack string) error { if currentFlashpack != previousFlashpack { _, err := cars.UpdateCarFlashpackVersion(vin, currentFlashpack) if err != nil { return errors.WithStack(err) } _, err = cvl.LogVersionChange(&common.CarVersionLogs{ VIN: vin, VersionSource: common.FlashpackVersionSource, Version: currentFlashpack, }) if err != nil { return errors.WithStack(err) } } return nil } func getLatestCarECUsMap(cars queries.CarsInterface, vin string) (map[string]string, error) { allLatestCarECUs, err := cars.GetCarECUs(common.CarECUFilter{VIN: vin, Unique: true}, nil) if err != nil { return nil, err } // Put into a map by ECU name var latestCarECUsToVersionsMap = make(map[string]string) for _, e := range allLatestCarECUs { latestCarECUsToVersionsMap[e.ECU] = e.SupplierSWVersion } // Sometimes the TBOX version is applied to MCU, or the PDU version is applied to OBC, etc. for ecuName, ecuReplacementName := range common.FlashpackCalculationECUReplacement { _, hasECUName := latestCarECUsToVersionsMap[ecuName] _, hasECUReplacementName := latestCarECUsToVersionsMap[ecuReplacementName] if hasECUName && !hasECUReplacementName { latestCarECUsToVersionsMap[ecuReplacementName] = latestCarECUsToVersionsMap[ecuName] } } return latestCarECUsToVersionsMap, nil } func getAllFlashpackMappingsByModelTrimMap(cars queries.CarsInterface, model string, trim string) (map[string][]common.CarECU, []string, error) { allFlashpackMappingsByModelTrim, err := cars.GetCarFlashpackVersionMappingsByModelTrim(model, trim, &queries.PageQueryOptions{Order: "flashpack DESC"}) if err != nil { return nil, nil, err } // Map flashpack numbers to an array of ECU names and versions var flashpackMappings = make(map[string][]common.CarECU) for _, f := range allFlashpackMappingsByModelTrim { flashpackMappings[f.Flashpack] = append(flashpackMappings[f.Flashpack], common.CarECU{ ECU: f.CarECUName, SupplierSWVersion: f.CarECUVersion, }) } // Sort the flashpack numbers in descending order var flashpackNumbers = maps.Keys(flashpackMappings) sort.Slice(flashpackNumbers, func(i, j int) bool { iFp, _ := strconv.ParseFloat(flashpackNumbers[i], 64) // guaranteed numeric jFp, _ := strconv.ParseFloat(flashpackNumbers[j], 64) // guaranteed numeric return iFp > jFp }) return flashpackMappings, flashpackNumbers, nil }