185 lines
5.4 KiB
Go
185 lines
5.4 KiB
Go
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
|
|
|
|
}
|