package cache import ( "fmt" "sort" "strconv" "strings" "time" "fiskerinc.com/modules/dbc/state" "fiskerinc.com/modules/logger" "fiskerinc.com/modules/redis" "fiskerinc.com/modules/utils/querystring" redigo "github.com/gomodule/redigo/redis" "github.com/pkg/errors" ) const ( pattern = "car:*:state" ) type DigitalTwinTimestampState struct { redisClient redis.Client } func NewDigitalTwinTimestampState(redisClient redis.Client) *DigitalTwinTimestampState { return &DigitalTwinTimestampState{ redisClient: redisClient, } } // getStateKeys retrieves car state keys from Redis based on the specified pattern // and returns a sliced list of keys according to the provided offset and limit. // // Parameters: // - offset: An integer indicating the starting index of the slice. // - limit: An integer specifying the maximum number of elements in the sliced list. // // Returns: // - []string: A sliced list of car state keys based on the given offset and limit. // - error: An error, if any, encountered during the Redis operation or slicing process. func (dtts *DigitalTwinTimestampState) getStateKeys(offset, limit int) ([]string, error) { keys, err := redigo.Strings(dtts.redisClient.Execute("KEYS", pattern)) if err != nil { return nil, err } totalKeys := len(keys) if totalKeys <= offset { return nil, nil } if (offset + limit) > totalKeys { limit = totalKeys - offset } sort.Strings(keys) keys = keys[offset : offset+limit] return keys, nil } // readCarStateByKey retrieves data from Redis based on the specified key using the HGETALL command. // It iterates over all keys and values returned by the command, sets them in a response map, // and returns the populated map along with any encountered errors. // // Parameters: // - key: A string representing the key to retrieve data from in Redis. // // Returns: // - map[string]interface{}: A map containing keys and values retrieved from Redis. // - error: An error, if any, encountered during the Redis HGETALL operation or mapping process. func (dtts *DigitalTwinTimestampState) readCarStateByKey(key string) (map[string]interface{}, error) { keyval := make(map[string]interface{}) batch := redis.NewRedisBatchCommands() batch.Add("HGETALL", key) payload, err := redigo.Values(dtts.redisClient.ExecuteBatch(batch)) if err != nil { return keyval, err } stateValues, err := redigo.Values(payload[0], nil) if err != nil { return keyval, err } for i := 0; i < len(stateValues); i += 2 { key, okKey := stateValues[i].([]byte) value, okValue := stateValues[i+1].([]byte) if !okKey || !okValue { continue } err = dtts.parseCarState(string(key), value, keyval) // log error, do not return error so we can read other properties for digital twin if err != nil { logger.Warn().Err(err).Send() continue } } return keyval, nil } // GetDigitalTwinSignals retrieves digital twin signals from Redis based on the specified offset and limit. // It reads all signals from Redis and returns a list of maps, where each map represents a cars signal with its properties. // // Parameters: // - offset: An integer indicating the starting index of the signals to retrieve. // - limit: An integer specifying the maximum number of signals to retrieve. // // Returns: // - []map[string]interface{}: A list of maps representing digital twin signals. func (dtts *DigitalTwinTimestampState) GetDigitalTwinSignals(offset, limit int) (resp []map[string]interface{}) { keys, err := dtts.getStateKeys(offset, limit) if err != nil { logger.Warn().Err(err).Send() return } for _, key := range keys { keyval, err := dtts.readCarStateByKey(key) if err != nil { logger.Warn().Err(err).Send() continue } if len(keyval) > 0 { keySlice := strings.Split(key, ":") keyval["VIN"] = keySlice[1] resp = append(resp, keyval) } } return } // timestampKey generates a timestamp key based on the provided key by appending ":updated" to it. // It formats the key in a way suitable for storing timestamps associated with the original key in data storage systems. // // Parameters: // - key: A string representing the original key for which the timestamp key is generated. // // Returns: // - string: A formatted string representing the timestamp key. func (dtts *DigitalTwinTimestampState) timestampKey(key string) string { return fmt.Sprintf("%s:%s", key, "updated") } // timestampVal parses a byte slice containing a JSON-encoded timestamp and returns a pointer to a time.Time // representing the parsed timestamp. It uses the UnmarshalJSON method of the time.Time type for decoding. // // Parameters: // - val: A byte slice containing the JSON-encoded timestamp to be parsed. // // Returns: // - time.Time: A time representing the parsed timestamp. // - error: An error, if any, encountered during the parsing process. func (dtts *DigitalTwinTimestampState) timestampVal(val []byte) (time.Time, error) { t := &time.Time{} err := t.UnmarshalJSON(val) return *t, err } // parseCarState checks if the provided key is needed and, if so, sets the key and value in the given map. // // Parameters: // - key: A string representing the key to check and potentially set in the map. // - value: A byte slice containing the value associated with the key. // - keyval: A map[string]interface{} where the key and valueset if the key is needed. // // Returns: // - error: An error, if any, encountered during the parsing and mapping process. func (dtts *DigitalTwinTimestampState) parseCarState(key string, value []byte, keyval map[string]interface{}) error { var err error val := string(value) switch key { case state.BMS_PwrBattRmngCpSOC, state.BMS_RmChrgTi_FullChrg, state.BCM_PwrMod, state.PWC_ChrgSts, state.VCU_DCChrgRmngTi, state.BMS_RmChrgTi_TrgtSoC: keyval[key], err = strconv.Atoi(val) case state.ICC_TotMilg_ODO: keyval[key], err = querystring.ConvertStringToInt(val) case state.IBS_BatteryVoltage: keyval[key], err = strconv.ParseFloat(val, 64) // updated timestamps case dtts.timestampKey(state.BMS_PwrBattRmngCpSOC), dtts.timestampKey(state.ICC_TotMilg_ODO), dtts.timestampKey(state.VCU_DCChrgRmngTi), dtts.timestampKey(state.BMS_RmChrgTi_TrgtSoC), dtts.timestampKey(state.IBS_BatteryVoltage), dtts.timestampKey(state.BMS_RmChrgTi_FullChrg), dtts.timestampKey(state.BCM_PwrMod), dtts.timestampKey(state.PWC_ChrgSts): keyval[key], err = dtts.timestampVal(value) } return errors.WithStack(err) }