package models import ( "encoding/base64" "fmt" "math" "strings" "github.com/fiskerinc/cloud-services/pkg/common" "github.com/fiskerinc/cloud-services/pkg/digitaltwin" "github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc" can "github.com/fiskerinc/cloud-services/pkg/can-go" "github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor" "github.com/pkg/errors" ) type DBCVersionInterface interface { AddMsg(msg CANMessageInterface) DBCVersionInterface DecodeMessage(msg *kafka_grpc.GRPC_CANFrame) ([]descriptor.DecodedSignal, error) GenerateCANSignals(vin string, msg *kafka_grpc.GRPC_CANFrame) ([]common.CANSignal, error) GetActiveDiagnosticFlags(vin string, msg *kafka_grpc.GRPC_CANFrame) ([]common.CANSignal, bool, error) SetDiagnosticFlags(flags map[int]map[string]float64) DBCVersionInterface ParseState(id int, keyvaluesOut map[string]interface{}, vin string, timestampUSec int, incomingSignals []common.CANSignal, cached digitaltwin.DigitalTwinCacheInterface) error GetDatabase() *descriptor.Database Hash() string } func NewDBCVersion(dbc *descriptor.Database, hash string) DBCVersionInterface { if len(hash) < 3 { panic("NewDBCVersion hash cannot be less than 3 characters") } return &DBCVersion{ dbc: dbc, hash: hash, } } type DBCVersion struct { state map[int]CANMessageInterface dbc *descriptor.Database diagnosticFlags map[int]map[string]float64 hash string } func (d *DBCVersion) AddMsg(msg CANMessageInterface) DBCVersionInterface { m := d.getMsgMap() if _, ok := m[msg.GetID()]; ok { panic(fmt.Sprintf("CAN message %d already exists", msg.GetID())) } m[msg.GetID()] = msg return d } func (d *DBCVersion) getMsgMap() map[int]CANMessageInterface { if d.state == nil { d.state = map[int]CANMessageInterface{} } return d.state } func (d *DBCVersion) ParseState(id int, keyvaluesOut map[string]interface{}, vin string, timestampUSec int, incomingSignals []common.CANSignal, cached digitaltwin.DigitalTwinCacheInterface) error { m := d.getMsgMap() var msg CANMessageInterface var ok bool if msg, ok = m[id]; !ok { return nil } if i := len(incomingSignals); i != msg.GetLength() { return IncorrectSignalsLength(id, msg.GetLength(), i) } err := msg.ParseState(keyvaluesOut, vin, timestampUSec, incomingSignals, cached) // TODO run options here return err } func (d *DBCVersion) GenerateCANSignals(vin string, msg *kafka_grpc.GRPC_CANFrame) ([]common.CANSignal, error) { var signals []common.CANSignal decodedSignals, err := d.DecodeMessage(msg) if err != nil { return signals, err } for _, signal := range decodedSignals { signals = append(signals, common.CANSignal{ VIN: vin, Timestamp: float64(msg.Epoch) * math.Pow(10, -6), ID: int(msg.ID), Name: signal.Signal.Name, Value: signal.Value, }) } return signals, nil } func (d *DBCVersion) DecodeMessage(msg *kafka_grpc.GRPC_CANFrame) ([]descriptor.DecodedSignal, error) { var decodedSignals []descriptor.DecodedSignal if msg.ID >= 2048 || msg.ID < 0 { if GetInvalidCANIDCache().Exists(d.hash, msg.ID) { return decodedSignals, nil } return decodedSignals, errors.Errorf("message id %v out of bounds", msg.ID) } message, ok := d.dbc.Message(uint32(msg.ID)) if !ok { if GetInvalidCANIDCache().Exists(d.hash, msg.ID) { return decodedSignals, nil } return decodedSignals, errors.Errorf( "message id %v not found in %s, version: %s", msg.ID, d.dbc.SourceFile, d.hash) } strippedData := strings.TrimRight(string(msg.Value), "=") data, err := base64.RawStdEncoding.DecodeString(strippedData) if err != nil { return decodedSignals, errors.WithStack(err) } p := can.Payload{Data: data} decodedSignals = message.Decode(&p) return decodedSignals, nil } func (d *DBCVersion) GetActiveDiagnosticFlags(vin string, msg *kafka_grpc.GRPC_CANFrame) ([]common.CANSignal, bool, error) { var flags []common.CANSignal diagnosticSignals, ok := d.diagnosticFlags[int(msg.ID)] if !ok { return flags, false, nil } decodedSignals, err := d.DecodeMessage(msg) if err != nil { return flags, false, err } flags = make([]common.CANSignal, 0) for _, signal := range decodedSignals { faultVal, ok := diagnosticSignals[signal.Signal.Name] if !ok { continue } else if signal.Value != faultVal { continue } flags = append(flags, common.CANSignal{ VIN: vin, Timestamp: float64(msg.Epoch) * math.Pow(10, -6), ID: int(msg.ID), Name: signal.Signal.Name, Value: signal.Value, }) } return flags, len(flags) != 0, nil } func (d *DBCVersion) Hash() string { return d.hash } // TODO this should be replaced by generated diagnostic alerts from the DBC func (d *DBCVersion) SetDiagnosticFlags(flags map[int]map[string]float64) DBCVersionInterface { d.diagnosticFlags = flags return d } func (d *DBCVersion) GetDatabase() *descriptor.Database { return d.dbc }