package controllers import ( "github.com/fiskerinc/cloud-services/services/attendant/services" "encoding/base64" "encoding/json" "errors" "sync" "time" "github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc" "github.com/fiskerinc/cloud-services/pkg/logger" "github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor" ) type DTCEntry struct { CreatedAt time.Time `bson:"created_at"` VIN string `bson:"vin"` ECU string `json:"ecu" bson:"ecu"` DTC uint64 `json:"dtc" bson:"dtc"` Status uint8 `json:"status" bson:"status"` Timestamp time.Time `json:"timestamp" bson:"timestamp"` Speed uint16 `json:"speed" bson:"speed"` Mileage uint32 `json:"mileage" bson:"mileage"` Voltage uint16 `json:"voltage" bson:"voltage"` SnapshotBase64 string `json:"snapshot,omitempty" bson:"snapshot,omitempty"` } func GRPCToDTCEntry(payload *kafka_grpc.GRPC_AttendantPayload) []byte { if payload.Data == nil { return nil } data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_DtcEntry) if data == nil { return nil } dtc := &DTCEntry{ VIN: data.DtcEntry.Vin, ECU: data.DtcEntry.Ecu, CreatedAt: milliToDate(data.DtcEntry.CreatedAt), DTC: data.DtcEntry.Dtc, Status: uint8(data.DtcEntry.Status), Timestamp: milliToDate(data.DtcEntry.Timestamp), Speed: uint16(data.DtcEntry.Speed), Mileage: data.DtcEntry.Mileage, Voltage: uint16(data.DtcEntry.Volt), SnapshotBase64: data.DtcEntry.SnapshotBase64, } bytes, _ := json.Marshal(dtc) return bytes } func milliToDate(timestamp int64) time.Time { seconds := timestamp / 1000 nanoseconds := (timestamp % 1000) * int64(time.Millisecond) return time.Unix(seconds, nanoseconds) } const SNAPSHOT_LEN = 21 func (entry *DTCEntry) ParseSnapshot() error { data, errc := base64.StdEncoding.DecodeString(entry.SnapshotBase64) if errc != nil { return errc } payloadLen := len(data) - 2 if payloadLen < SNAPSHOT_LEN { logger.Debug().Msgf("DTC snapshot payload is too small. Required length=%d, actual=%d, ECU=%s\n", SNAPSHOT_LEN, len(data), entry.ECU) return errors.New("Snapshot too small") } // Our "Basic Diagnostic" spec defines 4 mandatory DIDs that must be // stored inside DTCStapshotRecord: // 1. EF F6 - Timestamp (6 bytes). // 2. EF F7 - Vehicle Speed (2 bytes). // 3. EF F8 - Milage (idk, our spec doesn't specify exact number. // Empirically it's 4 bytes (same as "ICC_0x531::TotMilg_ODO"). // 4. EF F9 - Battery Voltage (2 bytes). ///entry.Speed = int16(speed.ToPhysical(float64(uint16(decodedBytes[0])<<8 | uint16(decodedBytes[1])))) //entry.Voltage = int16(voltage.ToPhysical(float64(uint16(decodedBytes[0])<<8 | uint16(decodedBytes[1])))) for idx := 2; idx < len(data)-1; { if data[idx] != 0xEF { idx++ continue } if data[idx+1] == 0xF6 && idx+7 < len(data) { idx += 2 entry.Timestamp, idx = consumeTimestamp(data, idx) } else if data[idx+1] == 0xF7 && idx+3 < len(data) { idx += 2 entry.Speed, idx = consumeSpeed(data, idx) } else if data[idx+1] == 0xF8 && idx+5 < len(data) { idx += 2 entry.Mileage, idx = consumeMileage(data, idx) } else if data[idx+1] == 0xF9 && idx+3 < len(data) { idx += 2 entry.Voltage, idx = consumeVoltage(data, idx) } else { idx++ } } return nil } var onceTimestamp sync.Once var ( day *descriptor.Signal hr *descriptor.Signal mins *descriptor.Signal yr *descriptor.Signal month *descriptor.Signal sec *descriptor.Signal ) func consumeTimestamp(data []byte, idx int) (res time.Time, idx_ret int) { onceTimestamp.Do(func() { day, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Day") hr, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Hr") mins, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Mins") yr, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Yr") month, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Mth") sec, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Sec") }) res = time.Date(int(yr.ToPhysical(float64(data[idx]))), time.Month(month.ToPhysical(float64(data[idx+1]))), int(day.ToPhysical(float64(data[idx+2]))), int(hr.ToPhysical(float64(data[idx+3]))), int(mins.ToPhysical(float64(data[idx+4]))), int(sec.ToPhysical(float64(data[idx+5]))), 0, time.UTC) idx_ret = idx + 6 return res, idx_ret } var onceSpeed sync.Once var speed *descriptor.Signal func consumeSpeed(data []byte, idx int) (res uint16, idx_ret int) { onceSpeed.Do(func() { speed, _ = services.GetDBC().Signal(0x318, "ESP_VehSpd") }) res = uint16(speed.ToPhysical(float64(uint16(data[idx])<<8 | uint16(data[idx+1])))) idx_ret = idx + 2 return res, idx_ret } var onceMileage sync.Once var mileage *descriptor.Signal func consumeMileage(data []byte, idx int) (res uint32, idx_ret int) { onceMileage.Do(func() { mileage, _ = services.GetDBC().Signal(0x531, "ICC_TotMilg_ODO") }) res = uint32(mileage.ToPhysical( float64( uint64(data[idx])<<24 | uint64(data[idx+1])<<16 | uint64(data[idx+2])<<8 | uint64(data[idx+3])))) idx_ret = idx + 4 return res, idx_ret } var onceVoltage sync.Once var voltage *descriptor.Signal func consumeVoltage(data []byte, idx int) (res uint16, idx_ret int) { onceVoltage.Do(func() { voltage, _ = services.GetDBC().Signal(0x507, "VCU_BattVolt") }) res = uint16(voltage.ToPhysical(float64(uint16(data[idx])<<8 | uint16(data[idx+1])))) idx_ret = idx + 2 return res, idx_ret }