Initial cloud-services repo - gateway service + pkg modules
This commit is contained in:
37
pkg/common/actionlogger/actionlogger.go
Normal file
37
pkg/common/actionlogger/actionlogger.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package actionlogger
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Use Action Log to track actions taken against a car including remote commands, car updates etc. Mostly worried about car commands for now
|
||||
type ActionLog struct {
|
||||
VIN string
|
||||
UserIdentifier string
|
||||
//UserType string // Not sure how different users can be identified between fisker customers and api admins
|
||||
Action Action // Short Hand of description
|
||||
Description string // JSON string explaining full action
|
||||
// TrackingID *uuid.UUID // If we want to log the same action as it goes through, this tracking ID will follow the same request through
|
||||
CallLocation string // Informative field where we created this. Can use runtime reflection if we want, but is more expensive
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
type Action string
|
||||
|
||||
const (
|
||||
RemoteCommand Action = "RemoteCommand"
|
||||
CarConfigurationUpdate Action = "CarConfigurationUpdate"
|
||||
CarUpdate Action = "CarUpdate"
|
||||
)
|
||||
|
||||
type ActionLogFilter struct {
|
||||
VINs []string `pg:",array"`
|
||||
Actions []string `pg:",array"`
|
||||
Before time.Time
|
||||
After time.Time
|
||||
Limit int
|
||||
TrackingID *uuid.UUID
|
||||
}
|
||||
28
pkg/common/add_car_request.go
Normal file
28
pkg/common/add_car_request.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package common
|
||||
|
||||
type AddCarRequest struct {
|
||||
VIN string `json:"vin" validate:"required,vin"`
|
||||
LogLevel LogLevel `json:"log_level,omitempty" swaggertype:"string"`
|
||||
CANBus *CANBus `json:"canbus,omitempty"`
|
||||
IDPSEnabled bool `json:"idps_enabled,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateCarRequest struct {
|
||||
VIN string `json:"vin" validate:"required,vin"`
|
||||
ICCID string `json:"iccid,omitempty" validate:"omitempty,max=50"`
|
||||
Year int `json:"year,omitempty" validate:"required,gte=1000,lte=9999"`
|
||||
Model string `json:"model,omitempty" validate:"required,max=256"`
|
||||
Trim string `json:"trim,omitempty" validate:"required,max=256"`
|
||||
Country string `json:"country,omitempty" validate:"max=256"`
|
||||
Powertrain string `json:"powertrain,omitempty" validate:"max=256"`
|
||||
Restraint string `json:"restraint,omitempty" validate:"max=256"`
|
||||
BodyType string `json:"body_type,omitempty" validate:"max=256"`
|
||||
LogLevel LogLevel `json:"log_level,omitempty" swaggertype:"string"`
|
||||
DLTEnabled bool `json:"dlt_enabled,omitempty" swaggertype:"boolean"`
|
||||
DLTLevel int `json:"dlt_level,omitempty" validate:"oneof=0 1 2 3 4 5 6 255"`
|
||||
CANBus *CANBus `json:"canbus,omitempty"`
|
||||
IDPSEnabled bool `json:"idps_enabled,omitempty"`
|
||||
DebugMask string `json:"debug_mask,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
SUMSVersion string `json:"sums_version,omitempty"`
|
||||
}
|
||||
26
pkg/common/apicalls.go
Normal file
26
pkg/common/apicalls.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
type AccessType string
|
||||
|
||||
const AccessTypeJWT = "jwt"
|
||||
const AccessTypeAPIToken = "api_token"
|
||||
|
||||
type APICall struct {
|
||||
// ClientID can be email or api_token.
|
||||
ClientID string `json:"client_id" pg:"client_id"`
|
||||
|
||||
// Check allowed access types above.
|
||||
AccessType string `json:"access_type" pg:"access_type"`
|
||||
Method string `json:"method" pg:"method"`
|
||||
Endpoint string `json:"endpoint" pg:"endpoint"`
|
||||
CreatedAt *time.Time `json:"created_at" pg:"default:now()"`
|
||||
}
|
||||
|
||||
// APICallsSearch is supposed to be used for calls only.
|
||||
type APICallsSearch struct {
|
||||
Search string
|
||||
From *time.Time
|
||||
To *time.Time
|
||||
}
|
||||
19
pkg/common/apitoken.go
Normal file
19
pkg/common/apitoken.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
type APIToken struct {
|
||||
Token string `json:"token" validate:"required,max=1000" pg:",pk"`
|
||||
Roles string `json:"roles" validate:"required,max=10000"`
|
||||
Description string `json:"description" validate:"required,max=1000"`
|
||||
ExpiresAt *time.Time `json:"expires_at" pg:"expires_at"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
func (a *APIToken) String() string {
|
||||
return fmt.Sprintf("APIToken<%s>", a.Token)
|
||||
}
|
||||
32
pkg/common/approval_update.go
Normal file
32
pkg/common/approval_update.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package common
|
||||
|
||||
func NewApprovalUpdates(cu *CarUpdate) ApprovalUpdate {
|
||||
a := ApprovalUpdate{}
|
||||
a.Update(cu)
|
||||
return a
|
||||
}
|
||||
|
||||
type ApprovalUpdate struct {
|
||||
ID int64 `json:"id,omitempty"`
|
||||
VIN string `json:"vin"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ReleaseNotes string `json:"release_notes,omitempty"`
|
||||
}
|
||||
|
||||
func (a *ApprovalUpdate) Update(cu *CarUpdate) {
|
||||
a.ID = cu.ID
|
||||
a.VIN = cu.VIN
|
||||
|
||||
if cu.UpdateManifest == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m := cu.UpdateManifest
|
||||
|
||||
a.Name = m.Name
|
||||
a.Version = m.Version
|
||||
a.Description = m.Description
|
||||
a.ReleaseNotes = m.ReleaseNotes
|
||||
}
|
||||
12
pkg/common/authproviders/authproviders.go
Normal file
12
pkg/common/authproviders/authproviders.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package authproviders
|
||||
|
||||
import "fiskerinc.com/modules/utils/envtool"
|
||||
|
||||
const (
|
||||
Default = "Default" // This is for any provider
|
||||
FiskerAD = "Fisker"
|
||||
FiskerQA = "Fisker-QA"
|
||||
FiskerAPIKey = "FiskerAPIKey"
|
||||
)
|
||||
|
||||
var Magna = envtool.GetEnv("MAGNA_PROVIDER", "REPLACE_ME")
|
||||
54
pkg/common/binary_hex._test.go
Normal file
54
pkg/common/binary_hex._test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
)
|
||||
|
||||
type testHexBinary struct {
|
||||
Data *common.BinaryHex `json:"data"`
|
||||
}
|
||||
|
||||
func TestBinaryHexMarshalJSON(t *testing.T) {
|
||||
expected := `"0000ffff"`
|
||||
value := common.BinaryHex{00, 00, 0xff, 0xff}
|
||||
|
||||
json, err := value.MarshalJSON()
|
||||
|
||||
testhelper.NoError(t, "MarshalJSON error", err)
|
||||
testhelper.Equal(t, "MarshalJSON json", expected, string(json))
|
||||
}
|
||||
|
||||
func TestBinaryHexUnmarshalJSON(t *testing.T) {
|
||||
expected := []byte{00, 00, 0xff, 0xff}
|
||||
value := common.BinaryHex{}
|
||||
|
||||
err := value.UnmarshalJSON([]byte(`"0000ffff"`))
|
||||
|
||||
testhelper.NoError(t, "UnmarshalJSON error", err)
|
||||
testhelper.EqualByteArray(t, "UnmarshalJSON len", expected, value)
|
||||
}
|
||||
|
||||
func TestBinaryHexStructMarshalJSON(t *testing.T) {
|
||||
expected := `{"data":"0000ffff"}`
|
||||
value := testHexBinary{Data: &common.BinaryHex{00, 00, 0xff, 0xff}}
|
||||
|
||||
json, err := json.Marshal(value)
|
||||
|
||||
testhelper.NoError(t, "MarshalJSON error", err)
|
||||
testhelper.Equal(t, "MarshalJSON json", expected, string(json))
|
||||
}
|
||||
|
||||
func TestBinaryHexStructUnmarshalJSON(t *testing.T) {
|
||||
obj := testHexBinary{}
|
||||
data := `{"data":"0e00ffff"}`
|
||||
expected := []byte{0x0e, 00, 0xff, 0xff}
|
||||
|
||||
err := json.Unmarshal([]byte(data), &obj)
|
||||
|
||||
testhelper.NoError(t, "UnmarshalJSON error", err)
|
||||
testhelper.EqualByteArray(t, "UnmarshalJSON json", *obj.Data, expected)
|
||||
}
|
||||
56
pkg/common/binary_hex.go
Normal file
56
pkg/common/binary_hex.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func NewBinaryHex(data []byte) BinaryHex {
|
||||
var result BinaryHex = data
|
||||
return result
|
||||
}
|
||||
|
||||
type BinaryHex []byte
|
||||
|
||||
func (bh BinaryHex) Bytes() []byte {
|
||||
return []byte(bh)
|
||||
}
|
||||
|
||||
func (bh *BinaryHex) SetBytes(value []byte) {
|
||||
v := BinaryHex(value)
|
||||
*bh = v
|
||||
}
|
||||
|
||||
func (bh BinaryHex) String() string {
|
||||
return hex.EncodeToString(bh)
|
||||
}
|
||||
|
||||
// TODO Hack to render []byte as int array for the JSON RPC. Otherwise Go renders in base64
|
||||
func (bh BinaryHex) UintArray() []uint {
|
||||
bytes := bh.Bytes()
|
||||
result := make([]uint, len(bytes))
|
||||
for i, d := range bytes {
|
||||
result[i] = uint(d)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (bh *BinaryHex) SetHex(data string) error {
|
||||
x, err := hex.DecodeString(strings.Trim(string(data), `"`))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*bh = x
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bh BinaryHex) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`"%s"`, bh.String())), nil
|
||||
}
|
||||
|
||||
func (bh *BinaryHex) UnmarshalJSON(data []byte) error {
|
||||
return bh.SetHex(string(data))
|
||||
}
|
||||
7
pkg/common/bulk_car_commands.go
Normal file
7
pkg/common/bulk_car_commands.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
// BulkCarCommands for sending commands to multiple cars
|
||||
type BulkCarCommands struct {
|
||||
VINs []string `json:"vins,omitempty" validate:"required,max=1000,dive,vin"`
|
||||
RemoteCommandSource
|
||||
}
|
||||
112
pkg/common/can.go
Normal file
112
pkg/common/can.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/grpc/kafka_grpc"
|
||||
)
|
||||
|
||||
// CANFrame provides struct for can bus messages
|
||||
type CANFrame struct {
|
||||
TimestampUSec int `json:"epoch_usec" parquet:"name=epoch_usec, type=INT64"`
|
||||
ID int `json:"id" parquet:"name=id, type=INT32"`
|
||||
Data string `json:"data" parquet:"name=data, type=BYTE_ARRAY"`
|
||||
}
|
||||
|
||||
type CANBusMessage struct {
|
||||
EpochUsec int `json:"epoch_usec"`
|
||||
Dropped int `json:"dropped"`
|
||||
Filtered int `json:"filtered"`
|
||||
Frames []CANFrame `json:"frames"`
|
||||
}
|
||||
|
||||
// CANSignal provides struct for parsed can bus messages
|
||||
type CANSignal struct {
|
||||
VIN string `json:"vin"`
|
||||
Timestamp float64 `json:"timestamp" parquet:"name=timestamp, type=FLOAT"`
|
||||
ID int `json:"id" parquet:"name=id, type=INT32"`
|
||||
Name string `json:"name" parquet:"name=name, type=BYTE_ARRAY"`
|
||||
Value float64 `json:"value" parquet:"name=value, type=FLOAT"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
type CANSignalExport struct {
|
||||
VIN string `json:"vin"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Name string `json:"name"`
|
||||
Value float64 `json:"value"`
|
||||
TimestampInMilli int64 `json:"tm"`
|
||||
}
|
||||
|
||||
// "missing destination name \"BCM_FrntDrDoorLockSts\" in *common.CANSignal"
|
||||
type CANSignalQuery struct {
|
||||
VIN string `json:"vin" validate:"required"`
|
||||
TimestampStart float64 `json:"timestamp_start" validate:"required"`
|
||||
TimestampEnd float64 `json:"timestamp_end" validate:"required"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
SelectAll bool `json:"select_all"`
|
||||
CanSignals []string `json:"can_signals"`
|
||||
}
|
||||
|
||||
type ExportCANSignalQuery struct {
|
||||
VIN string `json:"vin" validate:"required"`
|
||||
TimestampStart int64 `json:"timestamp_start" validate:"required"`
|
||||
TimestampEnd int64 `json:"timestamp_end" validate:"required"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
SelectAll bool `json:"select_all"`
|
||||
CanSignals []string `json:"can_signals"`
|
||||
}
|
||||
|
||||
type CANSignalNameList struct {
|
||||
Signal_Name string `json:"signal_name"`
|
||||
}
|
||||
|
||||
type CANSignalData struct {
|
||||
cansignals []CANSignal `json:"cansignals"`
|
||||
}
|
||||
|
||||
type CANSignalBatchPayload struct {
|
||||
Data CANSignalData `json:"data"`
|
||||
}
|
||||
|
||||
// CANFilter provides struct for filtering can messages based on ID
|
||||
type CANFilter struct {
|
||||
CANID string `json:"can_id" bson:"can_id" validate:"required,can_id"`
|
||||
Interval *int `json:"interval,omitempty" bson:"interval" validate:"omitempty,gte=0"`
|
||||
EdgeMask *BinaryHex `json:"edge_mask,omitempty" bson:"edge_mask,omitempty" validate:"omitempty,lte=10000"`
|
||||
}
|
||||
|
||||
// CANFilterWithFleet is used only for rendering vehicle's fleets' filters.
|
||||
type CANFilterWithFleet struct {
|
||||
CANID string `json:"can_id" bson:"can_id" validate:"required,can_id"`
|
||||
Interval *int `json:"interval,omitempty" bson:"interval" validate:"gte=0"`
|
||||
Fleet string `json:"fleet,omitempty" bson:"fleet,omitempty"`
|
||||
EdgeMask *BinaryHex `json:"edge_mask,omitempty" bson:"edge_mask,omitempty" validate:"omitempty,lte=10000"`
|
||||
}
|
||||
|
||||
func (c *CANSignalBatchPayload) ToGrpc(data []CANSignal) *kafka_grpc.GRPC_CANSignalBatchPayload {
|
||||
grpccansignals := make([]*kafka_grpc.GRPC_CANSignal, len(data))
|
||||
|
||||
msg := kafka_grpc.GRPC_CANSignalData{
|
||||
Cansignals: grpccansignals,
|
||||
}
|
||||
|
||||
for i, cs := range data {
|
||||
msg.Cansignals[i] = &kafka_grpc.GRPC_CANSignal{
|
||||
Vin: cs.VIN,
|
||||
Timestamp: cs.Timestamp,
|
||||
Id: int32(cs.ID),
|
||||
Name: cs.Name,
|
||||
Value: cs.Value,
|
||||
Description: cs.Description,
|
||||
}
|
||||
}
|
||||
|
||||
batchPayload := kafka_grpc.GRPC_CANSignalBatchPayload{
|
||||
Data: &msg,
|
||||
}
|
||||
|
||||
return &batchPayload
|
||||
}
|
||||
61
pkg/common/car.go
Normal file
61
pkg/common/car.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
const (
|
||||
InfoSourceAutoCreated = "autocreated"
|
||||
CarSoldStatusRetailed = "Retailed"
|
||||
)
|
||||
|
||||
type RegionCode string
|
||||
|
||||
const (
|
||||
US RegionCode = "US"
|
||||
EU RegionCode = "EU"
|
||||
)
|
||||
|
||||
// Car schema
|
||||
type Car struct {
|
||||
VIN string `pg:",pk" json:"vin" validate:"required,vin"`
|
||||
Region RegionCode `json:"region,omitempty"`
|
||||
Country string `json:"country,omitempty" validate:"max=256"`
|
||||
Year int `json:"year,omitempty" validate:"required,gte=1000,lte=9999"`
|
||||
Model string `json:"model,omitempty" validate:"required,max=256"`
|
||||
Trim string `json:"trim,omitempty" validate:"required,max=256"`
|
||||
Powertrain string `json:"powertrain,omitempty" validate:"max=256"`
|
||||
Restraint string `json:"restraint,omitempty" validate:"max=256"`
|
||||
BodyType string `json:"body_type,omitempty" validate:"max=256"`
|
||||
ECUList string `json:"ecu_list,omitempty"`
|
||||
ICCID string `json:"iccid,omitempty" validate:"omitempty,max=50"`
|
||||
InfoSource string `pg:"info_source" json:"-"`
|
||||
Blocked bool `pg:"blocked" json:"-"`
|
||||
Tags []string `json:"tags,omitempty" pg:"tags,array" validate:"omitempty,max=50"`
|
||||
Drivers []CarToDriver `pg:"-" json:"drivers,omitempty"`
|
||||
Manifests []StatusManifest `pg:"-" json:"manifests,omitempty"`
|
||||
Document string `pg:"-" json:"document,omitempty"`
|
||||
SoldStatus string `pg:"sold_status" json:"sold_status,omitempty"`
|
||||
SUMSVersion string `pg:"sums_version" json:"sums_version,omitempty"`
|
||||
OSVersion string `pg:"-" json:"os_version,omitempty"`
|
||||
Flashpack string `json:"flashpack,omitempty"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
// CarToDriver table storing cars-to-drivers
|
||||
type CarToDriver struct {
|
||||
ID int64 `json:"id"`
|
||||
VIN string `pg:",unique:carid_driverid" json:"vin,omitempty" validate:"required,vin"`
|
||||
DriverID string `pg:",unique:carid_driverid" json:"driverid,omitempty" validate:"required,max=256"`
|
||||
DriverRole string `json:"role" validate:"required,max=100"`
|
||||
BLEKey string `pg:"ble_key" json:"ble_key,omitempty" validation:"hexdecimal,max=32"`
|
||||
Settings []CarSetting `pg:"-" json:"settings,omitempty"`
|
||||
Subscriptions []Subscription `pg:"rel:has-many" json:"subscriptions,omitempty"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
func (c Car) String() string {
|
||||
return fmt.Sprintf("Car<%s %s %d>", c.VIN, c.Model, c.Year)
|
||||
}
|
||||
14
pkg/common/car_command_locks.go
Normal file
14
pkg/common/car_command_locks.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package common
|
||||
|
||||
const (
|
||||
LockDoor string = "lock"
|
||||
UnlockDoor string = "unlock"
|
||||
)
|
||||
|
||||
type CarCommandLocks struct {
|
||||
LeftFront string `json:"left_front,omitempty"`
|
||||
RightFront string `json:"right_front,omitempty"`
|
||||
LeftRear string `json:"left_rear,omitempty"`
|
||||
RightRear string `json:"right_rear,omitempty"`
|
||||
Trunk string `json:"trunk,omitempty"`
|
||||
}
|
||||
16
pkg/common/car_command_wins.go
Normal file
16
pkg/common/car_command_wins.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package common
|
||||
|
||||
const (
|
||||
OpenWindow string = "open"
|
||||
CloseWindow string = "close"
|
||||
)
|
||||
|
||||
type CarCommandWindows struct {
|
||||
LeftFront string `json:"left_front,omitempty"`
|
||||
RightFront string `json:"right_front,omitempty"`
|
||||
LeftRear string `json:"left_rear,omitempty"`
|
||||
RightRear string `json:"right_rear,omitempty"`
|
||||
LeftRearQuarter string `json:"left_rear_quarter,omitempty"`
|
||||
RightRearQuarter string `json:"right_rear_quarter,omitempty"`
|
||||
RearWindshield string `json:"rear_windshield,omitempty"`
|
||||
}
|
||||
76
pkg/common/car_data.go
Normal file
76
pkg/common/car_data.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"fiskerinc.com/modules/grpc/kafka_grpc"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type CANBusFrame struct {
|
||||
EpochUsec int64 `json:"epoch_usec"`
|
||||
ID int `json:"id"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
type CANBusMessageRaw struct {
|
||||
EpochUsec int64 `json:"epoch_usec"`
|
||||
Dropped int `json:"dropped"`
|
||||
Filtered int `json:"filtered"`
|
||||
Frames []CANBusFrame `json:"frames"`
|
||||
}
|
||||
type CarDataBatchPayloadRaw struct {
|
||||
Handler string `json:"handler"`
|
||||
Data CANBusMessageRaw `json:"data"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// CarDataBatchPayload is a payload received from T.Rex
|
||||
//
|
||||
// it contains batches of CANMessages (can.go)
|
||||
type CarDataBatchPayload struct {
|
||||
Handler string `json:"handler"`
|
||||
Data CANBusMessage `json:"data"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (c *CarDataBatchPayload) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*c)
|
||||
return data, errors.WithStack(err)
|
||||
|
||||
}
|
||||
|
||||
func (c *CarDataBatchPayload) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, c)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (c *CarDataBatchPayload) ToGrpc(data MessageRawJSON, vin string) (*kafka_grpc.GRPC_BatchPayload, error) {
|
||||
var payload CANBusMessageRaw
|
||||
err := json.Unmarshal(data.Data, &payload)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
frames := make([]*kafka_grpc.GRPC_CANFrame, len(payload.Frames))
|
||||
msg := kafka_grpc.GRPC_CANData{
|
||||
EpochUsec: payload.EpochUsec,
|
||||
Dropped: int32(payload.Dropped),
|
||||
Filtered: int32(payload.Filtered),
|
||||
Frames: frames,
|
||||
Vin: vin,
|
||||
}
|
||||
|
||||
for i, frame := range payload.Frames {
|
||||
msg.Frames[i] = &kafka_grpc.GRPC_CANFrame{
|
||||
Epoch: frame.EpochUsec,
|
||||
ID: int32(frame.ID),
|
||||
Value: []byte(frame.Data),
|
||||
}
|
||||
}
|
||||
batch_payload := kafka_grpc.GRPC_BatchPayload{
|
||||
Handler: data.Handler,
|
||||
Data: &msg,
|
||||
Version: data.Version,
|
||||
}
|
||||
return &batch_payload, nil
|
||||
}
|
||||
9
pkg/common/car_driver.go
Normal file
9
pkg/common/car_driver.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package common
|
||||
|
||||
type CarToDriverModel struct {
|
||||
User UserProfile `json:"user,omitempty"`
|
||||
DriverID string `json:"driver_id"`
|
||||
Role string `json:"role"`
|
||||
Settings []CarSetting `json:"settings"`
|
||||
Subscriptions []Subscription `json:"subscriptions"`
|
||||
}
|
||||
102
pkg/common/car_ecu.go
Normal file
102
pkg/common/car_ecu.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
type CarECU struct {
|
||||
VIN string `json:"vin,omitempty" pg:",unique:vin_ecu" validate:"required,vin"`
|
||||
ECU string `json:"ecu" pg:",unique:vin_ecu" validate:"required,max=100"`
|
||||
Version string `json:"sw_version" validate:"max=255"`
|
||||
SerialNumber string `json:"serial_number,omitempty" validate:"max=1024"`
|
||||
HWVersion string `json:"hw_version,omitempty" validate:"max=1024"`
|
||||
BootLoaderVersion string `json:"boot_loader_version,omitempty" validate:"max=1024"`
|
||||
Fingerprint string `json:"fingerprint,omitempty" validate:"max=1024"`
|
||||
// cloud/attendant/handlers/car_update_state.go JSON message config was renamed to code_data_string
|
||||
Config string `json:"code_data_string,omitempty" pg:"code_data_string" validate:"max=2048"` // config was renamed to code_data_string
|
||||
Vendor string `json:"vendor,omitempty" validate:"max=1024"`
|
||||
SupplierSWVersion string `json:"supplier_sw_version,omitempty" validate:"max=1024"`
|
||||
Epoch_usec int64 `json:"epoch_usec" pg:"epoch_usec"`
|
||||
ASSYNumber string `json:"assy_number,omitempty" pg:"assy_number"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
func (c *CarECU) CacheKey() string {
|
||||
return fmt.Sprintf("%s:%s", c.VIN, c.ECU)
|
||||
}
|
||||
|
||||
func (c *CarECU) HashValues() string {
|
||||
data := []byte(fmt.Sprintf("%s:%s:%s:%s:%s:%s:%s:%s:%s", c.Version, c.SerialNumber, c.HWVersion, c.BootLoaderVersion, c.Fingerprint, c.Config, c.Vendor, c.SupplierSWVersion, c.ASSYNumber))
|
||||
hash := sha256.Sum256(data)
|
||||
signature := hex.EncodeToString(hash[:])
|
||||
return signature
|
||||
}
|
||||
|
||||
// Ensure we always have the correct car_ecu name for OTA
|
||||
func (c *CarECU) TransformECUName() {
|
||||
replacement, ok := OTAUpdateECUReplacement[c.ECU]
|
||||
if ok {
|
||||
c.ECU = replacement
|
||||
}
|
||||
}
|
||||
|
||||
type CarECUFilter struct {
|
||||
VIN string
|
||||
ECUs []string
|
||||
Search string
|
||||
Unique bool
|
||||
FlashPackNumberExist bool
|
||||
HWVersionRequired bool // Ensure that the hw_version has a value. This may not retrieve the latest entry for the ecu, but until we find out why hw_versions are being inserted as empty
|
||||
}
|
||||
|
||||
type CarFlashpackVersion struct {
|
||||
Flashpack string `json:"flashpack" validate:"required,numeric"`
|
||||
CarModel string `json:"car_model" validate:"required"`
|
||||
CarTrim string `json:"car_trim" validate:"required"`
|
||||
CarYear int `json:"car_year" validate:"required"`
|
||||
CarECUName string `json:"car_ecu_name" validate:"required"`
|
||||
CarECUVersion string `json:"car_ecu_version" validate:"required"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
type CarECUVersion struct {
|
||||
CarECUName string `json:"car_ecu_name"`
|
||||
CarECUVersion string `json:"car_ecu_version"`
|
||||
}
|
||||
|
||||
type CarFlashpackVersionRequest struct {
|
||||
Flashpack string `json:"flashpack" validate:"required,numeric"`
|
||||
CarModel string `json:"car_model" validate:"required"`
|
||||
CarTrim string `json:"car_trim" validate:"required"`
|
||||
CarYear int `json:"car_year" validate:"required"`
|
||||
}
|
||||
|
||||
type ECUVersionRequest struct {
|
||||
CarECUName string `json:"car_ecu_name" validate:"required"`
|
||||
CarECUVersion string `json:"car_ecu_version" validate:"required"`
|
||||
}
|
||||
|
||||
type CarFlashpackVersionAddRequest struct {
|
||||
Flashpack string `json:"flashpack" validate:"required,numeric"`
|
||||
CarModel string `json:"car_model" validate:"required"`
|
||||
CarTrim string `json:"car_trim" validate:"required"`
|
||||
CarYear int `json:"car_year" validate:"required"`
|
||||
ECUVersions []ECUVersionRequest `json:"ecu_versions" validate:"required"`
|
||||
}
|
||||
|
||||
type CarFlashpackVersionResponse struct {
|
||||
Flashpack string `json:"flashpack"`
|
||||
CarModel string `json:"car_model"`
|
||||
CarTrim string `json:"car_trim"`
|
||||
CarYear int `json:"car_year"`
|
||||
}
|
||||
|
||||
type CarFlashpackVersionInfoResponse struct {
|
||||
Flashpack string `json:"flashpack"`
|
||||
NextFlashpack string `json:"next_flashpack"`
|
||||
ECUVersions []CarECUVersion `json:"ecu_versions"`
|
||||
}
|
||||
41
pkg/common/car_location.go
Normal file
41
pkg/common/car_location.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Location represents the location state of the car
|
||||
type Location struct {
|
||||
Altitude float64 `json:"altitude" redis:"altitude"`
|
||||
Longitude float64 `json:"longitude" redis:"longitude"`
|
||||
Latitude float64 `json:"latitude" redis:"latitude"`
|
||||
Heading float64 `json:"-" redis:"heading"`
|
||||
}
|
||||
|
||||
func (l *Location) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*l)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (l *Location) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, l)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (it *Location) MarshalBinary() ([]byte, error) {
|
||||
return json.Marshal(it)
|
||||
}
|
||||
|
||||
func (it *Location) UnmarshalBinary(data []byte) error {
|
||||
return json.Unmarshal(data, it)
|
||||
}
|
||||
|
||||
type JSONCarLocation struct {
|
||||
VIN string `json:"vin"`
|
||||
Altitude float64 `json:"altitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Heading float64 `json:"heading"`
|
||||
}
|
||||
15
pkg/common/car_search.go
Normal file
15
pkg/common/car_search.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package common
|
||||
|
||||
type CarSearch struct {
|
||||
Search string `json:"search" validate:"max=1024"`
|
||||
VINs string `json:"vins" validate:"omitempty"`
|
||||
Online *CarOnlineFilter
|
||||
NoEU bool `json:"no_eu", validate:"omitempty"`
|
||||
Car
|
||||
}
|
||||
|
||||
type CarOnlineFilter struct {
|
||||
Online *bool
|
||||
HMI *bool
|
||||
VINsOnline []string
|
||||
}
|
||||
80
pkg/common/car_setting.go
Normal file
80
pkg/common/car_setting.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
type CarSetting struct {
|
||||
VIN string `pg:"vin" json:"-"`
|
||||
DriverID string `pg:"driver_id" json:"-"`
|
||||
Name string `pg:",pk" json:"name"`
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
type MobileSettingsUpdate struct {
|
||||
VIN string `json:"vin"`
|
||||
Settings []CarSetting `json:"settings"`
|
||||
}
|
||||
|
||||
type HMISettingsUpdate struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
Settings []CarSetting `json:"settings"`
|
||||
}
|
||||
|
||||
/*
|
||||
It would be nice if we could do this easily, but requires a cast to string which isn't so nice
|
||||
type CarSettingEnum string
|
||||
*/
|
||||
const (
|
||||
SEQUENCE_NUMBER string = "SEQUENCE_NUMBER"
|
||||
BODY_COLOR string = "BODY_COLOR"
|
||||
DELIVERY_DESTINATION string = "DELIVERY_DESTINATION"
|
||||
)
|
||||
|
||||
// Take in the feature codes for the car, and convert it to body color string, will probably change
|
||||
func FeatureCodeToBodyColor(VehicleFeatures []FeatureCodes) (bodyColor string) {
|
||||
var colorCode string
|
||||
for x := range VehicleFeatures {
|
||||
if VehicleFeatures[x].FamilyCode == "0103" {
|
||||
colorCode = VehicleFeatures[x].FeatureCode
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch colorCode {
|
||||
case "010300":
|
||||
return "PRIMERED"
|
||||
case "010301":
|
||||
return "SOLID_WHITE"
|
||||
case "010302":
|
||||
return "SOLID_BLACK"
|
||||
case "010303":
|
||||
return "BLUE_GREY_MET"
|
||||
case "010304":
|
||||
return "MID_BLUE_GLOSS"
|
||||
case "010305":
|
||||
return "MID_BLUE_MATTE"
|
||||
case "010306":
|
||||
return "VIVID_BLUE"
|
||||
case "010307":
|
||||
return "SPE_COOL_SILVER"
|
||||
case "010308":
|
||||
return "STEALTH_GREEN"
|
||||
case "010309":
|
||||
return "VIVID_ORANGE"
|
||||
case "010310":
|
||||
return "EARTH_COPPER"
|
||||
case "010311":
|
||||
return "METALLIC_BLUE_BLACK"
|
||||
case "010312":
|
||||
return "WHITE_PEARL"
|
||||
case "010313":
|
||||
return "STEALTH_GREEN_GLOSS"
|
||||
case "010314":
|
||||
return "SOLID_RED"
|
||||
default:
|
||||
return "MISSING_COLOR"
|
||||
}
|
||||
}
|
||||
552
pkg/common/car_state.go
Normal file
552
pkg/common/car_state.go
Normal file
@@ -0,0 +1,552 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type CarState struct {
|
||||
Online bool `json:"online"`
|
||||
OnlineHMI bool `json:"online_hmi"`
|
||||
Battery *Battery `json:"battery,omitempty"`
|
||||
MaxRange *MaxRange `json:"max_range,omitempty"`
|
||||
Doors *Doors `json:"doors,omitempty"`
|
||||
Location *Location `json:"location,omitempty"`
|
||||
Locks *Locks `json:"door_locks,omitempty"`
|
||||
Windows *Windows `json:"windows,omitempty"`
|
||||
MiscWindows *MiscWindows `json:"misc_windows,omitempty"`
|
||||
Sunroof *Sunroof `json:"sunroof,omitempty"`
|
||||
CabinClimate *CabinClimate `json:"cabin_climate,omitempty"`
|
||||
RearDefrost *RearDefrost `json:"rear_defrost,omitempty"`
|
||||
DriverSeatHeat *DriverSeatHeat `json:"driver_seat_heat,omitempty"`
|
||||
PassengerSeatHeat *PassengerSeatHeat `json:"passenger_seat_heat,omitempty"`
|
||||
SteeringWheelHeat *SteeringWheelHeat `json:"steering_wheel_heat,omitempty"`
|
||||
AmbientTemperature *AmbientTemperature `json:"ambient_temperature,omitempty"`
|
||||
CellTemperature *CellTemperature `json:"cell_temp,omitempty"`
|
||||
VehicleSpeed *VehicleSpeed `json:"vehicle_speed,omitempty"`
|
||||
VCU0x260 *VCU0x260Descriptor `json:"vcu0x260,omitempty"`
|
||||
ChargingMetrics *VCUChargingMetrics `json:"charging_metrics,omitempty"`
|
||||
Gear *Gear `json:"gear,omitempty"`
|
||||
StateOfCharge *StateOfCharge `json:"state_of_charge,omitempty"`
|
||||
TRexVersion string `json:"trex_version,omitempty"`
|
||||
DBCVersion string `json:"dbc_version,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated,omitempty"`
|
||||
SafeState *SafeState `json:"safe_state,omitempty"`
|
||||
DriverOccupySeatState *int `json:"driver_occupy_seat_state,omitempty"`
|
||||
PowerMode *int `json:"power_mode,omitempty"`
|
||||
ChargingStatus *int `json:"charging_status,omitempty"`
|
||||
VehicleReadyState *VehicleReadyState `json:"vehicle_ready_state,omitempty"`
|
||||
Battery12V *Battery12V `json:"battery_12v,omitempty"` // maybe make it Battery12v
|
||||
ExpandedSignals *ExpandedSignals `json:"expanded_signals,omitempty"`
|
||||
}
|
||||
|
||||
func (c *CarState) GetBattery() *Battery {
|
||||
if c.Battery == nil {
|
||||
c.Battery = &Battery{}
|
||||
}
|
||||
|
||||
return c.Battery
|
||||
}
|
||||
|
||||
func (c *CarState) GetMaxRange() *MaxRange {
|
||||
if c.MaxRange == nil {
|
||||
c.MaxRange = &MaxRange{}
|
||||
}
|
||||
|
||||
return c.MaxRange
|
||||
}
|
||||
|
||||
func (c *CarState) GetDoors() *Doors {
|
||||
if c.Doors == nil {
|
||||
c.Doors = &Doors{}
|
||||
}
|
||||
|
||||
return c.Doors
|
||||
}
|
||||
|
||||
func (c *CarState) UpdateLocation(value []byte) error {
|
||||
loc := Location{}
|
||||
err := loc.Unmarshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
location := c.GetLocation()
|
||||
location.Latitude = loc.Latitude
|
||||
location.Longitude = loc.Longitude
|
||||
|
||||
// Altitude could have already been set from GPS_ALTITUDE so do not overwrite unless it is a non-zero
|
||||
if loc.Altitude != 0 {
|
||||
location.Altitude = loc.Altitude
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CarState) GetLocation() *Location {
|
||||
if c.Location == nil {
|
||||
c.Location = &Location{}
|
||||
}
|
||||
|
||||
return c.Location
|
||||
}
|
||||
|
||||
func (c *CarState) GetLocks() *Locks {
|
||||
if c.Locks == nil {
|
||||
c.Locks = &Locks{}
|
||||
}
|
||||
|
||||
return c.Locks
|
||||
}
|
||||
|
||||
func (c *CarState) GetWindows() *Windows {
|
||||
if c.Windows == nil {
|
||||
c.Windows = &Windows{}
|
||||
}
|
||||
|
||||
return c.Windows
|
||||
}
|
||||
|
||||
func (c *CarState) GetMiscWindows() *MiscWindows {
|
||||
if c.MiscWindows == nil {
|
||||
c.MiscWindows = &MiscWindows{}
|
||||
}
|
||||
|
||||
return c.MiscWindows
|
||||
}
|
||||
|
||||
func (c *CarState) GetSunroof() *Sunroof {
|
||||
if c.Sunroof == nil {
|
||||
c.Sunroof = &Sunroof{}
|
||||
}
|
||||
|
||||
return c.Sunroof
|
||||
}
|
||||
|
||||
func (c *CarState) GetCabinClimate() *CabinClimate {
|
||||
if c.CabinClimate == nil {
|
||||
c.CabinClimate = &CabinClimate{}
|
||||
}
|
||||
|
||||
return c.CabinClimate
|
||||
}
|
||||
|
||||
func (c *CarState) GetRearDefrost() *RearDefrost {
|
||||
if c.RearDefrost == nil {
|
||||
c.RearDefrost = &RearDefrost{}
|
||||
}
|
||||
|
||||
return c.RearDefrost
|
||||
}
|
||||
|
||||
func (c *CarState) GetDriverSeatHeat() *DriverSeatHeat {
|
||||
if c.DriverSeatHeat == nil {
|
||||
c.DriverSeatHeat = &DriverSeatHeat{}
|
||||
}
|
||||
|
||||
return c.DriverSeatHeat
|
||||
}
|
||||
|
||||
func (c *CarState) GetPassengerSeatHeat() *PassengerSeatHeat {
|
||||
if c.PassengerSeatHeat == nil {
|
||||
c.PassengerSeatHeat = &PassengerSeatHeat{}
|
||||
}
|
||||
|
||||
return c.PassengerSeatHeat
|
||||
}
|
||||
|
||||
func (c *CarState) GetSteeringWheelHeat() *SteeringWheelHeat {
|
||||
if c.SteeringWheelHeat == nil {
|
||||
c.SteeringWheelHeat = &SteeringWheelHeat{}
|
||||
}
|
||||
|
||||
return c.SteeringWheelHeat
|
||||
}
|
||||
|
||||
func (c *CarState) GetAmbientTemperature() *AmbientTemperature {
|
||||
if c.AmbientTemperature == nil {
|
||||
c.AmbientTemperature = &AmbientTemperature{}
|
||||
}
|
||||
|
||||
return c.AmbientTemperature
|
||||
}
|
||||
|
||||
func (c *CarState) GetCellTemperature() *CellTemperature {
|
||||
if c.CellTemperature == nil {
|
||||
c.CellTemperature = &CellTemperature{}
|
||||
}
|
||||
|
||||
return c.CellTemperature
|
||||
}
|
||||
|
||||
func (c *CarState) GetVCU0x260() *VCU0x260Descriptor {
|
||||
if c.VCU0x260 == nil {
|
||||
c.VCU0x260 = &VCU0x260Descriptor{}
|
||||
}
|
||||
|
||||
return c.VCU0x260
|
||||
}
|
||||
|
||||
func (c *CarState) GetChargingMetrics() *VCUChargingMetrics {
|
||||
if c.ChargingMetrics == nil {
|
||||
c.ChargingMetrics = &VCUChargingMetrics{}
|
||||
}
|
||||
|
||||
return c.ChargingMetrics
|
||||
}
|
||||
|
||||
func (c *CarState) GetGear() *Gear {
|
||||
if c.Gear == nil {
|
||||
c.Gear = &Gear{}
|
||||
}
|
||||
|
||||
return c.Gear
|
||||
}
|
||||
|
||||
func (c *CarState) GetStateOfCharge() *StateOfCharge {
|
||||
if c.StateOfCharge == nil {
|
||||
c.StateOfCharge = &StateOfCharge{}
|
||||
}
|
||||
|
||||
return c.StateOfCharge
|
||||
}
|
||||
|
||||
func (c *CarState) GetVehicleSpeed() *VehicleSpeed {
|
||||
if c.VehicleSpeed == nil {
|
||||
c.VehicleSpeed = &VehicleSpeed{}
|
||||
}
|
||||
|
||||
return c.VehicleSpeed
|
||||
}
|
||||
|
||||
// Battery represents the battery state of the car
|
||||
type Battery struct {
|
||||
Percent int `json:"percent" redis:"percent"`
|
||||
TotalMileageOdometer int `json:"total_mileage_odometer"`
|
||||
BatteryVoltage float64 `json:"battery_voltage"` // The fact that this is called battery voltage is really dumb. Its the 12 volt battery, not the high voltage one
|
||||
}
|
||||
|
||||
func (b *Battery) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*b)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (b *Battery) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, b)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// StateOfCharge represents the battery state of charge
|
||||
type StateOfCharge struct {
|
||||
Usable int `json:"usable"`
|
||||
Health int `json:"health"`
|
||||
}
|
||||
|
||||
type SafeState struct {
|
||||
VehicleSafeState bool `json:"vehicle_safe_state" redis:"vehicle_safe_state"`
|
||||
VCUSafeState bool `json:"vcu_safe_state" redis:"vcu_safe_state"`
|
||||
MCUFrontSafeState bool `json:"mcu_front_safe_state" redis:"mcu_front_safe_state"`
|
||||
MCURearSafeState bool `json:"mcu_rear_safe_state" redis:"mcu_rear_safe_state"`
|
||||
MCURearDecoupState bool `json:"mcu_rear_decoup_state" redis:"mcu_rear_decoup_state"`
|
||||
MCUFrontInverterError bool `json:"mcu_front_inverter_error" redis:"mcu_front_inverter_error"`
|
||||
MCURearInverterError bool `json:"mcu_rear_inverter_error" redis:"mcu_rear_inverter_error"`
|
||||
}
|
||||
|
||||
func (s *SafeState) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*s)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (s *SafeState) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, s)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (c *CarState) GetSafeState() *SafeState {
|
||||
if c.SafeState == nil {
|
||||
c.SafeState = &SafeState{}
|
||||
}
|
||||
|
||||
return c.SafeState
|
||||
}
|
||||
|
||||
// MaxRange represents the predicted max range of the car
|
||||
type MaxRange struct {
|
||||
MaxMiles int `json:"max_miles" redis:"max_miles"`
|
||||
}
|
||||
|
||||
type VehicleReadyState struct {
|
||||
IsVehicleReady bool `json:"is_vehicle_ready" redis:"is_vehicle_ready"`
|
||||
}
|
||||
|
||||
func (c *CarState) GetVehicleReadyState() *VehicleReadyState {
|
||||
if c.VehicleReadyState == nil {
|
||||
c.VehicleReadyState = &VehicleReadyState{}
|
||||
}
|
||||
return c.VehicleReadyState
|
||||
}
|
||||
|
||||
func (b *MaxRange) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*b)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (b *MaxRange) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, b)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// Doors represents the doors state of the car
|
||||
// false means closed, true means open
|
||||
type Doors struct {
|
||||
Hood bool `json:"hood" redis:"hood"`
|
||||
LeftFront bool `json:"left_front" redis:"left_front"`
|
||||
LeftRear bool `json:"left_rear" redis:"left_rear"`
|
||||
RightFront bool `json:"right_front" redis:"right_front"`
|
||||
RightRear bool `json:"right_rear" redis:"right_rear"`
|
||||
Trunk bool `json:"trunk" redis:"trunk"`
|
||||
}
|
||||
|
||||
func (d *Doors) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*d)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (d *Doors) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, d)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// Locks represents the lock state of the car
|
||||
type Locks struct {
|
||||
Driver bool `json:"driver" redis:"driver"`
|
||||
All bool `json:"all" redis:"all"`
|
||||
}
|
||||
|
||||
func (l *Locks) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*l)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (l *Locks) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, l)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// Windows represents the windows state of the car
|
||||
//
|
||||
// value is a percentage 0-100 in increments of 0.5
|
||||
type Windows struct {
|
||||
LeftFront int `json:"left_front" redis:"left_front"`
|
||||
LeftRear int `json:"left_rear" redis:"left_rear"`
|
||||
RightFront int `json:"right_front" redis:"right_front"`
|
||||
RightRear int `json:"right_rear" redis:"right_rear"`
|
||||
}
|
||||
|
||||
func (w *Windows) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*w)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (w *Windows) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, w)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// MiscWindows represents the windows state of the car for misc windows (left / right rear quarter and rear windshield)
|
||||
// value is a percentage 0-100 in increments of 0.5
|
||||
type MiscWindows struct {
|
||||
LeftRearQuarter int `json:"left_rear_quarter" redis:"left_rear_quarter"`
|
||||
RightRearQuarter int `json:"right_rear_quarter" redis:"right_rear_quarter"`
|
||||
RearWindshield int `json:"rear_windshield" redis:"rear_windshield"`
|
||||
}
|
||||
|
||||
func (w *MiscWindows) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*w)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (w *MiscWindows) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, w)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
type Sunroof struct {
|
||||
Sunroof int `json:"sunroof" redis:"sunroof"`
|
||||
}
|
||||
|
||||
func (s *Sunroof) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*s)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (s *Sunroof) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, s)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
type CabinClimate struct {
|
||||
CabinTemperature int `json:"cabin_temperature" redis:"cabin_temperature"`
|
||||
InternalTemperature int `json:"internal_temperature" redis:"internal_temperature"`
|
||||
}
|
||||
|
||||
func (w *CabinClimate) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*w)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (w *CabinClimate) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, w)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
type RearDefrost struct {
|
||||
On bool `json:"on" redis:"on"`
|
||||
}
|
||||
|
||||
func (w *RearDefrost) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*w)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (w *RearDefrost) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, w)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
type DriverSeatHeat struct {
|
||||
Level int `json:"level" redis:"level"`
|
||||
}
|
||||
|
||||
func (w *DriverSeatHeat) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*w)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (w *DriverSeatHeat) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, w)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
type PassengerSeatHeat struct {
|
||||
Level int `json:"level" redis:"level"`
|
||||
}
|
||||
|
||||
func (w *PassengerSeatHeat) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*w)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (w *PassengerSeatHeat) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, w)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
type SteeringWheelHeat struct {
|
||||
On bool `json:"on" redis:"on"`
|
||||
}
|
||||
|
||||
func (w *SteeringWheelHeat) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*w)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (w *SteeringWheelHeat) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, w)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
type AmbientTemperature struct {
|
||||
Temperature int `json:"temperature" redis:"temperature"`
|
||||
}
|
||||
|
||||
func (w *AmbientTemperature) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*w)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (w *AmbientTemperature) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, w)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
type VehicleSpeed struct {
|
||||
Speed float64 `json:"speed" redis:"speed"`
|
||||
}
|
||||
|
||||
func (w *VehicleSpeed) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*w)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (w *VehicleSpeed) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, w)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
type CellTemperature struct {
|
||||
AvgBatteryTemp int `json:"avg_battery_temp"`
|
||||
}
|
||||
|
||||
type VCU0x260Descriptor struct {
|
||||
ChargeType string `json:"charge_type"`
|
||||
}
|
||||
|
||||
type VCUChargingMetrics struct {
|
||||
RemainingChargingTime int `json:"remaining_charging_time"`
|
||||
RemainingChargingTimeFull int `json:"remaining_charging_time_full"`
|
||||
}
|
||||
|
||||
type Gear struct {
|
||||
InPark bool `json:"in_park"`
|
||||
Immobilizer string `json:"immobilizer,omitempty"`
|
||||
}
|
||||
|
||||
type Battery12V struct {
|
||||
IBS_BatteryVoltage *float64 `json:"voltage,omitempty"` // 12 Volt battery voltage
|
||||
IBS_StateOfCharge *float64 `json:"percent_charge,omitempty"` // Percentages of the voltage out of about 15.5 Volts
|
||||
IBS_StateOfHealth *int `json:"health,omitempty"` // estimated health of the 12v battery
|
||||
}
|
||||
|
||||
func (c *CarState) GetBattery12V() *Battery12V {
|
||||
if c.Battery12V == nil {
|
||||
c.Battery12V = &Battery12V{}
|
||||
}
|
||||
|
||||
return c.Battery12V
|
||||
}
|
||||
|
||||
type ExpandedSignals struct {
|
||||
// IBS_SOCUpperTolerance *float64 //unconfirmed
|
||||
// IBS_SOCLowerTolerance *float64 //unconfirmed
|
||||
IBS_NominalCapacity *int `json:",omitempty"`
|
||||
IBS_AvailableCapacity *int `json:",omitempty"`
|
||||
BCM_TotMilg_ODO *float64 `json:",omitempty"`
|
||||
BMS_SwVersS *int `json:",omitempty"`
|
||||
BMS_SwVersM *int `json:",omitempty"`
|
||||
BMS_SwVers *int `json:",omitempty"`
|
||||
BMS_AccueDchaTotAh *int `json:",omitempty"`
|
||||
BMS_AccueChrgTotAh *int `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (c *CarState) GetExpandedSignals() *ExpandedSignals {
|
||||
if c.ExpandedSignals == nil {
|
||||
c.ExpandedSignals = &ExpandedSignals{}
|
||||
}
|
||||
|
||||
return c.ExpandedSignals
|
||||
}
|
||||
|
||||
// I am quite certain there is no reason to have these custom marshalers, but want to keep with the current form incase of unexpected side effects
|
||||
func (w *ExpandedSignals) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*w)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (w *ExpandedSignals) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, w)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
16
pkg/common/car_state_al.go
Normal file
16
pkg/common/car_state_al.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package common
|
||||
|
||||
// CarStateAL builds on top of the normal car state, and includes the parsing of a few additional fields
|
||||
type CarStateAL struct {
|
||||
*CarState
|
||||
PKCVersion string `json:"pkc_version"`
|
||||
SumsVersion string `json:"sums_version"`
|
||||
OSVersion string `json:"os_version"`
|
||||
}
|
||||
|
||||
type CarPKCOSVersion struct {
|
||||
Vin string
|
||||
PKCVersion string
|
||||
SumsVersion string
|
||||
OSVersion string
|
||||
}
|
||||
33
pkg/common/car_state_towman.go
Normal file
33
pkg/common/car_state_towman.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package common
|
||||
|
||||
type TowmanDigitalTwin struct {
|
||||
Online bool `json:"online"`
|
||||
Location *Location `json:"location"`
|
||||
Gear *Gear `json:"gear"`
|
||||
Charging *bool `json:"charging"`
|
||||
}
|
||||
|
||||
func (c *TowmanDigitalTwin) GetLocation() *Location {
|
||||
if c.Location == nil {
|
||||
c.Location = &Location{}
|
||||
}
|
||||
|
||||
return c.Location
|
||||
}
|
||||
|
||||
func (c *TowmanDigitalTwin) GetGear() *Gear {
|
||||
if c.Gear == nil {
|
||||
c.Gear = &Gear{}
|
||||
}
|
||||
|
||||
return c.Gear
|
||||
}
|
||||
|
||||
func (c *TowmanDigitalTwin) GetCharging() *bool {
|
||||
if c.Charging == nil {
|
||||
temp := false
|
||||
c.Charging = &temp
|
||||
}
|
||||
|
||||
return c.Charging
|
||||
}
|
||||
5
pkg/common/car_state_update.go
Normal file
5
pkg/common/car_state_update.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package common
|
||||
|
||||
type CarStateUpdate struct {
|
||||
ECUs map[string]CarECU `json:"ecus" validate:"required,min=1"`
|
||||
}
|
||||
37
pkg/common/car_update.go
Normal file
37
pkg/common/car_update.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
// CarUpdate schema
|
||||
type CarUpdate struct {
|
||||
ID int64 `json:"id,omitempty"`
|
||||
VIN string `pg:",unique:vin_update_manifest" json:"vin" validate:"required,max=17"`
|
||||
UpdateManifestID int64 `pg:",unique:vin_update_manifest" json:"manifest_id,omitempty" validate:"required"`
|
||||
Status string `pg:"default:'pending'" json:"status,omitempty" validate:"max=100"`
|
||||
ErrorCode int `json:"err,omitempty"`
|
||||
Info string `json:"info,omitempty" pg:"info" validate:"max=1000"`
|
||||
Username string `json:"username,omitempty" validate:"required"`
|
||||
UpdateManifest *UpdateManifest `pg:"rel:has-one" json:"updatemanifest,omitempty"`
|
||||
UpdateSource string
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
func (cu CarUpdate) String() string {
|
||||
return fmt.Sprintf("CarUpdate<%d %d %s %s>", cu.ID, cu.UpdateManifestID, cu.VIN, cu.Status)
|
||||
}
|
||||
|
||||
func (cu *CarUpdate) Scrub() {
|
||||
cu.UpdateManifestID = 0
|
||||
cu.UpdateManifest = nil
|
||||
cu.CreatedAt = nil
|
||||
cu.UpdatedAt = nil
|
||||
}
|
||||
|
||||
const (
|
||||
UPDATE_SOURCE_OTA = "OTA" // The cloud has deployed this update
|
||||
UPDATE_SOURCE_AFTERSALES = "AFTERSALES" // An update generated to be sent by aftersales.
|
||||
UPDATE_SOURCE_FLASHPACK = "FLASHPACK"
|
||||
)
|
||||
29
pkg/common/car_update_progress.go
Normal file
29
pkg/common/car_update_progress.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package common
|
||||
|
||||
// CarUpdateProgress represents multi-file update download progress
|
||||
// If you change this structure and it relevant database entry, please update LogStatusIfNotARepeat
|
||||
// cloud/modules_go/db/queries/carupdates.go:194
|
||||
type CarUpdateProgress struct {
|
||||
FileCurrent uint64 `json:"file_current" redis:"file_size"`
|
||||
FileTotal uint64 `json:"file_total" redis:"file_total"`
|
||||
PackageCurrent uint64 `json:"package_current" redis:"current_size"`
|
||||
PackageTotal uint64 `json:"package_total" redis:"total_size"`
|
||||
InstalledFiles int `json:"installed" redis:"installed"`
|
||||
TotalFiles int `json:"total_files" redis:"total_files"`
|
||||
CarUpdateID int64 `json:"car_update_id" redis:"id"`
|
||||
ECU string `json:"ecu" redis:"ecu" validate:"max=100"`
|
||||
Status string `json:"msg" redis:"status,omitempty" validate:"max=1000"`
|
||||
Info string `json:"extra_info,omitempty" redis:"info,omitempty" validate:"max=1000"`
|
||||
ErrorCode int `json:"err" redis:"errorcode"`
|
||||
}
|
||||
|
||||
func (cu *CarUpdateProgress) Combine(status *CarUpdateProgress) {
|
||||
if status == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cu.PackageCurrent += status.PackageCurrent
|
||||
cu.PackageTotal += status.PackageTotal
|
||||
cu.InstalledFiles += status.InstalledFiles
|
||||
cu.TotalFiles += status.TotalFiles
|
||||
}
|
||||
37
pkg/common/car_update_progress_test.go
Normal file
37
pkg/common/car_update_progress_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
)
|
||||
|
||||
func TestCarUpdateProgressCombine(t *testing.T) {
|
||||
status1 := common.CarUpdateProgress{
|
||||
InstalledFiles: 0,
|
||||
TotalFiles: 1,
|
||||
PackageCurrent: 100,
|
||||
PackageTotal: 200,
|
||||
}
|
||||
status2 := common.CarUpdateProgress{
|
||||
InstalledFiles: 1,
|
||||
TotalFiles: 2,
|
||||
PackageCurrent: 200,
|
||||
PackageTotal: 300,
|
||||
}
|
||||
|
||||
status1.Combine(&status2)
|
||||
if status1.InstalledFiles != 1 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "InstalledFiles", 1, status1.InstalledFiles)
|
||||
}
|
||||
if status1.TotalFiles != 3 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "TotalFiles", 3, status1.TotalFiles)
|
||||
}
|
||||
if status1.PackageCurrent != 300 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "PackageCurrent", 300, status1.PackageCurrent)
|
||||
}
|
||||
if status1.PackageTotal != 500 {
|
||||
t.Errorf(testhelper.TestErrorTemplate, "PackageTotal", 500, status1.PackageTotal)
|
||||
}
|
||||
}
|
||||
17
pkg/common/car_update_status.go
Normal file
17
pkg/common/car_update_status.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
// CarUpdateStatus database model for logging history of car updates
|
||||
// If this model is changed for the database, please update LogStatusIfNotARepeat
|
||||
// cloud/modules_go/db/queries/carupdates.go:194
|
||||
type CarUpdateStatus struct {
|
||||
ID int64 `json:"id" pg:",pk"`
|
||||
CarUpdateID int64 `json:"carupdate_id"`
|
||||
Status string `json:"status" validate:"max=100"`
|
||||
ErrorCode int `json:"error_code"`
|
||||
Info string `json:"info,omitempty" pg:"info" validate:"max=1000"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
20
pkg/common/car_version_log.go
Normal file
20
pkg/common/car_version_log.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
type VersionSource string
|
||||
|
||||
const (
|
||||
DBCVersionSource VersionSource = "DBC"
|
||||
TREXVersionSource VersionSource = "TREX"
|
||||
FlashpackVersionSource VersionSource = "Flashpack"
|
||||
)
|
||||
|
||||
// CarVersionLogs is used for logging dbc version changes to DB.
|
||||
type CarVersionLogs struct {
|
||||
ID int64 `json:"id" pg:"id"`
|
||||
VIN string `json:"vin" pg:"vin"`
|
||||
VersionSource VersionSource `json:"version_source" pg:"version_source"`
|
||||
Version string `json:"version" pg:"version"`
|
||||
CreatedAt *time.Time `json:"created_at" pg:"created_at"`
|
||||
}
|
||||
60
pkg/common/carupdatestatus/carupdatestatus.go
Normal file
60
pkg/common/carupdatestatus/carupdatestatus.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package carupdatestatus
|
||||
|
||||
const (
|
||||
ManifestReceived = "manifest_received"
|
||||
ManifestAccepted = "manifest_accepted"
|
||||
ManifestRejected = "manifest_rejected"
|
||||
ManifestCancelPending = "manifest_cancel_pending"
|
||||
ManifestCancelReceived = "manifest_cancel_received"
|
||||
ManifestCancelAccepted = "manifest_cancel_accepted"
|
||||
ManifestCancelRejected = "manifest_cancel_rejected"
|
||||
ManifestValidationSucceeded = "manifest_validation_succeeded"
|
||||
ManifestValidationFailed = "manifest_validation_failed"
|
||||
DownloadStarted = "download_started"
|
||||
Downloading = "downloading"
|
||||
DownloadCompleted = "download_completed"
|
||||
DownloadFailed = "download_failed"
|
||||
InstallApprovalAwait = "install_approval_await"
|
||||
InstallApprovalReceived = "install_approval_received"
|
||||
InstallStarted = "install_started"
|
||||
Installing = "installing"
|
||||
InstallSucceeded = "install_succeeded"
|
||||
InstallFailed = "install_failed"
|
||||
RollbackStarted = "rollback_started"
|
||||
RollbackSucceeded = "rollback_succeeded"
|
||||
RollbackFailed = "rollback_failed"
|
||||
CleanupSucceeded = "cleanup_succeeded"
|
||||
CleanupFailed = "cleanup_failed"
|
||||
ManifestError = "manifest_error"
|
||||
ManifestRollback = "manifest_rollback"
|
||||
ManifestSucceeded = "manifest_succeeded"
|
||||
ManifestCanceled = "manifest_canceled"
|
||||
ManifestPending = "manifest_pending"
|
||||
Pending = "pending"
|
||||
Sent = "sent"
|
||||
RequirementsFailed = "requirements_failed"
|
||||
RequirementsAwait = "requirements_await"
|
||||
InstallScheduled = "install_scheduled"
|
||||
InitialFlashPack = "initial_flashpack_install"
|
||||
)
|
||||
|
||||
// These final update statuses are ones that we will want to filter out
|
||||
var FINAL_UPDATE_STATUS = []string{
|
||||
ManifestSucceeded,
|
||||
ManifestCanceled,
|
||||
ManifestError,
|
||||
DownloadFailed,
|
||||
ManifestCancelPending,
|
||||
RollbackSucceeded,
|
||||
ManifestRejected,
|
||||
RollbackFailed,
|
||||
CleanupSucceeded,
|
||||
}
|
||||
|
||||
var NoRepeatUpdateStatus = map[string]struct{}{}
|
||||
|
||||
func init() {
|
||||
for _, status := range FINAL_UPDATE_STATUS {
|
||||
NoRepeatUpdateStatus[status] = struct{}{}
|
||||
}
|
||||
}
|
||||
57
pkg/common/certificate.go
Normal file
57
pkg/common/certificate.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
// CertificateRequest schema
|
||||
const (
|
||||
CertCharging string = "CHARGING"
|
||||
CertICC string = "ICC"
|
||||
CertTBOX string = "TBOX"
|
||||
CertAftersales string = "AFTERSALES"
|
||||
)
|
||||
|
||||
type Certificate struct {
|
||||
PublicKey string `json:"public_key"`
|
||||
CommonName string `json:"-" validate:"required"`
|
||||
PrivateKey string `json:"private_key,omitempty" pg:"-"`
|
||||
EncryptedKey []byte `json:"-" pg:"encrypted_key"`
|
||||
SerialNumber string `json:"serial_number" pg:",pk"`
|
||||
Type string `json:"type"`
|
||||
Valid bool `json:"-" pg:",use_zero"`
|
||||
CreatedBy string `json:"-" pg:"created_by"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
func (cert Certificate) String() string {
|
||||
return fmt.Sprintf("Certificate for Common Name:<%s>", cert.CommonName)
|
||||
}
|
||||
|
||||
func (cert Certificate) IsExpiredOrInvalidAtTime(t time.Time, certDaysBeforeExp int) (bool, error) {
|
||||
if !cert.Valid {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if cert.PublicKey != "" {
|
||||
p, _ := pem.Decode([]byte(cert.PublicKey))
|
||||
if p != nil {
|
||||
c, err := x509.ParseCertificate(p.Bytes)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
day := c.NotAfter.AddDate(0, 0, 0-certDaysBeforeExp)
|
||||
if t.After(day) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
32
pkg/common/certificate_request.go
Normal file
32
pkg/common/certificate_request.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package common
|
||||
|
||||
import "fmt"
|
||||
|
||||
// CertificateRequest schema
|
||||
type CertificateRequest struct {
|
||||
CommonName string `json:"common_name" validate:"required,max=100"`
|
||||
CertificateType string `json:"type" validate:"max=100"`
|
||||
}
|
||||
|
||||
type CertificateRevokeRequest struct {
|
||||
Serial string `json:"serial_number" validate:"required,serial,max=1000"`
|
||||
CertificateType string `json:"type" validate:"max=100"`
|
||||
}
|
||||
|
||||
type CertificateRenewRequest struct {
|
||||
Type string `json:"type" validate:"required,max=10000"`
|
||||
CommonName string `json:"common_name" validate:"max=100"`
|
||||
}
|
||||
|
||||
type UpdateCert struct {
|
||||
SSLCertBase64 string `json:"ssl_cert_base64"`
|
||||
}
|
||||
|
||||
type CertificateInstallRequest struct {
|
||||
VIN string `pg:",pk" json:"vin" validate:"required,vin"`
|
||||
ICCID string `json:"iccid,omitempty" validate:"omitempty,max=50"`
|
||||
}
|
||||
|
||||
func (c CertificateRequest) String() string {
|
||||
return fmt.Sprintf("CertificateRequest for Common Name<%s>", c.CommonName)
|
||||
}
|
||||
63
pkg/common/certificate_test.go
Normal file
63
pkg/common/certificate_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCertIsExpiredOrInvalidAtTime(t *testing.T) {
|
||||
// this is a fake example cert generated at https://www.samltool.com/self_signed_certs.php
|
||||
var testcert = `-----BEGIN CERTIFICATE-----
|
||||
MIIDUzCCAjqgAwIBAgIBADANBgkqhkiG9w0BAQsFADBDMQswCQYDVQQGEwJ1czEL
|
||||
MAkGA1UECAwCQ0ExFDASBgNVBAoMC0Zpc2tlciBJbmMuMREwDwYDVQQDDAh0ZXN0
|
||||
Y2VydDAeFw0yMzAxMzExOTM5MjZaFw0yNDAxMzExOTM5MjZaMEMxCzAJBgNVBAYT
|
||||
AnVzMQswCQYDVQQIDAJDQTEUMBIGA1UECgwLRmlza2VyIEluYy4xETAPBgNVBAMM
|
||||
CHRlc3RjZXJ0MIIBIzANBgkqhkiG9w0BAQEFAAOCARAAMIIBCwKCAQIA13BpkJvp
|
||||
tqqGTwnMq+t+A50tzENZ3tmtKLIMeuprTux3oqT9PiUHRTLl0zp2r6X+T0A98P+/
|
||||
Ad2ybhKtd3qCBEIOkV+M84+q5ecOy2majNQJOgpHNSOtHiAqaZyUslCEtQrLX/Cj
|
||||
TLT8RvepzxWf7wB9iIj1hYiUFSXWYqWx07TrtcYEdoGiOd8syjRSHr2nMYjOr/K8
|
||||
4Ihyrze9g5j5Dosp943j2WjPETmGebu6bdi5SsoGbkm6dgtKbTKihuo5RBYKMS7t
|
||||
xis22jjq4nJigDz506aqY7zRn2Ph1B1CwqxP1O21c7nS78sUmewyKKJY2SX2yB9S
|
||||
XcfS4uYjFWC+9GcCAwEAAaNQME4wHQYDVR0OBBYEFMnlDS32ShOeQVUahFE3GUoX
|
||||
p/kEMB8GA1UdIwQYMBaAFMnlDS32ShOeQVUahFE3GUoXp/kEMAwGA1UdEwQFMAMB
|
||||
Af8wDQYJKoZIhvcNAQELBQADggECAJXUtgm9zuXsDGI1x2zzNY8gjIjsrhToWNAN
|
||||
tZKIR2eQETEWwzGLVuz/fmpbSdFN/jnlxLQUjaX2YqlU4gSqHcp4ypYLygs+UEbp
|
||||
tfdFDDfxw/1Oc8BRxAxygt6hnFsGM/uMingc6ON4qKg6UeFx9NTfq4jco+/5YDHL
|
||||
DNiAv8KUPxreR19bODue6+OKCU6JIkZbMa1/sKzTLkzHbUlHAsxe1JmoqquRvI5z
|
||||
a/6nNNka6vwyoSSH6PABU976DkPgDS4tSUvz0yUTwss7an6v5YM+i4T+VpA1nMTA
|
||||
LrSlbsmC+whMPAkl4DE9JtmrM3TQTO10bdWmcpMQuOuQpTmdyCfu
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
cert1 := common.Certificate{
|
||||
PublicKey: testcert,
|
||||
Valid: true,
|
||||
}
|
||||
|
||||
// unexpired cert
|
||||
result1, err := cert1.IsExpiredOrInvalidAtTime(time.Date(2023, 2, 14, 0, 0, 0, 0, time.Local), 30)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, result1)
|
||||
|
||||
// expired cert
|
||||
result2, err := cert1.IsExpiredOrInvalidAtTime(time.Date(2024, 2, 14, 0, 0, 0, 0, time.Local), 30)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, result2)
|
||||
|
||||
// less than 30 days before expiration
|
||||
result3, err := cert1.IsExpiredOrInvalidAtTime(time.Date(2024, 1, 14, 0, 0, 0, 0, time.Local), 30)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, result3)
|
||||
|
||||
cert2 := common.Certificate{
|
||||
PublicKey: testcert,
|
||||
Valid: false,
|
||||
}
|
||||
|
||||
// invalid cert
|
||||
result4, err := cert2.IsExpiredOrInvalidAtTime(time.Date(2023, 2, 14, 0, 0, 0, 0, time.Local), 30)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, result4)
|
||||
}
|
||||
62
pkg/common/charge_settings.go
Normal file
62
pkg/common/charge_settings.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
type OffPeakCharging struct {
|
||||
Start time.Time `json:"start" validate:"required"`
|
||||
End time.Time `json:"end" validate:"required"`
|
||||
}
|
||||
|
||||
type MobileChargeSetting struct {
|
||||
VIN string `json:"vin" validate:"required,vin"`
|
||||
ChargeSettings ChargeSettings `json:"charge_settings" validate:"required"`
|
||||
}
|
||||
|
||||
func (m *MobileChargeSetting) GetVIN() string {
|
||||
return m.VIN
|
||||
}
|
||||
|
||||
func (m *MobileChargeSetting) GetPayload() interface{} {
|
||||
return m.ChargeSettings
|
||||
}
|
||||
|
||||
func (m *MobileChargeSetting) SetVIN(vin string) {
|
||||
m.VIN = vin
|
||||
}
|
||||
|
||||
func (m *MobileChargeSetting) SetPayload(payload interface{}) error {
|
||||
var ok bool
|
||||
m.ChargeSettings, ok = payload.(ChargeSettings)
|
||||
if !ok {
|
||||
return ErrInvalidType
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type TRexChargeSettings = ChargeSettings
|
||||
|
||||
type ChargeSettings struct {
|
||||
ChargeLimit int `json:"charge_limit" validate:"required"`
|
||||
MaxCurrent int `json:"max_current" validate:"required"`
|
||||
MinCharge *int `json:"min_charge,omitempty"`
|
||||
OffPeakCharging *OffPeakCharging `json:"off_peak_charging,omitempty"`
|
||||
}
|
||||
|
||||
func (m *TRexChargeSettings) SetPayload(payload interface{}) error {
|
||||
var ok bool
|
||||
*m, ok = payload.(ChargeSettings)
|
||||
if !ok {
|
||||
return ErrInvalidType
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *TRexChargeSettings) GetMessage() interface{} {
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *TRexChargeSettings) GetPayload() interface{} {
|
||||
return *m
|
||||
}
|
||||
11
pkg/common/clickhouse_realtime.go
Normal file
11
pkg/common/clickhouse_realtime.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ClickHouseSignal struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Name string `json:"name"`
|
||||
Value *float64 `json:"value"`
|
||||
}
|
||||
16
pkg/common/configuration.go
Normal file
16
pkg/common/configuration.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
ID string `pg:",pk,unique" json:"id" validate:"required,max=256"`
|
||||
UniqueCode string `json:"unique_code" validate:"min=8,max=8"`
|
||||
UserId string `json:"user_id" validate:"required"`
|
||||
Trim string `json:"trim" validate:"min=2,max=2"`
|
||||
Color string `json:"color" validate:"min=2,max=2"`
|
||||
Interior string `json:"interior" validate:"min=2,max=2"`
|
||||
Wheels string `json:"wheels" validate:"min=2,max=2"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
73
pkg/common/consent.go
Normal file
73
pkg/common/consent.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
type UserConsentFromHMI struct {
|
||||
Name string `json:"name" validate:"required,user_consent_name"`
|
||||
Accept bool `json:"accept"`
|
||||
DriverID string `json:"driver_id" validate:"required"`
|
||||
}
|
||||
|
||||
type UserConsentDataRequest struct {
|
||||
UserID string `json:"userId"`
|
||||
ConsentAdminID string `json:"consentAdminId"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
VehicleIdNum string `json:"vehicleIdNum"`
|
||||
ConsentName string `json:"consentName"`
|
||||
ConsentAction string `json:"consentAction"`
|
||||
PreviousAction string `json:"previousAction,omitempty"`
|
||||
DataSubjectCategory string `json:"dataSubjectCategory"`
|
||||
Channel string `json:"channel"`
|
||||
GuestUser *UserConsentGuestUser `json:"guestUser,omitempty"`
|
||||
}
|
||||
|
||||
type UserConsentGuestUser struct {
|
||||
ConsentAdminID string `json:"consentAdminId"`
|
||||
ConsentName string `json:"consentName"`
|
||||
ConsentAction string `json:"consentAction"`
|
||||
PreviousAction string `json:"previousAction"`
|
||||
DataSubjectCategory string `json:"dataSubjectCategory"`
|
||||
}
|
||||
|
||||
type UserConsentDataResponse struct {
|
||||
UserID string `json:"userId"`
|
||||
ConsentAdminID string `json:"consentAdminId"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
VehicleIdNum string `json:"vehicleIdNum"`
|
||||
ConsentName string `json:"consentName"`
|
||||
ConsentAction string `json:"consentAction"`
|
||||
PreviousAction string `json:"previousAction"`
|
||||
DataSubjectCategory string `json:"dataSubjectCategory"`
|
||||
ConsentRegisteredAt time.Time `json:"consentRegisteredAt"`
|
||||
Channel string `json:"channel"`
|
||||
ValidFrom string `json:"validFrom"`
|
||||
ValidTo string `json:"validTo"`
|
||||
}
|
||||
|
||||
type UserConsentByVehicleNumberRequest struct {
|
||||
VehicleNumber string `json:"vehicleNumber"`
|
||||
}
|
||||
|
||||
type UserConsentDataTrexMsg struct {
|
||||
UserID string `json:"driver_id"`
|
||||
ConsentAdminID string `json:"consent_admin_id"`
|
||||
VehicleIdNum string `json:"vin"`
|
||||
ConsentName string `json:"name"`
|
||||
ConsentAction string `json:"action"`
|
||||
DataSubjectCategory string `json:"data_subject_category"`
|
||||
Channel string `json:"channel"`
|
||||
}
|
||||
|
||||
func BuildUserConsentTrexMessage(ucd UserConsentDataResponse) UserConsentDataTrexMsg {
|
||||
return UserConsentDataTrexMsg{
|
||||
VehicleIdNum: ucd.VehicleIdNum,
|
||||
ConsentName: ucd.ConsentName,
|
||||
ConsentAction: ucd.ConsentAction,
|
||||
Channel: ucd.Channel,
|
||||
DataSubjectCategory: ucd.DataSubjectCategory,
|
||||
ConsentAdminID: ucd.ConsentAdminID,
|
||||
UserID: ucd.UserID,
|
||||
}
|
||||
}
|
||||
1336
pkg/common/consumer_payload.go
Normal file
1336
pkg/common/consumer_payload.go
Normal file
File diff suppressed because it is too large
Load Diff
1337
pkg/common/consumer_payload_input.go
Normal file
1337
pkg/common/consumer_payload_input.go
Normal file
File diff suppressed because it is too large
Load Diff
968
pkg/common/consumer_payload_test.go
Normal file
968
pkg/common/consumer_payload_test.go
Normal file
@@ -0,0 +1,968 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/grpc/kafka_grpc"
|
||||
"fiskerinc.com/modules/utils/elptr"
|
||||
)
|
||||
|
||||
func TestDepotRouteHMIPayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *kafka_grpc.GRPC_DepotPayload
|
||||
expectedResult *ConsumerPayload
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid payload with 'init' handler",
|
||||
payload: &kafka_grpc.GRPC_DepotPayload{
|
||||
Handler: "init",
|
||||
Data: &kafka_grpc.GRPC_DepotPayload_HmiSession{
|
||||
HmiSession: &kafka_grpc.HMISessionData{
|
||||
SessionId: "123456",
|
||||
Vin: "ABC123",
|
||||
Salt: "salt123",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "init",
|
||||
Data: func() []byte {
|
||||
data, _ := json.Marshal(&HMISessionData{
|
||||
SessionID: "123456",
|
||||
VIN: "ABC123",
|
||||
Salt: "salt123",
|
||||
})
|
||||
return data
|
||||
}(),
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Add more test cases as needed
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := DepotRouteHMIPayload(test.payload)
|
||||
|
||||
if err != test.expectedError {
|
||||
t.Errorf("Expected error: %v, got: %v", test.expectedError, err)
|
||||
}
|
||||
|
||||
if result.Handler != test.expectedResult.Handler {
|
||||
t.Errorf("Handler mismatch. Expected: %s, got: %s", test.expectedResult.Handler, result.Handler)
|
||||
}
|
||||
|
||||
if string(result.Data) != string(test.expectedResult.Data) {
|
||||
t.Errorf("Data mismatch. Expected: %s, got: %s", test.expectedResult.Data, result.Data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDepotRouteTRexPayloadMessage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *kafka_grpc.GRPC_DepotPayload
|
||||
expectedResult *ConsumerPayload
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid payload with 'init' handler",
|
||||
payload: &kafka_grpc.GRPC_DepotPayload{
|
||||
Handler: "init",
|
||||
Data: &kafka_grpc.GRPC_DepotPayload_InitPayload{
|
||||
InitPayload: &kafka_grpc.InitPayload{
|
||||
Data: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "init",
|
||||
Data: func() []byte {
|
||||
data, _ := json.Marshal(map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
})
|
||||
return data
|
||||
}(),
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Payload with nil data",
|
||||
payload: &kafka_grpc.GRPC_DepotPayload{
|
||||
Handler: "init",
|
||||
Data: nil,
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "init",
|
||||
Data: nil,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
|
||||
{
|
||||
name: "Invalid handlerl handler",
|
||||
payload: &kafka_grpc.GRPC_DepotPayload{
|
||||
Handler: "fake_handler",
|
||||
Data: nil,
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "fake_handler",
|
||||
Data: nil,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := DepotRouteTRexPayload(test.payload)
|
||||
|
||||
if err != test.expectedError {
|
||||
t.Errorf("Expected error: %v, got: %v", test.expectedError, err)
|
||||
}
|
||||
|
||||
if result.Handler != test.expectedResult.Handler {
|
||||
t.Errorf("Handler mismatch. Expected: %s, got: %s", test.expectedResult.Handler, result.Handler)
|
||||
}
|
||||
|
||||
if string(result.Data) != string(test.expectedResult.Data) {
|
||||
t.Errorf("Data mismatch. Expected: %s, got: %s", test.expectedResult.Data, result.Data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttendantRouteMobilePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *kafka_grpc.GRPC_AttendantPayload
|
||||
expectedResult *ConsumerPayload
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid payload with 'update_approve' handler",
|
||||
payload: &kafka_grpc.GRPC_AttendantPayload{
|
||||
Handler: "update_approve",
|
||||
Data: &kafka_grpc.GRPC_AttendantPayload_UpdateApprove{
|
||||
UpdateApprove: &kafka_grpc.UpdateData{
|
||||
Id: 123,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "update_approve",
|
||||
Data: func() []byte {
|
||||
data, _ := json.Marshal(map[string]int{"id": 123})
|
||||
return data
|
||||
}(),
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Valid payload with 'updates_get' handler",
|
||||
payload: &kafka_grpc.GRPC_AttendantPayload{
|
||||
Handler: "updates_get",
|
||||
Data: &kafka_grpc.GRPC_AttendantPayload_UpdateGet{
|
||||
UpdateGet: &kafka_grpc.VehicleData{
|
||||
Vin: "ABC123",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "updates_get",
|
||||
Data: func() []byte {
|
||||
data, _ := json.Marshal(map[string]string{"vin": "ABC123"})
|
||||
return data
|
||||
}(),
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Payload with nil data",
|
||||
payload: &kafka_grpc.GRPC_AttendantPayload{
|
||||
Handler: "update_approve",
|
||||
Data: nil,
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "update_approve",
|
||||
Data: nil,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := AttendantRouteMobilePayload(test.payload)
|
||||
|
||||
if err != test.expectedError {
|
||||
t.Errorf("Expected error: %v, got: %v", test.expectedError, err)
|
||||
}
|
||||
|
||||
if result.Handler != test.expectedResult.Handler {
|
||||
t.Errorf("Handler mismatch. Expected: %s, got: %s", test.expectedResult.Handler, result.Handler)
|
||||
}
|
||||
|
||||
if string(result.Data) != string(test.expectedResult.Data) {
|
||||
t.Errorf("Data mismatch. Expected: %s, got: %s", test.expectedResult.Data, result.Data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateManifestToGRPC(t *testing.T) {
|
||||
pub1 := "9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5b"
|
||||
pub2 := "407f59557fb64ae98bc30b5370fab138f4827e14784d79bcf707dbe35ba2b85d"
|
||||
priv1 := "9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5c"
|
||||
var pub1B BinaryHex = []byte(pub1)
|
||||
var pub2B BinaryHex = []byte(pub2)
|
||||
var priv1B BinaryHex = []byte(priv1)
|
||||
date := time.Date(2022, 5, 27, 12, 49, 0, 0, time.UTC)
|
||||
unixMillis := date.UnixMilli()
|
||||
toDate := time.Unix(0, unixMillis*int64(time.Millisecond))
|
||||
flag := true
|
||||
expectedUpdate := &UpdateManifest{
|
||||
ID: 1,
|
||||
ECUs: []*UpdateManifestECU{
|
||||
{
|
||||
ID: 3,
|
||||
Mode: "v",
|
||||
ECCKeys: &ECCKeys{
|
||||
ECU: "ecu",
|
||||
Env: "env",
|
||||
PubKey1: &pub1B,
|
||||
PrivKey1: &priv1B,
|
||||
PubKey2: &pub2B,
|
||||
},
|
||||
Files: []*UpdateManifestFile{
|
||||
{
|
||||
FileID: "file_id_value",
|
||||
UpdateManifestECUID: 123456789,
|
||||
Filename: "filename_value",
|
||||
URL: "url_value",
|
||||
FileSize: 1024, // Example file size
|
||||
Checksum: "checksum_value",
|
||||
FileType: "type_value",
|
||||
FileOrder: 1, // Example order
|
||||
WriteRegion: MemoryRegion{
|
||||
Offset: 1024, // Example offset
|
||||
Length: 2048, // Example length
|
||||
},
|
||||
EraseRegion: &MemoryRegion{
|
||||
Offset: 4096, // Example offset
|
||||
Length: 8192, // Example length
|
||||
},
|
||||
FileKey: &FileKeyResponse{
|
||||
FileID: "file_key_file_id_value",
|
||||
Key: "file_key_key_value",
|
||||
Auth: "file_key_auth_value",
|
||||
Nonce: "file_key_nonce_value",
|
||||
Error: "file_key_error_value",
|
||||
},
|
||||
Parsed: &flag, // Example parsed file value
|
||||
Signature: "signature_value",
|
||||
CompatibleTrims: []CompatibleTrim{
|
||||
EXTREME,
|
||||
SPORT,
|
||||
},
|
||||
CompatibleDriveSides: []CompatibleDriveSide{
|
||||
LEFT_HAND_DRIVE,
|
||||
RIGHT_HAND_DRIVE,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedUpdate.CreatedAt = &toDate
|
||||
expectedUpdate.ECUs[0].CreatedAt = &toDate
|
||||
expectedUpdate.ECUs[0].UpdatedAt = &toDate
|
||||
expectedUpdate.ECUs[0].Files[0].CreatedAt = &date
|
||||
expectedUpdate.ECUs[0].Files[0].UpdatedAt = &date
|
||||
|
||||
b, _ := json.Marshal(expectedUpdate)
|
||||
msg := MessageRawJSON{
|
||||
Handler: "send_manifest",
|
||||
Data: b,
|
||||
}
|
||||
|
||||
grpcRep := UpdateManifestToGRPC(msg)
|
||||
|
||||
if grpcRep == nil {
|
||||
t.Errorf("Expected not nil reponse from UpdateManifestToGRPC but got: nil")
|
||||
}
|
||||
|
||||
if grpcRep.GetHandler() != msg.Handler {
|
||||
t.Errorf("Expected handler should equal to %v but got %v:", msg.Handler, grpcRep.Handler)
|
||||
}
|
||||
|
||||
reveseGRPC := GRPCToUpdateManifest(grpcRep)
|
||||
|
||||
grpcP, _ := json.Marshal(reveseGRPC)
|
||||
|
||||
if string(grpcP) != string(b) {
|
||||
t.Errorf("Expected data should equal to %v but got %v:", string(b), string(grpcP))
|
||||
}
|
||||
|
||||
}
|
||||
func TestAttendantSendManifest(t *testing.T) {
|
||||
pub1 := "9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5b"
|
||||
pub2 := "407f59557fb64ae98bc30b5370fab138f4827e14784d79bcf707dbe35ba2b85d"
|
||||
priv1 := "9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5c"
|
||||
var pub1B BinaryHex = []byte(pub1)
|
||||
var pub2B BinaryHex = []byte(pub2)
|
||||
var priv1B BinaryHex = []byte(priv1)
|
||||
date := time.Date(2022, 5, 27, 12, 49, 0, 0, time.UTC)
|
||||
dateString := "2022-05-27T12:49:00Z"
|
||||
unixMillis := date.UnixMilli()
|
||||
toDate := time.Unix(0, unixMillis*int64(time.Millisecond))
|
||||
flag := true
|
||||
expectedUpdate := &UpdateManifest{
|
||||
ID: 1,
|
||||
ECUs: []*UpdateManifestECU{
|
||||
{
|
||||
ID: 3,
|
||||
Mode: "v",
|
||||
ECCKeys: &ECCKeys{
|
||||
ECU: "ecu",
|
||||
Env: "env",
|
||||
PubKey1: &pub1B,
|
||||
PrivKey1: &priv1B,
|
||||
PubKey2: &pub2B,
|
||||
},
|
||||
Files: []*UpdateManifestFile{
|
||||
{
|
||||
FileID: "file_id_value",
|
||||
UpdateManifestECUID: 123456789,
|
||||
Filename: "filename_value",
|
||||
URL: "url_value",
|
||||
FileSize: 1024, // Example file size
|
||||
Checksum: "checksum_value",
|
||||
FileType: "type_value",
|
||||
FileOrder: 1, // Example order
|
||||
WriteRegion: MemoryRegion{
|
||||
Offset: 1024, // Example offset
|
||||
Length: 2048, // Example length
|
||||
},
|
||||
EraseRegion: &MemoryRegion{
|
||||
Offset: 4096, // Example offset
|
||||
Length: 8192, // Example length
|
||||
},
|
||||
FileKey: &FileKeyResponse{
|
||||
FileID: "file_key_file_id_value",
|
||||
Key: "file_key_key_value",
|
||||
Auth: "file_key_auth_value",
|
||||
Nonce: "file_key_nonce_value",
|
||||
Error: "file_key_error_value",
|
||||
},
|
||||
Parsed: &flag, // Example parsed file value
|
||||
Signature: "signature_value",
|
||||
CompatibleTrims: []CompatibleTrim{
|
||||
EXTREME,
|
||||
SPORT,
|
||||
},
|
||||
CompatibleDriveSides: []CompatibleDriveSide{
|
||||
LEFT_HAND_DRIVE,
|
||||
RIGHT_HAND_DRIVE,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedUpdate.CreatedAt = &toDate
|
||||
expectedUpdate.ECUs[0].CreatedAt = &toDate
|
||||
expectedUpdate.ECUs[0].UpdatedAt = &toDate
|
||||
expectedUpdate.ECUs[0].Files[0].CreatedAt = &date
|
||||
expectedUpdate.ECUs[0].Files[0].UpdatedAt = &date
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *kafka_grpc.GRPC_AttendantPayload
|
||||
expectedResult *ConsumerPayload
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid payload with 'send_manifest' handler",
|
||||
payload: &kafka_grpc.GRPC_AttendantPayload{
|
||||
Handler: "send_manifest",
|
||||
Data: &kafka_grpc.GRPC_AttendantPayload_UpdateManifest{
|
||||
UpdateManifest: &kafka_grpc.UpdateManifest{
|
||||
Id: 1,
|
||||
EcuUpdates: []*kafka_grpc.UpdateManifestECU{
|
||||
{
|
||||
Created: &unixMillis,
|
||||
Updated: &unixMillis,
|
||||
Id: 3,
|
||||
Mode: "v",
|
||||
EccKeys: &kafka_grpc.ECCKey{
|
||||
Ecu: "ecu",
|
||||
Env: "env",
|
||||
PubKeyLevel_1: &kafka_grpc.BineryHex{
|
||||
Data: []byte(pub1),
|
||||
},
|
||||
Level_1: &kafka_grpc.BineryHex{
|
||||
Data: []byte(priv1),
|
||||
},
|
||||
PubKeyLevel_2: &kafka_grpc.BineryHex{
|
||||
Data: []byte(pub2),
|
||||
},
|
||||
},
|
||||
Files: []*kafka_grpc.UpdateManifestFile{
|
||||
{
|
||||
FileId: "file_id_value",
|
||||
ManifestEcuId: 123456789,
|
||||
Filename: "filename_value",
|
||||
Url: "url_value",
|
||||
FileSize: 1024, // Example file size
|
||||
Checksum: "checksum_value",
|
||||
Type: "type_value",
|
||||
Order: 1, // Example order
|
||||
WriteRegion: &kafka_grpc.MemoryRegion{
|
||||
Offset: 1024, // Example offset
|
||||
Length: 2048, // Example length
|
||||
},
|
||||
EraseRegion: &kafka_grpc.MemoryRegion{
|
||||
Offset: 4096, // Example offset
|
||||
Length: 8192, // Example length
|
||||
},
|
||||
FileKey: &kafka_grpc.FileKeyResponse{
|
||||
FileId: "file_key_file_id_value",
|
||||
Key: "file_key_key_value",
|
||||
Auth: "file_key_auth_value",
|
||||
Nonce: "file_key_nonce_value",
|
||||
Error: "file_key_error_value",
|
||||
},
|
||||
Updated: &dateString,
|
||||
Created: &dateString,
|
||||
ParsedFile: &flag, // Example parsed file value
|
||||
Signature: "signature_value",
|
||||
CompatibleTrims: []string{
|
||||
string(EXTREME),
|
||||
string(SPORT),
|
||||
},
|
||||
CompatibleDriveSides: []string{
|
||||
string(LEFT_HAND_DRIVE),
|
||||
string(RIGHT_HAND_DRIVE),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Created: &unixMillis,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "send_manifest",
|
||||
Data: func() []byte {
|
||||
data, _ := json.Marshal(expectedUpdate)
|
||||
return data
|
||||
}(),
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Payload with nil data",
|
||||
payload: &kafka_grpc.GRPC_AttendantPayload{
|
||||
Handler: "send_manifest",
|
||||
Data: nil,
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "send_manifest",
|
||||
Data: nil,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Add more test cases as needed
|
||||
}
|
||||
|
||||
// Set date
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := AttendantRouteServicePayload(test.payload)
|
||||
|
||||
if err != test.expectedError {
|
||||
t.Errorf("Expected error: %v, got: %v", test.expectedError, err)
|
||||
}
|
||||
|
||||
if result.Handler != test.expectedResult.Handler {
|
||||
t.Errorf("Handler mismatch. Expected: %s, got: %s", test.expectedResult.Handler, result.Handler)
|
||||
}
|
||||
|
||||
if string(result.Data) != string(test.expectedResult.Data) {
|
||||
t.Errorf("Data mismatch. Expected: %s, <<><><<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n<<<<<got: %s", test.expectedResult.Data, result.Data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttendantOrderUpdated(t *testing.T) {
|
||||
date := time.Date(2022, 5, 27, 12, 49, 0, 0, time.UTC)
|
||||
unixMillis := date.UnixMilli()
|
||||
toDate := time.Unix(0, unixMillis*int64(time.Millisecond))
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *kafka_grpc.GRPC_AttendantPayload
|
||||
expectedResult *ConsumerPayload
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid payload with 'order_updated' handler",
|
||||
payload: &kafka_grpc.GRPC_AttendantPayload{
|
||||
Handler: "order_updated",
|
||||
Data: &kafka_grpc.GRPC_AttendantPayload_Order{
|
||||
Order: &kafka_grpc.Order{
|
||||
OrderNo: 123,
|
||||
MsgIdentifier: "msg123",
|
||||
SpecId: 456,
|
||||
VehicleSpecification: &kafka_grpc.VehicleSpecification{
|
||||
DestCon: "DestinationCountry",
|
||||
FOrderId: "FleetOrderIndicator",
|
||||
MfPlant: "ManufacturingPlant",
|
||||
ModelType: "ModelType",
|
||||
ModelYear: 2022,
|
||||
ModelId: 1,
|
||||
OrderId: "OrderIndicator",
|
||||
ProductId: "ProductionPhaseIndicator",
|
||||
Sn: "SequenceNumber",
|
||||
VehicleId: "VehicleIndicator",
|
||||
Model: "VehicleModel",
|
||||
VinPre: "VinPrefix",
|
||||
Version: 1,
|
||||
Date: unixMillis,
|
||||
Feature: []*kafka_grpc.FeatureCodes{
|
||||
{
|
||||
FamilyCode: "family1",
|
||||
FeatureCode: "feature1",
|
||||
},
|
||||
{
|
||||
FamilyCode: "family2",
|
||||
FeatureCode: "feature2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "order_updated",
|
||||
Data: func() []byte {
|
||||
order := &VehicleOrder{
|
||||
OrderNumber: 123,
|
||||
MessageIdentifier: "msg123",
|
||||
SpecID: 456,
|
||||
VehicleSpecification: VehicleSpecification{
|
||||
DestinationCountry: "DestinationCountry",
|
||||
FleetOrderIndicator: "FleetOrderIndicator",
|
||||
ManufacturingPlant: "ManufacturingPlant",
|
||||
ModelType: "ModelType",
|
||||
ModelYear: 2022,
|
||||
ModelYearIndicator: 1,
|
||||
OrderIndicator: "OrderIndicator",
|
||||
ProductionPhaseIndicator: "ProductionPhaseIndicator",
|
||||
SequenceNumber: "SequenceNumber",
|
||||
VehicleIndicator: "VehicleIndicator",
|
||||
VehicleModel: "VehicleModel",
|
||||
VinPrefix: "VinPrefix",
|
||||
VersionDuringModelYear: 1,
|
||||
ExpectedReferenceDate: ExpectedReferenceDate{toDate},
|
||||
VehicleFeatures: []FeatureCodes{
|
||||
{
|
||||
FamilyCode: "family1",
|
||||
FeatureCode: "feature1",
|
||||
},
|
||||
{
|
||||
FamilyCode: "family2",
|
||||
FeatureCode: "feature2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
data, _ := json.Marshal(order)
|
||||
return data
|
||||
}(),
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Payload with nil data",
|
||||
payload: &kafka_grpc.GRPC_AttendantPayload{
|
||||
Handler: "order_updated",
|
||||
Data: nil,
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "order_updated",
|
||||
Data: nil,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Add more test cases as needed
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := AttendantRouteServicePayload(test.payload)
|
||||
|
||||
if err != test.expectedError {
|
||||
t.Errorf("Expected error: %v, got: %v", test.expectedError, err)
|
||||
}
|
||||
|
||||
if result.Handler != test.expectedResult.Handler {
|
||||
t.Errorf("Handler mismatch. Expected: %s, got: %s", test.expectedResult.Handler, result.Handler)
|
||||
}
|
||||
|
||||
if string(result.Data) != string(test.expectedResult.Data) {
|
||||
t.Errorf("Data mismatch. Expected: %s, got: %s", test.expectedResult.Data, result.Data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValetRouteTRexCMDChangedPayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *kafka_grpc.GRPC_ValetPayload
|
||||
expectedResult *ConsumerPayload
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid payload with 'charging_command' handler",
|
||||
payload: &kafka_grpc.GRPC_ValetPayload{
|
||||
Handler: "charging_command",
|
||||
Data: &kafka_grpc.GRPC_ValetPayload_ChCMD{
|
||||
ChCMD: &kafka_grpc.ChargingCommand{
|
||||
Action: "start_charging",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "charging_command",
|
||||
Data: func() []byte {
|
||||
data, _ := json.Marshal(&kafka_grpc.ChargingCommand{
|
||||
Action: "start_charging",
|
||||
})
|
||||
return data
|
||||
}(),
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Payload with nil data",
|
||||
payload: &kafka_grpc.GRPC_ValetPayload{
|
||||
Handler: "charging_command",
|
||||
Data: nil,
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "charging_command",
|
||||
Data: nil,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := ValetRouteTRexPayload(test.payload)
|
||||
|
||||
if err != test.expectedError {
|
||||
t.Errorf("Expected error: %v, got: %v", test.expectedError, err)
|
||||
}
|
||||
|
||||
if result.Handler != test.expectedResult.Handler {
|
||||
t.Errorf("Handler mismatch. Expected: %s, got: %s", test.expectedResult.Handler, result.Handler)
|
||||
}
|
||||
|
||||
if string(result.Data) != string(test.expectedResult.Data) {
|
||||
t.Errorf("Data mismatch. Expected: %s, got: %s", test.expectedResult.Data, result.Data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValetRouteTRexDepartureSchedulePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload *kafka_grpc.GRPC_ValetPayload
|
||||
expectedResult *ConsumerPayload
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid payload with 'departure_schedule' handler",
|
||||
payload: &kafka_grpc.GRPC_ValetPayload{
|
||||
Handler: "departure_schedule",
|
||||
Data: &kafka_grpc.GRPC_ValetPayload_DepartureSchedule{
|
||||
DepartureSchedule: &kafka_grpc.DepartureSchedule{
|
||||
NextDayDeparture: nil,
|
||||
DepartureDays: []*kafka_grpc.DepartureDay{
|
||||
{
|
||||
DayOfWeek: "Monday",
|
||||
Time: "08:00",
|
||||
},
|
||||
{
|
||||
DayOfWeek: "Wednesday",
|
||||
Time: "10:00",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "departure_schedule",
|
||||
Data: func() []byte {
|
||||
data, _ := json.Marshal(&DepartureSchedule{
|
||||
NextDayDeparture: nil,
|
||||
DepartureDays: []DepartureDay{
|
||||
{
|
||||
DayOfWeek: "Monday",
|
||||
Time: "08:00",
|
||||
},
|
||||
{
|
||||
DayOfWeek: "Wednesday",
|
||||
Time: "10:00",
|
||||
},
|
||||
},
|
||||
})
|
||||
return data
|
||||
}(),
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Payload with nil data",
|
||||
payload: &kafka_grpc.GRPC_ValetPayload{
|
||||
Handler: "departure_schedule",
|
||||
Data: nil,
|
||||
},
|
||||
expectedResult: &ConsumerPayload{
|
||||
Handler: "departure_schedule",
|
||||
Data: nil,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := ValetRouteTRexPayload(test.payload)
|
||||
|
||||
if err != test.expectedError {
|
||||
t.Errorf("Expected error: %v, got: %v", test.expectedError, err)
|
||||
}
|
||||
|
||||
if result.Handler != test.expectedResult.Handler {
|
||||
t.Errorf("Handler mismatch. Expected: %s, got: %s", test.expectedResult.Handler, result.Handler)
|
||||
}
|
||||
|
||||
if string(result.Data) != string(test.expectedResult.Data) {
|
||||
t.Errorf("Data mismatch. Expected: %s, got: %s", test.expectedResult.Data, result.Data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Unit tests for CarStateToGRPC function
|
||||
func TestCarStateToGRPC(t *testing.T) {
|
||||
// Test case 1: Valid input
|
||||
ecu := CarECU{
|
||||
VIN: "123456",
|
||||
ECU: "ECU1",
|
||||
Version: "v1",
|
||||
Epoch_usec: time.Now().Unix(),
|
||||
}
|
||||
carState := CarStateUpdate{
|
||||
ECUs: map[string]CarECU{"ecu1": ecu},
|
||||
}
|
||||
data, err := json.Marshal(carState)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal test data: %v", err)
|
||||
}
|
||||
input1 := MessageRawJSON{
|
||||
Handler: "car_state_updated",
|
||||
Data: data,
|
||||
}
|
||||
expected := &kafka_grpc.GRPC_AttendantPayload{
|
||||
Handler: "car_state_updated",
|
||||
Data: &kafka_grpc.GRPC_AttendantPayload_CarUpdateStatus{
|
||||
CarUpdateStatus: &kafka_grpc.CarUpdateStatus{
|
||||
Ecus: map[string]*kafka_grpc.CarECU{
|
||||
"ecu1": {
|
||||
Vin: ecu.VIN,
|
||||
Ecu: ecu.ECU,
|
||||
SwVersion: ecu.Version,
|
||||
EpochUsec: ecu.Epoch_usec,
|
||||
// Add other fields here as needed
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
output := CarStateToGRPC(input1)
|
||||
expectedJSON, _ := json.Marshal(expected)
|
||||
actualJSON, _ := json.Marshal(output)
|
||||
if string(expectedJSON) != string(actualJSON) {
|
||||
t.Errorf("Test case 1 failed: expected handler %+v, got %+v", expected.Data, output.Data)
|
||||
}
|
||||
}
|
||||
|
||||
// func TestGRPCToECCKeys(t *testing.T) {
|
||||
|
||||
// var pub1, pub2, priv1, priv2 []byte
|
||||
// pub1 = []byte(`9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5b`)
|
||||
// pub2 = []byte(`9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5c`)
|
||||
// priv1 = []byte(`407f59557fb64ae98bc30b5370fab138f4827e14784d79bcf707dbe35ba2b85b`)
|
||||
// priv2 = []byte(`407f59557fb64ae98bc30b5370fab138f4827e14784d79bcf707dbe35ba2b85c`)
|
||||
|
||||
// var pub11, pub22, priv11, priv22 BinaryHex
|
||||
// pub11 = []byte(`9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5b`)
|
||||
// pub22 = []byte(`9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5c`)
|
||||
// priv11 = []byte(`407f59557fb64ae98bc30b5370fab138f4827e14784d79bcf707dbe35ba2b85b`)
|
||||
// priv22 = []byte(`407f59557fb64ae98bc30b5370fab138f4827e14784d79bcf707dbe35ba2b85c`)
|
||||
|
||||
// testCases := []struct {
|
||||
// name string
|
||||
// payload *kafka_grpc.GRPC_AttendantPayload
|
||||
// expected []ECCKeys
|
||||
// }{
|
||||
// {
|
||||
// name: "NilPayload",
|
||||
// payload: &kafka_grpc.GRPC_AttendantPayload{
|
||||
// Handler: "test",
|
||||
// },
|
||||
// expected: nil,
|
||||
// },
|
||||
// {
|
||||
// name: "WithPayload",
|
||||
// payload: &kafka_grpc.GRPC_AttendantPayload{
|
||||
// Handler: "test",
|
||||
// Data: &kafka_grpc.GRPC_AttendantPayload_Keys{
|
||||
// Keys: &kafka_grpc.ECCKeys{
|
||||
// EccKeys: []*kafka_grpc.ECCKey{
|
||||
// {
|
||||
// Ecu: "ecu",
|
||||
// Env: "env",
|
||||
// PubKeyLevel_1: &pub1,
|
||||
// PubKeyLevel_2: &pub2,
|
||||
// Level_1: &priv1,
|
||||
// Level_2: &priv2,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// expected: []ECCKeys{
|
||||
// {
|
||||
// ECU: "ecu",
|
||||
// Env: "env",
|
||||
// PubKey1: &pub11,
|
||||
// PubKey2: &pub22,
|
||||
// PrivKey1: &priv11,
|
||||
// PrivKey2: &priv22,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tc := range testCases {
|
||||
// t.Run(tc.name, func(t *testing.T) {
|
||||
// result := GRPCToECCKeys(tc.payload)
|
||||
|
||||
// // Compare the result with expected value
|
||||
// if !equal(result, tc.expected) {
|
||||
// t.Errorf("Test case %s failed. Expected: %v, got: %v", tc.name, tc.expected, result)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// Helper function to check equality of slices of ECCKeys
|
||||
// func equal(a, b []ECCKeys) bool {
|
||||
// if len(a) != len(b) {
|
||||
// return false
|
||||
// }
|
||||
// for i := range a {
|
||||
// if a[i] != b[i] {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
|
||||
func TestRemoteCMD(t *testing.T) {
|
||||
|
||||
input := []struct {
|
||||
name string
|
||||
cmd *RemoteCommandRequest
|
||||
}{
|
||||
{
|
||||
name: "With valid data",
|
||||
cmd: &RemoteCommandRequest{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
RemoteCommandSource: RemoteCommandSource{
|
||||
Command: "temp_cabin",
|
||||
Data: elptr.ElPtr("20"),
|
||||
Start: elptr.ElPtr(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
End: elptr.ElPtr(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With end date null",
|
||||
cmd: &RemoteCommandRequest{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
RemoteCommandSource: RemoteCommandSource{
|
||||
Command: "temp_cabin",
|
||||
Data: elptr.ElPtr("20"),
|
||||
Start: elptr.ElPtr(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
End: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With all values with zero",
|
||||
cmd: &RemoteCommandRequest{},
|
||||
},
|
||||
{
|
||||
name: "With null data",
|
||||
cmd: nil,
|
||||
},
|
||||
{
|
||||
name: "With zero cmd values",
|
||||
cmd: &RemoteCommandRequest{
|
||||
RemoteCommandSource: RemoteCommandSource{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, in := range input {
|
||||
t.Run(in.name, func(t *testing.T) {
|
||||
b := toBinaryArray(in.cmd)
|
||||
|
||||
rawMsg := MessageRawJSON{
|
||||
Handler: "remote_command",
|
||||
Data: b,
|
||||
}
|
||||
protoCmd := RemoteCMDToGRPC(rawMsg)
|
||||
|
||||
resCMD := GRPCToRemotCMD(protoCmd)
|
||||
protoCmdInBinary := toBinaryArray(resCMD)
|
||||
|
||||
if string(b) != string(protoCmdInBinary) {
|
||||
t.Errorf("Expected %v, got %v", string(b), string(protoCmdInBinary))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
5
pkg/common/context/value_keys.go
Normal file
5
pkg/common/context/value_keys.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package context
|
||||
|
||||
type ContextType string
|
||||
|
||||
const ProviderKey ContextType = "auth_provider"
|
||||
22
pkg/common/dbbasemodel/dbbase.go
Normal file
22
pkg/common/dbbasemodel/dbbase.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package dbbasemodel
|
||||
|
||||
import "time"
|
||||
|
||||
type DBModelBase struct {
|
||||
CreatedAt *time.Time `pg:"default:now()" json:"created,omitempty" swaggerignore:"true"`
|
||||
UpdatedAt *time.Time `pg:"default:now()" json:"updated,omitempty" swaggerignore:"true"`
|
||||
}
|
||||
|
||||
func (d *DBModelBase) ClearCreatedAt() {
|
||||
d.CreatedAt = nil
|
||||
}
|
||||
|
||||
func (d *DBModelBase) ClearDates() {
|
||||
d.CreatedAt = nil
|
||||
d.UpdatedAt = nil
|
||||
}
|
||||
|
||||
// Keeping the Scrub name used in other locations so people are more likely to find this function
|
||||
func (d *DBModelBase) Scrub(){
|
||||
d.ClearDates()
|
||||
}
|
||||
140
pkg/common/dbc_desc.go
Normal file
140
pkg/common/dbc_desc.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
can "github.com/Fisker-Inc/project-ai-can-go/pkg/descriptor"
|
||||
)
|
||||
|
||||
// SignalDesc is copied from project-ai-can-go and modified for local needs.
|
||||
type SignalDesc struct {
|
||||
DBCHash string `json:"dbc_hash" ch:"dbc_hash"`
|
||||
MessageID uint16 `json:"message_id" ch:"message_id"`
|
||||
// Description of the signal.
|
||||
Name string `json:"name" ch:"signal_name"`
|
||||
// Start bit.
|
||||
Start uint16 `json:"start" ch:"start"`
|
||||
// Length in bits.
|
||||
Length uint16 `json:"length" ch:"length"`
|
||||
// IsBigEndian is true if the signal is big-endian.
|
||||
IsBigEndian bool `json:"big_endian" ch:"big_endian"`
|
||||
// IsSigned is true if the signal uses raw signed values.
|
||||
IsSigned bool `json:"signed" ch:"signed"`
|
||||
// IsMultiplexer is true if the signal is the multiplexor of a multiplexed message.
|
||||
IsMultiplexer bool `json:"multiplexer" ch:"multiplexer"`
|
||||
// IsMultiplexed is true if the signal is multiplexed.
|
||||
IsMultiplexed bool `json:"multiplexed" ch:"multiplexed"`
|
||||
// MultiplexerValue is the value of the multiplexer when this signal is present.
|
||||
MultiplexerValue uint8 `json:"multiplexer_value" ch:"multiplexer_value"`
|
||||
// Offset for real-world transform.
|
||||
Offset float64 `json:"offset" ch:"offset"`
|
||||
// Scale for real-world transform.
|
||||
Scale float64 `json:"scale" ch:"scale"`
|
||||
// Min real-world value.
|
||||
Min float64 `json:"min" ch:"min"`
|
||||
// Max real-world value.
|
||||
Max float64 `json:"max" ch:"max"`
|
||||
// Unit of the signal.
|
||||
Unit string `json:"unit" ch:"unit"`
|
||||
// Description of the signal.
|
||||
Description string `json:"description" ch:"description"`
|
||||
// ValueDescriptions of the signal.
|
||||
ValueDescriptions []string `json:"value_descriptions" ch:"value_descriptions"`
|
||||
// ReceiverNodes is the list of names of the nodes receiving the signal.
|
||||
ReceiverNodes []string `json:"receiver_nodes" ch:"receiver_nodes"`
|
||||
// DefaultValue of the signal.
|
||||
DefaultValue int64 `json:"default_value" ch:"default_value"`
|
||||
// ECUName is a name of an ECU.
|
||||
ECUName string
|
||||
}
|
||||
|
||||
func (s *SignalDesc) CopyFromCAN(in *can.Signal) {
|
||||
s.Name = in.Name
|
||||
s.Start = in.Start
|
||||
s.Length = in.Length
|
||||
s.IsBigEndian = in.IsBigEndian
|
||||
s.IsSigned = in.IsSigned
|
||||
s.IsMultiplexer = in.IsMultiplexer
|
||||
s.IsMultiplexed = in.IsMultiplexed
|
||||
s.MultiplexerValue = uint8(in.MultiplexerValue)
|
||||
s.Offset = in.Offset
|
||||
s.Scale = in.Scale
|
||||
s.Min = in.Min
|
||||
s.Max = in.Max
|
||||
s.Unit = in.Unit
|
||||
s.Description = in.Description
|
||||
s.ValueDescriptions = getDescriptions(in.ValueDescriptions)
|
||||
s.ReceiverNodes = in.ReceiverNodes
|
||||
s.DefaultValue = int64(in.DefaultValue)
|
||||
}
|
||||
|
||||
func getDescriptions(ds []*can.ValueDescription) []string {
|
||||
res := make([]string, len(ds))
|
||||
for k, d := range ds {
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
res[k] = fmt.Sprintf("%d: %s", d.Value, d.Description)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
type SignalDescWithECU struct {
|
||||
SignalDesc
|
||||
ECUName string `json:"ecu_name" ch:"ecu_name"`
|
||||
}
|
||||
|
||||
type MessageDesc struct {
|
||||
DBCHash string
|
||||
// Name of the message.
|
||||
Name string
|
||||
// ECUName is a name of an ECU.
|
||||
ECUName string
|
||||
// ID of the message.
|
||||
ID uint32
|
||||
// IsExtended is true if the message is an extended CAN message.
|
||||
IsExtended bool
|
||||
// SendType is the message's send type.
|
||||
SendType string
|
||||
// Length in bytes.
|
||||
Length uint16
|
||||
// Description of the message.
|
||||
Description string
|
||||
// SenderNode is the name of the node sending the message.
|
||||
SenderNode string
|
||||
// CycleTime is the cycle time (ns) of a cyclic message.
|
||||
CycleTime int64
|
||||
// DelayTime is the allowed delay (ns) between cyclic message sends.
|
||||
DelayTime int64
|
||||
}
|
||||
|
||||
func (m *MessageDesc) CopyFromCAN(in *can.Message) {
|
||||
m.Name = in.Name
|
||||
m.ID = in.ID
|
||||
m.IsExtended = in.IsExtended
|
||||
m.SendType = in.SendType.String()
|
||||
m.Length = in.Length
|
||||
m.Description = in.Description
|
||||
m.SenderNode = in.SenderNode
|
||||
m.CycleTime = int64(in.CycleTime)
|
||||
m.DelayTime = int64(in.DelayTime)
|
||||
m.ECUName = extractECUName(in.Name)
|
||||
}
|
||||
|
||||
func extractECUName(msgName string) string {
|
||||
names := strings.Split(msgName, "_")
|
||||
ecuName := names[0]
|
||||
if names[0] == "Diag" && len(names) > 1 {
|
||||
ecuName = names[1]
|
||||
}
|
||||
|
||||
return ecuName
|
||||
}
|
||||
|
||||
type DBCDesc struct {
|
||||
Hash string
|
||||
Name string
|
||||
}
|
||||
134
pkg/common/dbc_desc_test.go
Normal file
134
pkg/common/dbc_desc_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"github.com/Fisker-Inc/project-ai-can-go/pkg/descriptor"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCopySignal(t *testing.T) {
|
||||
expOut := common.SignalDesc{
|
||||
Name: "TBOX_RemPassSeatHeatgCmd",
|
||||
Start: 23,
|
||||
Length: 3,
|
||||
IsBigEndian: true,
|
||||
IsSigned: false,
|
||||
IsMultiplexer: false,
|
||||
IsMultiplexed: false,
|
||||
MultiplexerValue: 0,
|
||||
Offset: 0,
|
||||
Scale: 1,
|
||||
Min: 0,
|
||||
Max: 7,
|
||||
Unit: "",
|
||||
Description: "Remote passenger seat heating command",
|
||||
ValueDescriptions: []string{
|
||||
"0: without_remote_control",
|
||||
"1: Remote_open_passenger_seat_heating_first_gear",
|
||||
"2: Remote_passenger_seat_heating_second_gear",
|
||||
"3: Remote_passenger_seat_heating_third_gear",
|
||||
"4: Remote_closing_passenger_seat_heating",
|
||||
"5: Reserved",
|
||||
"6: Reserved",
|
||||
"7: Reserved",
|
||||
},
|
||||
ReceiverNodes: []string{
|
||||
"GW",
|
||||
},
|
||||
DefaultValue: 0,
|
||||
}
|
||||
|
||||
in := descriptor.Signal{
|
||||
Name: "TBOX_RemPassSeatHeatgCmd",
|
||||
Start: 23,
|
||||
Length: 3,
|
||||
IsBigEndian: true,
|
||||
IsSigned: false,
|
||||
IsMultiplexer: false,
|
||||
IsMultiplexed: false,
|
||||
MultiplexerValue: 0,
|
||||
Offset: 0,
|
||||
Scale: 1,
|
||||
Min: 0,
|
||||
Max: 7,
|
||||
Unit: "",
|
||||
Description: "Remote passenger seat heating command",
|
||||
ValueDescriptions: []*descriptor.ValueDescription{
|
||||
{
|
||||
Value: 0,
|
||||
Description: "without_remote_control",
|
||||
},
|
||||
{
|
||||
Value: 1,
|
||||
Description: "Remote_open_passenger_seat_heating_first_gear",
|
||||
},
|
||||
{
|
||||
Value: 2,
|
||||
Description: "Remote_passenger_seat_heating_second_gear",
|
||||
},
|
||||
{
|
||||
Value: 3,
|
||||
Description: "Remote_passenger_seat_heating_third_gear",
|
||||
},
|
||||
{
|
||||
Value: 4,
|
||||
Description: "Remote_closing_passenger_seat_heating",
|
||||
},
|
||||
{
|
||||
Value: 5,
|
||||
Description: "Reserved",
|
||||
},
|
||||
{
|
||||
Value: 6,
|
||||
Description: "Reserved",
|
||||
},
|
||||
{
|
||||
Value: 7,
|
||||
Description: "Reserved",
|
||||
},
|
||||
},
|
||||
ReceiverNodes: []string{
|
||||
"GW",
|
||||
},
|
||||
DefaultValue: 0,
|
||||
}
|
||||
|
||||
s := common.SignalDesc{}
|
||||
s.CopyFromCAN(&in)
|
||||
|
||||
assert.Equal(t, expOut, s)
|
||||
}
|
||||
|
||||
func TestCopyMessage(t *testing.T) {
|
||||
in := &descriptor.Message{
|
||||
Name: "Diag_EPS2_Resp",
|
||||
ID: 2045,
|
||||
IsExtended: false,
|
||||
SendType: 0,
|
||||
Length: 8,
|
||||
Description: "",
|
||||
SenderNode: "GW",
|
||||
CycleTime: 1000000,
|
||||
DelayTime: 2000000,
|
||||
}
|
||||
|
||||
expOut := common.MessageDesc{
|
||||
Name: "Diag_EPS2_Resp",
|
||||
ID: 2045,
|
||||
IsExtended: false,
|
||||
SendType: "None",
|
||||
Length: 8,
|
||||
Description: "",
|
||||
SenderNode: "GW",
|
||||
ECUName: "EPS2",
|
||||
CycleTime: 1000000,
|
||||
DelayTime: 2000000,
|
||||
}
|
||||
|
||||
s := common.MessageDesc{}
|
||||
s.CopyFromCAN(in)
|
||||
|
||||
assert.Equal(t, expOut, s)
|
||||
}
|
||||
62
pkg/common/departure_schedule.go
Normal file
62
pkg/common/departure_schedule.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package common
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrInvalidType = errors.New("invalid type")
|
||||
|
||||
type MobileDepartureSchedule struct {
|
||||
VIN string `json:"vin" validate:"required,vin"`
|
||||
DepartureSchedule DepartureSchedule `json:"departure_schedule" validate:"required"`
|
||||
}
|
||||
|
||||
func (m *MobileDepartureSchedule) SetVIN(vin string) {
|
||||
m.VIN = vin
|
||||
}
|
||||
|
||||
func (m *MobileDepartureSchedule) SetPayload(payload interface{}) error {
|
||||
var ok bool
|
||||
m.DepartureSchedule, ok = payload.(DepartureSchedule)
|
||||
if !ok {
|
||||
return ErrInvalidType
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MobileDepartureSchedule) GetVIN() string {
|
||||
return m.VIN
|
||||
}
|
||||
|
||||
func (m *MobileDepartureSchedule) GetPayload() interface{} {
|
||||
return m.DepartureSchedule
|
||||
}
|
||||
|
||||
type TRexDepartureSchedule = DepartureSchedule
|
||||
|
||||
type DepartureSchedule struct {
|
||||
NextDayDeparture *string `json:"next_day_departure,omitempty" validate:"required_without=DepartureDays"`
|
||||
DepartureDays []DepartureDay `json:"departure_days,omitempty"`
|
||||
}
|
||||
|
||||
type DepartureDay struct {
|
||||
DayOfWeek string `json:"day_of_week" validate:"oneof=Mon Tue Wed Thu Fri Sat Sun"`
|
||||
Time string `json:"time" validate:"datetime=15:04"`
|
||||
}
|
||||
|
||||
func (m *TRexDepartureSchedule) SetPayload(payload interface{}) error {
|
||||
var ok bool
|
||||
*m, ok = payload.(DepartureSchedule)
|
||||
if !ok {
|
||||
return ErrInvalidType
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *TRexDepartureSchedule) GetMessage() interface{} {
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *TRexDepartureSchedule) GetPayload() interface{} {
|
||||
return *m
|
||||
}
|
||||
53
pkg/common/device.go
Normal file
53
pkg/common/device.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Device int
|
||||
|
||||
const (
|
||||
Unknown Device = iota
|
||||
TRex
|
||||
HMI
|
||||
Mobile
|
||||
Service
|
||||
)
|
||||
|
||||
// Key returns formatting for websockets, kafka, redis given an ID
|
||||
// ex: TRex.Key("FISKER123") returns "1:FISKER123"
|
||||
func (d Device) Key(id string) string {
|
||||
return fmt.Sprintf("%d:%s", d, id)
|
||||
}
|
||||
|
||||
func (d Device) String() string {
|
||||
return [...]string{"unknown", "trex", "hmi", "mobile", "service"}[d]
|
||||
}
|
||||
|
||||
func (d Device) EnumIndex() int {
|
||||
return int(d)
|
||||
}
|
||||
|
||||
// ParseDeviceKey is a generic parser for keys from websockets, kafka, redis
|
||||
// If unable to parse string correctly, will return device type Unknown
|
||||
// ex: "1:FISKER123" returns (TRex, "FISKER123")
|
||||
// ex: "-1:FISKER123" returns (Unknown, "FISKER123")
|
||||
// ex: "FISKER123" returns (Unknown, "FISKER123")
|
||||
func ParseDeviceKey(key string) (Device, string) {
|
||||
keys := strings.Split(key, ":")
|
||||
|
||||
if len(keys) == 1 {
|
||||
return Unknown, keys[0]
|
||||
} else if len(keys) != 2 {
|
||||
return Unknown, ""
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(keys[0])
|
||||
if err != nil {
|
||||
return Unknown, keys[1]
|
||||
}
|
||||
|
||||
return Device(i), keys[1]
|
||||
}
|
||||
58
pkg/common/digital_twin.go
Normal file
58
pkg/common/digital_twin.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
type DigitalTwinRequest struct {
|
||||
VIN string `json:"vin" validate:"vin"`
|
||||
}
|
||||
|
||||
type JSONDigitalTwin struct {
|
||||
VIN string `json:"vin"`
|
||||
Online bool `json:"online"`
|
||||
OnlineHMI bool `json:"online_hmi"`
|
||||
VehicleSpeed *VehicleSpeed `json:"vehicle_speed,omitempty"`
|
||||
Gear *Gear `json:"gear,omitempty"`
|
||||
Battery *JSONBattery `json:"battery,omitempty"`
|
||||
Doors *Doors `json:"doors,omitempty"`
|
||||
Location *Location `json:"location,omitempty"`
|
||||
Locks *Locks `json:"door_locks,omitempty"`
|
||||
Windows *JSONWindows `json:"windows,omitempty"`
|
||||
ClimateControl *JSONClimateControl `json:"climate_control,omitempty"`
|
||||
TRexVersion string `json:"trex_version,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
VehicleReadyState *VehicleReadyState `json:"vehicle_ready_state,omitempty"`
|
||||
ExpandedSignals *ExpandedSignals `json:"expanded_signals,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated,omitempty"`
|
||||
}
|
||||
|
||||
type JSONBattery struct {
|
||||
Percent *int `json:"percent,omitempty"`
|
||||
MaxMiles *int `json:"max_miles,omitempty"`
|
||||
AvgCellTemperature *int `json:"avg_cell_temp,omitempty"`
|
||||
ChargeType *string `json:"charge_type,omitempty"`
|
||||
RemainingChargingTime *int `json:"remaining_charging_time,omitempty"`
|
||||
RemainingChargingTimeFull *int `json:"remaining_charging_time_full,omitempty"`
|
||||
StateOfCharge *int `json:"state_of_charge,omitempty"`
|
||||
TotalMileageOdometer *int `json:"total_mileage_odometer,omitempty"`
|
||||
}
|
||||
|
||||
type JSONWindows struct {
|
||||
LeftFront int `json:"left_front"`
|
||||
LeftRear int `json:"left_rear"`
|
||||
LeftRearQuarter int `json:"left_rear_quarter"`
|
||||
RightFront int `json:"right_front"`
|
||||
RightRear int `json:"right_rear"`
|
||||
RightRearQuarter int `json:"right_rear_quarter"`
|
||||
RearWindshield int `json:"rear_windshield"`
|
||||
Sunroof int `json:"sunroof"`
|
||||
}
|
||||
|
||||
type JSONClimateControl struct {
|
||||
CabinTemperature int `json:"cabin_temperature"`
|
||||
RearDefrost bool `json:"rear_defrost"`
|
||||
DriverSeatHeat int `json:"driver_seat_heat"`
|
||||
PassengerSeatHeat int `json:"passenger_seat_heat"`
|
||||
SteeringWheelHeat bool `json:"steering_wheel_heat"`
|
||||
AmbientTemperature int `json:"ambient_temperature"`
|
||||
InternalTemperature int `json:"internal_temperature"`
|
||||
}
|
||||
31
pkg/common/driver.go
Normal file
31
pkg/common/driver.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
// DriverAccount model
|
||||
type Driver struct {
|
||||
ID string `pg:",pk,unique" json:"id" validate:"required,max=256"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
func (da Driver) String() string {
|
||||
return fmt.Sprintf("Driver<%s>", da.ID)
|
||||
}
|
||||
|
||||
type DriverExternal struct {
|
||||
ExternalID string `pg:"external_id"`
|
||||
Source OVSource `pg:"source"`
|
||||
}
|
||||
|
||||
type OVSource string
|
||||
|
||||
var (
|
||||
OV_DEFAULT OVSource = "OVLoop"
|
||||
OV_DEV OVSource = "OVLoop-dev"
|
||||
OV_QA OVSource = "OVLoop-qa"
|
||||
OV_PROD OVSource = "OVLoop-prod"
|
||||
)
|
||||
9
pkg/common/driver_emails.go
Normal file
9
pkg/common/driver_emails.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package common
|
||||
|
||||
type DriverEmail struct {
|
||||
Vin string `json:"vin"`
|
||||
DriverId string `json:"driver_id"`
|
||||
Email string `json:"email"`
|
||||
GivenName string `json:"given_name"`
|
||||
FamilyName string `json:"family_name"`
|
||||
}
|
||||
7
pkg/common/dtc_alert.go
Normal file
7
pkg/common/dtc_alert.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
type DTCAlert struct {
|
||||
Timestamp int `json:"epoch_usec"`
|
||||
ECU string `json:"ecu"`
|
||||
DTC string `json:"dtc"`
|
||||
}
|
||||
50
pkg/common/ecc_keys.go
Normal file
50
pkg/common/ecc_keys.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
type ECCKeys struct {
|
||||
ECU string `json:"ecu,omitempty" pg:",pk"`
|
||||
Env string `json:"env,omitempty" pg:",pk"`
|
||||
PubKey1 *BinaryHex `json:"pub_key_level_1,omitempty" pg:"pub_key_level_1,type:bytea" swaggertype:"string" format:"hex" example:"9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5b"`
|
||||
PrivKey1 *BinaryHex `json:"level_1,omitempty" pg:"priv_key_level_1,type:bytea" swaggertype:"string" format:"hex" example:"407f59557fb64ae98bc30b5370fab138f4827e14784d79bcf707dbe35ba2b85d"`
|
||||
PubKey2 *BinaryHex `json:"pub_key_level_2,omitempty" pg:"pub_key_level_2,type:bytea" swaggertype:"string" format:"hex" example:"9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5b"`
|
||||
PrivKey2 *BinaryHex `json:"level_2,omitempty" pg:"priv_key_level_2,type:bytea" swaggertype:"string" format:"hex" example:"407f59557fb64ae98bc30b5370fab138f4827e14784d79bcf707dbe35ba2b85d"`
|
||||
PubKey3 *BinaryHex `json:"pub_key_level_3,omitempty" pg:"pub_key_level_3,type:bytea" swaggertype:"string" format:"hex" example:"9a1a6949d7f8a511df6e2e2771e444dbd6de97e7d98bdecbb5adc4b8965ce3bef353f523dbea123d7882dc043d415cda02810bad1b6f1b8c6202234a424b7d5b"`
|
||||
PrivKey3 *BinaryHex `json:"level_3,omitempty" pg:"priv_key_level_3,type:bytea" swaggertype:"string" format:"hex" example:"407f59557fb64ae98bc30b5370fab138f4827e14784d79bcf707dbe35ba2b85d"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
func (ek *ECCKeys) Init(pub1 *BinaryHex, pub2 *BinaryHex, pub3 *BinaryHex, priv1 *BinaryHex, priv2 *BinaryHex, priv3 *BinaryHex) {
|
||||
ek.PubKey1 = pub1
|
||||
ek.PubKey2 = pub2
|
||||
ek.PubKey3 = pub3
|
||||
ek.PrivKey1 = priv1
|
||||
ek.PrivKey2 = priv2
|
||||
ek.PrivKey3 = priv3
|
||||
}
|
||||
|
||||
// Use this to remove unneeded values when adding to update manifest's ecc_keys
|
||||
func (ek *ECCKeys) ScrubForManifest() {
|
||||
ek.ScrubForAftersales()
|
||||
ek.PubKey1 = nil
|
||||
ek.PubKey2 = nil
|
||||
ek.PubKey3 = nil
|
||||
}
|
||||
|
||||
func (ek *ECCKeys) ScrubForAftersales() {
|
||||
ek.ECU = ""
|
||||
ek.Env = ""
|
||||
ek.CreatedAt = nil
|
||||
ek.UpdatedAt = nil
|
||||
}
|
||||
|
||||
// This transforming nonsense is getting pretty crazy here
|
||||
// Unsure if this will actually work
|
||||
func (ek *ECCKeys) TransformECUName() {
|
||||
replacement, ok := ECUReplacement()[ek.ECU]
|
||||
if ok {
|
||||
ek.ECU = replacement
|
||||
}
|
||||
}
|
||||
111
pkg/common/ecu_dtc.go
Normal file
111
pkg/common/ecu_dtc.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
type AddDTCRequest struct {
|
||||
Data map[string]DTCReq `validate:"required,gt=0,dive,keys,max=1000,endkeys,dive"`
|
||||
}
|
||||
type DTCReq struct {
|
||||
EpochUsec int64 `json:"epoch_usec"`
|
||||
DTCs [][]byte `json:"dtcs" validate:"required,gt=0,dive,max=10000,required,gt=0,dive,max=1024"`
|
||||
}
|
||||
|
||||
type DTC_ECU struct {
|
||||
ID int `json:"id,omitempty" pg:",pk"`
|
||||
VIN string `json:"vin,omitempty" bson:"vin" validate:"vin"`
|
||||
ECU string `json:"ecu_name" bson:"ecu" validate:"required,ecu"`
|
||||
TroubleCode int64 `json:"trouble_code" bson:"dtc"`
|
||||
StatusByte int64 `json:"status_byte" bson:"status"`
|
||||
Epoch_usec time.Time `json:"epoch_usec" bson:"timestamp"`
|
||||
Created_at time.Time `json:"created_at" bson:"created_at"`
|
||||
Speed uint16 `json:"speed" bson:"speed"`
|
||||
Mileage uint32 `json:"mileage" bson:"mileage"`
|
||||
Voltage uint16 `json:"voltage" bson:"voltage"`
|
||||
Snapshot string `json:"snapshot,omitempty" bson:"snapshot,omitempty"`
|
||||
Information *DTCInformation `json:"trouble_code_information,omitempty" pg:"-"`
|
||||
StatusByteDecode []DTCStatusByteMeaning `json:"status_byte_meaning,omitempty" pg:"-"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
type DTC_ECUQuery struct {
|
||||
VIN string `pg:"vin" validate:"vin"`
|
||||
ECU string `pg:"ecu,omit_empty" validate:"required,ecu"`
|
||||
TroubleCode int64 `pg:"trouble_code,omit_empty" json:"trouble_code"`
|
||||
StartTime *time.Time
|
||||
EndTime *time.Time
|
||||
}
|
||||
|
||||
type DTCInformation struct {
|
||||
ShortName string `bson:"ShortName"`
|
||||
TroubleCode int64 `bson:"TroubleCode"`
|
||||
TroubleCodeHex string `bson:"TroubleCodeHex"`
|
||||
DisplayTroubleCode string `bson:"DisplayTroubleCode"`
|
||||
ErrorText struct {
|
||||
Ti string `bson:"TI"`
|
||||
Text string `bson:"Text"`
|
||||
} `bson:"ErrorText"`
|
||||
SplDataGroups struct {
|
||||
SplDataGroup struct {
|
||||
SplDataGroupCaption interface{} `bson:"SplDataGroupCaption"`
|
||||
SplData []struct {
|
||||
SemanticInformation string `bson:"SemanticInformation"`
|
||||
Ti string `bson:"TI"`
|
||||
Text string `bson:"Text"`
|
||||
} `bson:"SplData"`
|
||||
} `bson:"SplDataGroup"`
|
||||
} `bson:"SplDataGroups"`
|
||||
// ID string `bson:"_id"`
|
||||
// OID interface{} `bson:"OId"`
|
||||
}
|
||||
|
||||
func (dtc DTC_ECU) DTCStatusByteMeaning() (statuses []DTCStatusByteMeaning) {
|
||||
statuses = make([]DTCStatusByteMeaning, 0)
|
||||
if 0b10000000&dtc.StatusByte != 0 {
|
||||
statuses = append(statuses, Bit0)
|
||||
}
|
||||
if 0b01000000&dtc.StatusByte != 0 {
|
||||
statuses = append(statuses, Bit1)
|
||||
}
|
||||
if 0b00100000&dtc.StatusByte != 0 {
|
||||
statuses = append(statuses, Bit2)
|
||||
}
|
||||
if 0b00010000&dtc.StatusByte != 0 {
|
||||
statuses = append(statuses, Bit3)
|
||||
}
|
||||
if 0b00001000&dtc.StatusByte != 0 {
|
||||
statuses = append(statuses, Bit4)
|
||||
}
|
||||
if 0b00000100&dtc.StatusByte != 0 {
|
||||
statuses = append(statuses, Bit5)
|
||||
}
|
||||
if 0b00000010&dtc.StatusByte != 0 {
|
||||
statuses = append(statuses, Bit6)
|
||||
}
|
||||
if 0b00000001&dtc.StatusByte != 0 {
|
||||
statuses = append(statuses, Bit7)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Bit meaning from here: https://www.ni.com/docs/en-US/bundle/automotive-diagnostic-command-set-toolkit/page/adcs/udsreportdtcbystatusmaskvi.html#:~:text=Status%20is%20the%20DTC%20status.%20Usually%2C%20this%20is%20a%20bit%20field%20with%20the%20following%20meaning
|
||||
type DTCStatusByteMeaning string
|
||||
|
||||
const (
|
||||
Bit0 DTCStatusByteMeaning = "testFailed"
|
||||
Bit1 DTCStatusByteMeaning = "testFailedThisMonitoringCycle" //monitoring or operation
|
||||
Bit2 DTCStatusByteMeaning = "pendingDTC"
|
||||
Bit3 DTCStatusByteMeaning = "confirmedDTC"
|
||||
Bit4 DTCStatusByteMeaning = "testNotCompletedSinceLastClear"
|
||||
Bit5 DTCStatusByteMeaning = "testFailedSinceLastClear"
|
||||
Bit6 DTCStatusByteMeaning = "testNotCompletedThisMonitoringCycle" //monitoring or operation
|
||||
Bit7 DTCStatusByteMeaning = "warningIndicatorRequested"
|
||||
)
|
||||
|
||||
func (dtc *DTC_ECU) CacheKey() string {
|
||||
return fmt.Sprintf("%s:%s:%d", dtc.VIN, dtc.ECU, dtc.TroubleCode)
|
||||
}
|
||||
62
pkg/common/ecu_dtc_test.go
Normal file
62
pkg/common/ecu_dtc_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDTCStatusByte(t *testing.T){
|
||||
fakeDTC := DTC_ECU{}
|
||||
|
||||
fakeDTC.StatusByte = 0x09
|
||||
dtcArrayCheck(t, fakeDTC, []DTCStatusByteMeaning{Bit4, Bit7})
|
||||
|
||||
fakeDTC.StatusByte = 0x00
|
||||
dtcArrayCheck(t, fakeDTC, []DTCStatusByteMeaning{})
|
||||
|
||||
fakeDTC.StatusByte = 0x2F
|
||||
dtcArrayCheck(t, fakeDTC, []DTCStatusByteMeaning{Bit7, Bit6, Bit5, Bit4, Bit2})
|
||||
}
|
||||
|
||||
func dtcArrayCheck(t *testing.T, fakeDTC DTC_ECU, expected []DTCStatusByteMeaning){
|
||||
statuses := fakeDTC.DTCStatusByteMeaning()
|
||||
for _, exp := range expected{
|
||||
found := false
|
||||
for _, rec := range statuses{
|
||||
if rec == exp{
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found{
|
||||
t.Logf("Expected %s but did not find", exp)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
for _, rec := range statuses{
|
||||
found := false
|
||||
for _, exp := range expected{
|
||||
if rec == exp{
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found{
|
||||
t.Logf("Extra Result %s", rec)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheKey(t *testing.T) {
|
||||
fakeDTC := DTC_ECU{
|
||||
VIN: "3FAFP13P71R199432",
|
||||
ECU: "ACU",
|
||||
TroubleCode: 8388881,
|
||||
}
|
||||
actual := fakeDTC.CacheKey()
|
||||
expected := "3FAFP13P71R199432:ACU:8388881"
|
||||
|
||||
assert.Equal(t, actual, expected)
|
||||
}
|
||||
5
pkg/common/ecu_keys.go
Normal file
5
pkg/common/ecu_keys.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package common
|
||||
|
||||
type ECUKeys struct {
|
||||
EccKeys []ECCKeys `json:"ecc_keys_map"`
|
||||
}
|
||||
170
pkg/common/ecu_list.go
Normal file
170
pkg/common/ecu_list.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"fiskerinc.com/modules/utils/envtool"
|
||||
"fiskerinc.com/modules/utils/whereami"
|
||||
)
|
||||
|
||||
var EcuMap = map[string]string{
|
||||
"AGS": "Active Grille Shutter",
|
||||
"ADB": "Adaptive Driving Beam",
|
||||
"ADAS": "Advanced Driver Assist System",
|
||||
"ACU": "Airbag Control Unit",
|
||||
"ACP": "Airconditioning Control Panel",
|
||||
"AMP": "Amplifier",
|
||||
"AP_FL": "Anti-Pinch Front Left",
|
||||
"AP_FR": "Anti-Pinch Front Right",
|
||||
"AP_RL": "Anti-Pinch Rear Left",
|
||||
"AP_RR": "Anti-Pinch Rear Right",
|
||||
"AL": "Atmosphere Lamp",
|
||||
"BCS": "Battery HV Current Sensor",
|
||||
"BMS": "Battery Management System",
|
||||
"BMU": "Battery Management Unit",
|
||||
"BCM": "Body Control Module",
|
||||
"CDS": "Center Display Screen",
|
||||
"CCU": "Charging Control Unit",
|
||||
"CIM": "Column Integrated Module",
|
||||
"CVM": "Coolant Valve Module",
|
||||
"CFM": "Cooling Fan Module",
|
||||
"CMRR_FL": "Corner Mid Range Radar Front Left",
|
||||
"CMRR_FR": "Corner Mid Range Radar Front Right",
|
||||
"CMRR_RL": "Corner Mid Range Radar Rear Left",
|
||||
"CMRR_RR": "Corner Mid Range Radar Rear Right",
|
||||
"DVRC": "Digital Video Recorder Camera",
|
||||
"DC-CHM": "Direct Current Charge Machine",
|
||||
"DMC": "Driver Monitor Camera",
|
||||
"DSMC": "Driver Seat Memory Controller",
|
||||
"DWSG": "Driver Window Switch Group",
|
||||
"EPS": "Electric Power Steering",
|
||||
"EPS1": "Electric Power Steering Sub 1",
|
||||
"EPS2": "Electric Power Steering Sub 2",
|
||||
"EAS": "Electrical Air Compressor System",
|
||||
"ECC": "Electrical Climate Controller",
|
||||
"EWP_B": "Electrical Water Pump Battery",
|
||||
"EWP_FD": "Electrical Water Pump Front Drive",
|
||||
"EWP_H": "Electrical Water Pump Heat",
|
||||
"EWP_RD": "Electrical Water Pump Rear Drive",
|
||||
"EWM": "Electrical Wiper Motor",
|
||||
"EXV_B": "Electronic Expansion Value Battery",
|
||||
"EXV_HP": "Electronic Expansion Valve HPC",
|
||||
"ESP": "Electronic Stability Program",
|
||||
"FDHA_FL": "Flush Door Handle Actuator Front Left",
|
||||
"FDHA_FR": "Flush Door Handle Actuator Front Right",
|
||||
"FDHA_RL": "Flush Door Handle Actuator Rear Left",
|
||||
"FDHA_RR": "Flush Door Handle Actuator Rear Right",
|
||||
"Lumber": "Four-Way Lumber",
|
||||
"FBM_L": "Front Beam Module Left",
|
||||
"FBM_R": "Front Beam Module Right",
|
||||
"FCM": "Front Camera Module",
|
||||
"GW": "Gateway",
|
||||
"HUD": "Head-Up Display",
|
||||
"IDS": "Instrument Display Screen",
|
||||
"ICC": "Integrated Cockpit Controller",
|
||||
"IBS": "Intelligent Battery Sensor",
|
||||
"iBooster": "Intelligent Booster",
|
||||
"KS": "Kick Sensor",
|
||||
"LSC": "Left Side Camera",
|
||||
"MRR": "Mid Range Radar",
|
||||
"MCU": "TBox Microcontroller",
|
||||
"MCU_F": "Motor Control Unit Front",
|
||||
"MCU_R": "Motor Control Unit Rear",
|
||||
"MDV": "Motorized Deco-Vent",
|
||||
"MFS": "Multifunction Steering",
|
||||
"MIS": "Multimedia Interactive Switch",
|
||||
"MPC": "Multipurpose Camera",
|
||||
"OBC": "Onboard Charger",
|
||||
"OMC": "Occupant Monitor Camera",
|
||||
"OHC": "Overhead Console",
|
||||
"PAS": "Parking Assistant System",
|
||||
"PCU": "Parking Control Unit",
|
||||
"PMS": "Particulate Matter Sensor",
|
||||
"PSM": "Passenger Seat Module",
|
||||
"PEPS": "Passive Entry And Passive Start",
|
||||
"PKC": "Phone Key Controller",
|
||||
"PKC_ANT_L": "Phone Key Controller Antenna Left",
|
||||
"PKC_ANT_R": "Phone Key Controller Antenna Right",
|
||||
"PWC_L": "Phone Wireless Charging Left",
|
||||
"PWC_R": "Phone Wireless Charging Right",
|
||||
"PVIU": "Photovoltaic Integration Unit",
|
||||
"PASC": "Power Adjust Steering Column",
|
||||
"PDU": "Power Distribution Unit",
|
||||
"PLGM": "Power Lift Gate Module",
|
||||
"RLS": "Rain Light Sensor",
|
||||
"RAC": "Rear Airconditioning Control",
|
||||
"RVC": "Rear View Camera",
|
||||
"RSC": "Right Side Camera",
|
||||
"RCM": "Roof Control Module",
|
||||
"RSM": "Roof Shade Module",
|
||||
"SCM": "Suspension Control Module",
|
||||
"TBOX": "Telematics Box",
|
||||
"TPMS": "Tire Pressure Monitoring System",
|
||||
"TDS": "Touch Display Screen",
|
||||
"TRM": "Trailer Module",
|
||||
"USB Box": "USB Box",
|
||||
"VCU": "Vehicle Control Unit",
|
||||
"VSP": "Vehicle Sound For Pedestrians",
|
||||
"WTC_B": "Water Thermal Controller Battery",
|
||||
"WTC_H": "Water Thermal Controller Heat",
|
||||
}
|
||||
|
||||
// Currently used by car configuration sender only
|
||||
var CurrentECUMap = map[string]struct{}{
|
||||
"ACU": {}, "ADAS": {}, "AMP": {}, "BCM": {}, "BMS": {},
|
||||
"CIM": {}, "CMRR_FL": {}, "CMRR_FR": {}, "CMRR_RL": {}, "CMRR_RR": {},
|
||||
"DSMC": {}, "EAS": {}, "ECC": {}, "EPS1": {}, "EPS2": {},
|
||||
"ESP": {}, "FCM": {}, "GW": {}, "ICC": {}, "MCU_F": {},
|
||||
"MCU_R": {}, "MFS": {}, "MRR": {}, "OBC": {}, "OHC": {},
|
||||
"PKC": {}, "PLGM": {}, "PSM": {}, "PVIU": {}, "PWC_R": {},
|
||||
"PWC": {}, "RAC": {}, "SCM": {}, "TRM": {}, "MCU": {},
|
||||
"VCU": {}, "VSP": {}, "WTC_B": {}, "WTC_H": {}, "iBooster": {},
|
||||
"VOD": {}, // Although not officially a ECU, we don't want to delete it from the list of data we receive
|
||||
}
|
||||
|
||||
// List of ECU's we want to filter out from our configuration
|
||||
var FilterECUConfigurationMap = map[string]struct{}{}
|
||||
|
||||
func init() {
|
||||
BuildFilterECUConfigurationMap()
|
||||
}
|
||||
|
||||
func BuildFilterECUConfigurationMap() {
|
||||
ecusToFilter := strings.Split(envtool.GetEnv("NO_CDS_ECUS", ""), ",")
|
||||
for _, str := range ecusToFilter {
|
||||
str = strings.TrimSpace(str)
|
||||
FilterECUConfigurationMap[str] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
var ECUReplacement = func() map[string]string {
|
||||
switch whereami.Service {
|
||||
case whereami.OTA:
|
||||
return OTAUpdateECUReplacement
|
||||
case whereami.AFTERSALES:
|
||||
return AfterSalesECUReplacement
|
||||
default:
|
||||
return OTAUpdateECUReplacement
|
||||
}
|
||||
}
|
||||
|
||||
var AfterSalesECUReplacement = map[string]string{}
|
||||
|
||||
var OTAUpdateECUReplacement = map[string]string{
|
||||
"EPS": "EPS1",
|
||||
"PDU": "OBC",
|
||||
"PWC_L": "PWC",
|
||||
}
|
||||
|
||||
// An inverse of the otaUpdateECUReplacement. To map the cars definition to the pdx/odx definitions
|
||||
// keep this updated
|
||||
var CarDTCLookupInverse = map[string]string{
|
||||
"EPS1": "EPS",
|
||||
"OBC": "PDU",
|
||||
"PWC": "PWC_L",
|
||||
}
|
||||
|
||||
var FlashpackCalculationECUReplacement = map[string]string{
|
||||
"MCU": "TBOX",
|
||||
"OBC": "PDU",
|
||||
}
|
||||
32
pkg/common/ecu_stat.go
Normal file
32
pkg/common/ecu_stat.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package common
|
||||
|
||||
// ECUStat describes ecu stat received from clickhouse.
|
||||
type ECUStat struct {
|
||||
ECUName string `json:"ecu_name" ch:"ecu_name"`
|
||||
IncorrectValues uint64 `json:"signals_w_incorrect_values" ch:"signals_w_incorrect_values"`
|
||||
AllZero uint64 `json:"signals_all_zero" ch:"signals_all_zero"`
|
||||
ECUSignalsTotal uint64 `json:"number_of_ecu_signals" ch:"number_of_ecu_signals"`
|
||||
SignalsTotal uint64 `json:"total_signal_records" ch:"total_signal_records"`
|
||||
IncorrectPercent float64 `json:"incorrect_val_signal_pct" ch:"incorrect_val_signal_pct"`
|
||||
ZeroPercent float64 `json:"zero_signals_pct" ch:"zero_signals_pct"`
|
||||
}
|
||||
|
||||
// StatsFilter is expected to be used for stats request.
|
||||
type StatsFilter struct {
|
||||
MinOutOfRangePct float32 `validate:"required" json:"min_out_of_range_pct"`
|
||||
MinZeroPct float32 `validate:"required" json:"min_zero_pct"`
|
||||
Hours int `validate:"required" json:"hours"`
|
||||
VINs []string `validate:"required,min=1,dive,vin" json:"vins"`
|
||||
DBCs []string `validate:"required,min=1" json:"dbcs"`
|
||||
ECUs []string `validate:"required,min=1" json:"ecus"`
|
||||
}
|
||||
|
||||
// VINStatsFilter is expected to be used for stats request for a specific vehicle.
|
||||
type VINStatsFilter struct {
|
||||
MinOutOfRangePct float32 `validate:"required" json:"min_out_of_range_pct"`
|
||||
MinZeroPct float32 `validate:"required" json:"min_zero_pct"`
|
||||
Hours int `validate:"required" json:"hours"`
|
||||
VIN string `validate:"required,vin" json:"-"`
|
||||
DBC string `validate:"required" json:"-"`
|
||||
ECUs []string `validate:"required,min=1" json:"ecus"`
|
||||
}
|
||||
13
pkg/common/empty_message.go
Normal file
13
pkg/common/empty_message.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package common
|
||||
|
||||
type EmptyMessageFromMobile struct {
|
||||
VIN string `json:"vin" validate:"required"`
|
||||
}
|
||||
|
||||
func (m EmptyMessageFromMobile) GetVIN() string {
|
||||
return m.VIN
|
||||
}
|
||||
|
||||
func (m EmptyMessageFromMobile) GetPayload() interface{} {
|
||||
return nil
|
||||
}
|
||||
31
pkg/common/event.go
Normal file
31
pkg/common/event.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Event is the generic structure for events sent over Kafka
|
||||
type Event struct {
|
||||
Topic string `json:"topic"`
|
||||
Key string `json:"key"`
|
||||
Payload Message `json:"payload"`
|
||||
}
|
||||
|
||||
// EventRawJSON is Event (see above) but keeps payload as raw data
|
||||
type EventRawJSON struct {
|
||||
Topic string `json:"topic"`
|
||||
Key string `json:"key"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
}
|
||||
|
||||
func (e *EventRawJSON) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*e)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (e *EventRawJSON) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, e)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
52
pkg/common/file_key.go
Normal file
52
pkg/common/file_key.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
b64 "encoding/base64"
|
||||
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
type FileKey struct {
|
||||
FileID string `pg:",pk" validate:"required,len=16,hexadecimal"`
|
||||
Key []byte `pg:",notnull" validate:"required"`
|
||||
Auth []byte `pg:",notnull" validate:"required"`
|
||||
Nonce []byte `pg:",notnull" validate:"required"`
|
||||
Error string `pg:"-"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
|
||||
type FileKeysRequest struct {
|
||||
FileIDs []string `json:"file_ids" validate:"required,dive,len=16,hexadecimal"`
|
||||
}
|
||||
|
||||
type FileKeyResponse struct {
|
||||
FileID string `json:"file_id" redis:"file_id" validate:"required,len=16,hexadecimal"`
|
||||
Key string `json:"key,omitempty" redis:"key,omitempty" validate:"required"`
|
||||
Auth string `json:"auth,omitempty" redis:"auth,omitempty" validate:"required"`
|
||||
Nonce string `json:"nonce,omitempty" redis:"nonce,omitempty" validate:"required"`
|
||||
Error string `json:"error,omitempty" redis:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Turns the FileKey into a FileKeyResponse
|
||||
func (fkr *FileKeyResponse) Apply(key *FileKey) {
|
||||
fkr.FileID = key.FileID
|
||||
fkr.Key = b64.StdEncoding.EncodeToString(key.Key)
|
||||
fkr.Auth = b64.StdEncoding.EncodeToString(key.Auth)
|
||||
fkr.Nonce = b64.StdEncoding.EncodeToString(key.Nonce)
|
||||
}
|
||||
|
||||
// Turns a FileKeyResponse into a file key
|
||||
func (key *FileKey) Apply(fkr *FileKeyResponse) (err error) {
|
||||
key.FileID = fkr.FileID
|
||||
key.Error = fkr.Error
|
||||
key.Key, err = b64.StdEncoding.DecodeString(fkr.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.Auth, err = b64.StdEncoding.DecodeString(fkr.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.Nonce, err = b64.StdEncoding.DecodeString(fkr.Nonce)
|
||||
return err
|
||||
}
|
||||
108
pkg/common/flashpack.go
Normal file
108
pkg/common/flashpack.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FlashpackDataRequest struct {
|
||||
XMLName xml.Name `xml:"SerializationData"`
|
||||
Header FlashPackHeaderArea `xml:"HeaderArea" validate:"required"`
|
||||
VehicleData VehicleData `xml:"DataArea>VehicleData" validate:"required"`
|
||||
SerializationDataIndices SerializationDataIndices `xml:"DataArea>SerializationDataIndices" validate:"required"`
|
||||
}
|
||||
|
||||
type FlashPackHeaderArea struct {
|
||||
CreationDateTime CreationDateTimeWithSeconds `xml:"CreationDateTime" json:"creation_date_time"`
|
||||
MessageIdentifier string `xml:"MessageIdentifier" json:"message_identifier"`
|
||||
InterfaceID int `xml:"InterfaceId" json:"interface_id"`
|
||||
SourceSystem string `xml:"Sender>SourceSystem" json:"source_system"`
|
||||
TargetSystem string `xml:"Sender>TargetSystem" json:"target_system"`
|
||||
}
|
||||
|
||||
type CreationDateTimeWithSeconds struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (t *CreationDateTimeWithSeconds) UnmarshalText(data []byte) error {
|
||||
// Fractional seconds are handled implicitly by Parse.
|
||||
var err error
|
||||
t.Time, err = time.Parse("2006-01-02T15:04:05", string(data))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t CreationDateTimeWithSeconds) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
s := t.Format("2006-01-02T15:04:05")
|
||||
return e.EncodeElement(s, start)
|
||||
}
|
||||
|
||||
type VehicleData struct {
|
||||
VIN string `xml:"VIN"`
|
||||
FlashPackNumber string `xml:"FlashPackNumber"`
|
||||
PDXData string `xml:"PDXData"`
|
||||
}
|
||||
|
||||
type SerializationDataIndices struct {
|
||||
SerializationDataIndices []SerializationDataIndex `xml:"SerializationDataIndex"`
|
||||
}
|
||||
|
||||
type SerializationDataIndex struct {
|
||||
IndexNr string `xml:"IndexNr"`
|
||||
IndexName string `xml:"IndexName"`
|
||||
ECUName string `xml:"ECUName"`
|
||||
DataType string `xml:"DataType"`
|
||||
SerialNumber string `xml:"SerialNumber"`
|
||||
F108_CodingData string `xml:"F108_CodingData"`
|
||||
F111_VehicleOrderData string `xml:"F111_VehicleOrderData"`
|
||||
F183_BootloaderVersion string `xml:"F183_BootloaderVersion"`
|
||||
F187_VehicleManufacturerSparePartNumber string `xml:"F187_VehicleManufacturerSparePartNumber"`
|
||||
F188_ECUSoftwareNumber string `xml:"F188_ECUSoftwareNumber"`
|
||||
F191_ECUHardwareNumber string `xml:"F191_ECUHardwareNumber"`
|
||||
F195_ECUSoftwareVersionNumber string `xml:"F195_ECUSoftwareVersionNumber"`
|
||||
F18C_ECUSerialNumber string `xml:"F18C_ECUSerialNumber"`
|
||||
MaterialNr string `xml:"MaterialNr"`
|
||||
SoftwareBranch string `xml:"SoftwareBranch"`
|
||||
SourcingType string `xml:"SourcingType"`
|
||||
SourcingTypeName string `xml:"SourcingTypeName"`
|
||||
}
|
||||
|
||||
// FlashpackDataRequestSwag is used ONLY to represent FlashpackDataRequest in swagger.
|
||||
type FlashpackDataRequestSwag struct {
|
||||
Header struct {
|
||||
CreationDateTime CreationDateTime `json:"CreationDateTime" swaggertype:"string"`
|
||||
MessageIdentifier string `json:"MessageIdentifier"`
|
||||
InterfaceID int `json:"InterfaceId"`
|
||||
Sender struct {
|
||||
SourceSystem string `json:"SourceSystem"`
|
||||
TargetSystem string `json:"TargetSystem"`
|
||||
} `json:"Sender"`
|
||||
} `json:"HeaderArea" validate:"required"`
|
||||
DataArea struct {
|
||||
VehicleData struct {
|
||||
VIN string `json:"vin"`
|
||||
FlashpackNumber string `json:"flashpack_number"`
|
||||
PDXNumber string `json:"pdx_number"`
|
||||
} `json:"VehicleData" validate:"required"`
|
||||
SerializationDataIndices struct {
|
||||
SerializationDataIndices []struct {
|
||||
IndexNr string `json:"index_nr"`
|
||||
IndexName string `json:"index_name"`
|
||||
DataType string `json:"data_type"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
F108_CodingData string `json:"f108_coding_data"`
|
||||
F111_VehicleOrderData string `json:"f111_vehicle_order_data"`
|
||||
F183_BootloaderVersion string `json:"f183_boot_loader_version"`
|
||||
F187_VehicleManufacturerSparePartNumber string `json:"f187_vehicle_manufacturer_spare_part_number"`
|
||||
F188_ECUSoftwareNumber string `json:"f188_ecu_software_number"`
|
||||
F191_ECUHardwareNumber string `json:"f191_ecu_hardware_number"`
|
||||
F195_ECUSoftwareVersionNumber string `json:"f195_ecu_software_version_number"`
|
||||
F18C_ECUSerialNumber string `json:"f18c_ecu_serial_number"`
|
||||
MaterialNr string `json:"material_nr"`
|
||||
SoftwareBranch string `json:"software_branch"`
|
||||
SourcingType string `json:"sourcing_type"`
|
||||
SourcingTypeName string `json:"sourcing_type_name"`
|
||||
}
|
||||
} `json:"SerializationDataIndices" validate:"required"`
|
||||
} `json:"DataArea"`
|
||||
} // @name SerializationData
|
||||
10
pkg/common/gps_path.go
Normal file
10
pkg/common/gps_path.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package common
|
||||
|
||||
// GpsPaths is a map of VIN:GpsPath
|
||||
type GpsPaths map[string]GpsPath
|
||||
|
||||
// GpsPath is a slice of GpsPoints
|
||||
type GpsPath []GpsPoint
|
||||
|
||||
// GpsPoint is a [latitude, longitude] pair
|
||||
type GpsPoint []float64
|
||||
10
pkg/common/handlers/handlers.go
Normal file
10
pkg/common/handlers/handlers.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package handlers
|
||||
|
||||
const (
|
||||
UpdateManifest = "update_manifest"
|
||||
UpdateManifestInstall = "update_manifest_install"
|
||||
UpdateManifestCancel = "update_manifest_cancel"
|
||||
CarUpdateStatus = "car_update_status"
|
||||
GetFileKeys = "get_filekeys"
|
||||
FileKeys = "filekeys"
|
||||
)
|
||||
295
pkg/common/hmi_rx_test.go
Normal file
295
pkg/common/hmi_rx_test.go
Normal file
@@ -0,0 +1,295 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
"fiskerinc.com/modules/common/handlers"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
)
|
||||
|
||||
func TestHMIRXMessages(t *testing.T) {
|
||||
schema := "file://" + testhelper.GetSchemaDirPath() + "/hmi/RXMessage.json"
|
||||
now := time.Now()
|
||||
manifest := common.UpdateManifest{
|
||||
ID: 1234567890,
|
||||
Name: "Test Update Manifest",
|
||||
Version: "1.2.3",
|
||||
Description: "This is a test manifest",
|
||||
ReleaseNotes: "https://fiskerinc.com",
|
||||
Fingerprint: "8fe27f7fb079ebe06db5f88586b98797",
|
||||
Type: "standard",
|
||||
Country: "US",
|
||||
PowerTrain: "MD23",
|
||||
Restraint: "None",
|
||||
Model: "Ocean",
|
||||
Trim: "Sport",
|
||||
Year: 2022,
|
||||
BodyType: "truck",
|
||||
ECUs: []*common.UpdateManifestECU{
|
||||
{
|
||||
ECU: "ICC",
|
||||
Version: "1.2.3",
|
||||
CurrentVersion: "1.0.0",
|
||||
HWVersions: []string{"10000"},
|
||||
ConfigurationMask: "04d923318b8e1517a9cc8b52af11b051",
|
||||
Files: []*common.UpdateManifestFile{
|
||||
{
|
||||
FileID: "XXXXXXXXXX",
|
||||
URL: "https://fiskerinc.com/ICC.bin",
|
||||
Checksum: "CHECKSUM",
|
||||
FileType: common.Software,
|
||||
FileSize: 90,
|
||||
WriteRegionID: 100,
|
||||
WriteRegion: common.MemoryRegion{
|
||||
Offset: 101,
|
||||
Length: 102,
|
||||
},
|
||||
EraseRegionID: 200,
|
||||
EraseRegion: &common.MemoryRegion{
|
||||
Offset: 201,
|
||||
Length: 202,
|
||||
},
|
||||
FileKey: &common.FileKeyResponse{
|
||||
FileID: "fileid",
|
||||
Key: "key",
|
||||
Auth: "auth",
|
||||
Nonce: "nonce",
|
||||
Error: "NoError",
|
||||
},
|
||||
},
|
||||
},
|
||||
Rollback: []*common.UpdateManifestECU{
|
||||
{
|
||||
Version: "1.2.3",
|
||||
ConfigurationMask: "04d923318b8e1517a9cc8b52af11b051",
|
||||
Files: []*common.UpdateManifestFile{
|
||||
{
|
||||
FileID: "XXXXXXXXXX",
|
||||
URL: "https://fiskerinc.com/settings.config",
|
||||
FileType: common.Calibration,
|
||||
Checksum: "CHECKSUM",
|
||||
FileSize: 100,
|
||||
WriteRegionID: 100,
|
||||
WriteRegion: common.MemoryRegion{
|
||||
Offset: 101,
|
||||
Length: 102,
|
||||
},
|
||||
EraseRegionID: 200,
|
||||
EraseRegion: &common.MemoryRegion{
|
||||
Offset: 201,
|
||||
Length: 202,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
CarUpdateID: 1,
|
||||
}
|
||||
manifest.Scrub(common.HMI)
|
||||
|
||||
tests := []testhelper.SchemaTest{
|
||||
{
|
||||
Name: "TestHMIUpdateManifest",
|
||||
Object: common.Message{
|
||||
Handler: "update_manifest",
|
||||
Data: manifest,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileMapDestinationSource",
|
||||
Object: common.Message{
|
||||
Handler: "map_destination",
|
||||
Data: common.MapDestinationSource{
|
||||
DriverID: "valid-cognito-id-1",
|
||||
Name: "Fisker Inc",
|
||||
Address: &common.TomTomAddress{
|
||||
StreetNumber: "3030",
|
||||
StreetName: "17th St",
|
||||
LocalName: "San Francisco",
|
||||
PostalCode: "94110",
|
||||
CountrySubdivisionName: "California",
|
||||
CountryCodeIso3: "USA",
|
||||
},
|
||||
Coordinates: common.MapCoordinates{
|
||||
Latitude: 37.0,
|
||||
Longitude: -122.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileMapRouteSource",
|
||||
Object: common.Message{
|
||||
Handler: "map_route",
|
||||
Data: common.MapRouteSource{
|
||||
DriverID: "valid-cognito-id-1",
|
||||
Waypoints: []common.MapWaypoint{
|
||||
{
|
||||
Type: "Restaurant",
|
||||
Title: "Test Restaurant",
|
||||
MapCoordinates: common.MapCoordinates{
|
||||
Latitude: 111,
|
||||
Longitude: 222,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "Park",
|
||||
Title: "Test Park",
|
||||
MapCoordinates: common.MapCoordinates{
|
||||
Latitude: 333.333,
|
||||
Longitude: 444.444,
|
||||
},
|
||||
},
|
||||
},
|
||||
Route: []common.MapCoordinates{
|
||||
{
|
||||
Latitude: 123,
|
||||
Longitude: 456,
|
||||
},
|
||||
{
|
||||
Latitude: 999,
|
||||
Longitude: 888,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIProfiles",
|
||||
Object: common.Message{
|
||||
Handler: "profiles",
|
||||
Data: []common.CarToDriverModel{{
|
||||
User: common.UserProfile{
|
||||
FirstName: "First",
|
||||
LastName: "Last",
|
||||
Email: "test@test.com",
|
||||
Phone: "1234567890",
|
||||
},
|
||||
DriverID: "valid-cognito-id-1",
|
||||
Role: "DRIVER",
|
||||
Settings: []common.CarSetting{{
|
||||
Name: "TESTSETTING",
|
||||
Value: "TESTVALUE",
|
||||
Type: "string",
|
||||
DBModelBase: dbbasemodel.DBModelBase{
|
||||
UpdatedAt: &now,
|
||||
},
|
||||
}},
|
||||
Subscriptions: []common.Subscription{{
|
||||
Name: "test",
|
||||
Destination: "ICC",
|
||||
Expires: time.Date(2021, 10, 30, 8, 35, 0, 0, time.UTC),
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMISettingsUpdate",
|
||||
Object: common.Message{
|
||||
Handler: "settings_update",
|
||||
Data: common.HMISettingsUpdate{
|
||||
DriverID: "valid-cognito-id-1",
|
||||
Settings: []common.CarSetting{{
|
||||
Name: "TESTSETTING",
|
||||
Value: "TESTVALUE",
|
||||
Type: "string",
|
||||
DBModelBase: dbbasemodel.DBModelBase{
|
||||
UpdatedAt: &now,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMISubscriptionsUpdate",
|
||||
Object: common.Message{
|
||||
Handler: "subscriptions_update",
|
||||
Data: common.HMISubscriptionUpdate{
|
||||
DriverID: "valid-cognito-id-1",
|
||||
Subscriptions: []common.Subscription{{
|
||||
Name: "test",
|
||||
Destination: "ICC",
|
||||
Expires: time.Date(2021, 10, 30, 8, 35, 0, 0, time.UTC),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIPOIs",
|
||||
Object: common.Message{
|
||||
Handler: "user_pois_get",
|
||||
Data: common.HMIPOIRequestMessage{
|
||||
DriverID: "valid-cognito-id-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIPOICreate",
|
||||
Object: common.Message{
|
||||
Handler: "user_poi_create",
|
||||
Data: common.HMIPOIMessage{
|
||||
DriverID: "valid-cognito-id-1",
|
||||
UserPOI: common.PointOfInterest{
|
||||
Name: "TestPOI",
|
||||
Location: common.POILocation{
|
||||
Latitude: 1.23,
|
||||
Longitude: 4.56,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIPOIEdit",
|
||||
Object: common.Message{
|
||||
Handler: "user_poi_edit",
|
||||
Data: common.HMIPOIMessage{
|
||||
DriverID: "valid-cognito-id-1",
|
||||
UserPOI: common.PointOfInterest{
|
||||
Name: "TestPOI",
|
||||
Location: common.POILocation{
|
||||
Latitude: 8.88,
|
||||
Longitude: 9.99,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIPOIDelete",
|
||||
Object: common.Message{
|
||||
Handler: "user_poi_delete",
|
||||
Data: common.HMIPOIDeleteMessage{
|
||||
DriverID: "valid-cognito-id-1",
|
||||
Name: "TestPOI",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIError",
|
||||
Object: common.Message{
|
||||
Handler: "error",
|
||||
Data: common.MessageString{
|
||||
Message: "disconnected by duplicate ID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestCancelUpdateManifest",
|
||||
Object: common.Message{
|
||||
Handler: handlers.UpdateManifestCancel,
|
||||
Data: common.CarUpdateRequest{
|
||||
CarUpdateID: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testhelper.NewSchemaTestHelper(t, schema).
|
||||
RunSchemaTests(tests)
|
||||
}
|
||||
12
pkg/common/hmi_session.go
Normal file
12
pkg/common/hmi_session.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package common
|
||||
|
||||
type HMISessionMessage struct {
|
||||
Handler string `json:"handler"`
|
||||
Data HMISessionData `json:"data"`
|
||||
}
|
||||
|
||||
type HMISessionData struct {
|
||||
Salt string `json:"salt,omitempty"`
|
||||
SessionID string `json:"session_id,omitempty"`
|
||||
VIN string `json:"vin,omitempty"` // FOR INSECURE ENDPOINT - DELETE THIS IN FUTURE ITERATIONS
|
||||
}
|
||||
88
pkg/common/hmi_tx_test.go
Normal file
88
pkg/common/hmi_tx_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
)
|
||||
|
||||
func TestHMITXMessages(t *testing.T) {
|
||||
schema := "file://" + testhelper.GetSchemaDirPath() + "/hmi/TXMessage.json"
|
||||
now := time.Now()
|
||||
|
||||
tests := []testhelper.SchemaTest{
|
||||
{
|
||||
Name: "TestHMIVerifySalt",
|
||||
Object: common.Message{
|
||||
Handler: "verify",
|
||||
Data: common.HMISessionData{
|
||||
Salt: "testsalt",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIVerifySession",
|
||||
Object: common.Message{
|
||||
Handler: "verify",
|
||||
Data: common.HMISessionData{
|
||||
SessionID: "test_session_id",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIProfiles",
|
||||
Object: common.Message{
|
||||
Handler: "profiles",
|
||||
Data: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMISettingsUpdate",
|
||||
Object: common.Message{
|
||||
Handler: "settings_update",
|
||||
Data: common.HMISettingsUpdate{
|
||||
DriverID: "valid-cognito-id-1",
|
||||
Settings: []common.CarSetting{{
|
||||
Name: "TESTSETTING",
|
||||
Value: "TESTVALUE",
|
||||
Type: "string",
|
||||
DBModelBase: dbbasemodel.DBModelBase{
|
||||
UpdatedAt: &now,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIPOIs",
|
||||
Object: common.Message{
|
||||
Handler: "user_pois",
|
||||
Data: common.HMIPOIsMessage{
|
||||
DriverID: "valid-cognito-id-1",
|
||||
UserPOIs: []common.PointOfInterest{
|
||||
{
|
||||
Name: "TestWorkPOI",
|
||||
Location: common.POILocation{
|
||||
Latitude: 1.11,
|
||||
Longitude: 2.22,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHomePOI",
|
||||
Location: common.POILocation{
|
||||
Latitude: 8.88,
|
||||
Longitude: 9.99,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testhelper.NewSchemaTestHelper(t, schema).
|
||||
RunSchemaTests(tests)
|
||||
}
|
||||
28
pkg/common/issue.go
Normal file
28
pkg/common/issue.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
type AddIssueRequest struct {
|
||||
Issue
|
||||
Images [][]byte `json:"images"`
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
ID int `json:"id" pg:",pk"`
|
||||
VIN string `json:"vin" pg:"vin" validate:"required,vin"`
|
||||
Title string `json:"title" pg:"title" validate:"required,max=256"`
|
||||
Description string `json:"description" pg:"description" validate:"required,max=1024"`
|
||||
DriverID string `json:"driver_id,omitempty" pg:"driver_id"`
|
||||
Timestamp time.Time `json:"timestamp" validate:"required" pg:"created_at"`
|
||||
IssueImages []IssueImage `json:"images,omitempty" pg:"rel:has-many"`
|
||||
}
|
||||
|
||||
type IssueImage struct {
|
||||
ID int `json:"id" pg:",pk"`
|
||||
Image []byte `json:"image" pg:"image"`
|
||||
IssueID int `json:"issue_id" pg:"issue_id"`
|
||||
}
|
||||
|
||||
type IssueSearch struct {
|
||||
Search string `json:"search" validate:"max=1024"`
|
||||
}
|
||||
8
pkg/common/json_blob_read_result.go
Normal file
8
pkg/common/json_blob_read_result.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package common
|
||||
|
||||
type JSONBlobReadResult struct {
|
||||
Data interface{} `json:"data"`
|
||||
RealOffset int64 `json:realOffset`
|
||||
BytesRead int64 `json:"bytesRead"`
|
||||
BlobSize int64 `json:"blobSize"`
|
||||
}
|
||||
6
pkg/common/json_dbquery_result.go
Normal file
6
pkg/common/json_dbquery_result.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package common
|
||||
|
||||
type JSONDBQueryResult struct {
|
||||
Data interface{} `json:"data"`
|
||||
Total int `json:"total,omitempty"`
|
||||
}
|
||||
18
pkg/common/json_resp.go
Normal file
18
pkg/common/json_resp.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package common
|
||||
|
||||
// JSONLink json response struct
|
||||
type JSONLink struct {
|
||||
Link string `json:"link,omitempty"`
|
||||
Timestamp int64 `json:"timestamp,omitempty"`
|
||||
}
|
||||
|
||||
// JSONError json error struct
|
||||
type JSONError struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// JSONMessage json error struct
|
||||
type JSONMessage struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
53
pkg/common/json_rpc.go
Normal file
53
pkg/common/json_rpc.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package common
|
||||
|
||||
const JSONRPCVersion = "2.0"
|
||||
|
||||
func NewJSONRPCResponse(req JSONRPCRequest, result map[string]interface{}) JSONRPCResponse {
|
||||
resp := JSONRPCResponse{Result: result}
|
||||
resp.Init(req)
|
||||
return resp
|
||||
}
|
||||
|
||||
func NewJSONRPCErrorResponse(code int, message string) JSONRPCErrorResponse {
|
||||
err := JSONRPCErrorResponse{}
|
||||
err.RPCVersion = JSONRPCVersion
|
||||
err.Err(code, message)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type JSONRPCData map[string]interface{}
|
||||
|
||||
type JSONRPCBase struct {
|
||||
RPCVersion string `json:"jsonrpc" validate:"required,max=10"`
|
||||
Method string `json:"method" validate:"required,max=1000"`
|
||||
ID int `json:"id" validate:"required,gt=0"`
|
||||
}
|
||||
|
||||
func (b *JSONRPCBase) Init(req JSONRPCRequest) {
|
||||
b.RPCVersion = req.RPCVersion
|
||||
b.Method = req.Method
|
||||
b.ID = req.ID
|
||||
}
|
||||
|
||||
type JSONRPCRequest struct {
|
||||
JSONRPCBase
|
||||
Params JSONRPCData `json:"params"`
|
||||
}
|
||||
|
||||
type JSONRPCResponse struct {
|
||||
JSONRPCBase
|
||||
Result JSONRPCData `json:"result"`
|
||||
}
|
||||
|
||||
type JSONRPCErrorResponse struct {
|
||||
JSONRPCBase
|
||||
Error JSONRPCData `json:"error"`
|
||||
}
|
||||
|
||||
func (e *JSONRPCErrorResponse) Err(code int, message string) {
|
||||
e.Error = JSONRPCData{
|
||||
"code": code,
|
||||
"message": message,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package manifestfingerprintparams
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/utils/envtool"
|
||||
)
|
||||
|
||||
var fpParams FingerprintParamer
|
||||
var fpParamsOnce sync.Once
|
||||
|
||||
func SetFPParams(fpp FingerprintParamer) {
|
||||
fpParams = fpp
|
||||
}
|
||||
|
||||
func GetFPParams() FingerprintParamer {
|
||||
fpParamsOnce.Do(func() {
|
||||
if fpParams == nil {
|
||||
fpParams = &FingerprintParams{
|
||||
serialNum: envtool.GetEnv("OTA_MANIFEST_SERIAL", "00000000000000000"),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return fpParams
|
||||
}
|
||||
|
||||
type FingerprintParams struct {
|
||||
serialNum string
|
||||
}
|
||||
|
||||
func (p *FingerprintParams) ManifestSerial() string {
|
||||
return p.serialNum
|
||||
}
|
||||
|
||||
func (p *FingerprintParams) CurTime() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
|
||||
var _ FingerprintParamer = &FingerprintParams{}
|
||||
|
||||
type FingerprintParamer interface {
|
||||
ManifestSerial() string
|
||||
CurTime() time.Time
|
||||
}
|
||||
18
pkg/common/manifestfingerprintparams/mock.go
Normal file
18
pkg/common/manifestfingerprintparams/mock.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package manifestfingerprintparams
|
||||
|
||||
import "time"
|
||||
|
||||
type MockFingerprintParamer struct{
|
||||
Time time.Time
|
||||
ManifestSerialValue string
|
||||
}
|
||||
|
||||
func (m *MockFingerprintParamer) CurTime()(time.Time){
|
||||
return m.Time
|
||||
}
|
||||
|
||||
func (m *MockFingerprintParamer) ManifestSerial()(string){
|
||||
return m.ManifestSerialValue
|
||||
}
|
||||
|
||||
var _ FingerprintParamer = &MockFingerprintParamer{}
|
||||
47
pkg/common/map_sequencing.go
Normal file
47
pkg/common/map_sequencing.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package common
|
||||
|
||||
type MapDestinationRequest struct {
|
||||
VIN string `json:"vin" validate:"vin"`
|
||||
Name string `json:"name"`
|
||||
Address *TomTomAddress `json:"address,omitempty"`
|
||||
Coordinates MapCoordinates `json:"coordinates"`
|
||||
}
|
||||
|
||||
type MapDestinationSource struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
Name string `json:"name"`
|
||||
Address *TomTomAddress `json:"address,omitempty"`
|
||||
Coordinates MapCoordinates `json:"coordinates"`
|
||||
}
|
||||
|
||||
type MapRouteRequest struct {
|
||||
VIN string `json:"vin" validate:"vin"`
|
||||
Waypoints []MapWaypoint `json:"waypoints"`
|
||||
Route []MapCoordinates `json:"route"`
|
||||
}
|
||||
|
||||
type MapRouteSource struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
Waypoints []MapWaypoint `json:"waypoints"`
|
||||
Route []MapCoordinates `json:"route"`
|
||||
}
|
||||
|
||||
type MapCoordinates struct {
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
}
|
||||
|
||||
type MapWaypoint struct {
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
MapCoordinates
|
||||
}
|
||||
|
||||
type TomTomAddress struct {
|
||||
StreetNumber string `json:"street_number"`
|
||||
StreetName string `json:"street_name"`
|
||||
LocalName string `json:"local_name"`
|
||||
PostalCode string `json:"postal_code"`
|
||||
CountrySubdivisionName string `json:"country_subdivision_name"`
|
||||
CountryCodeIso3 string `json:"country_code_iso3"`
|
||||
}
|
||||
26
pkg/common/maps_history.go
Normal file
26
pkg/common/maps_history.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package common
|
||||
|
||||
type MapHistory struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Location *MapCoordinates `json:"location,omitempty"`
|
||||
}
|
||||
|
||||
type MapHistoryAdd struct {
|
||||
VIN string `json:"vin"`
|
||||
MapHistory MapHistory `json:"map_history"`
|
||||
}
|
||||
|
||||
type HMIMapHistoryGet struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
}
|
||||
|
||||
type HMIMapHistoryAdd struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
Search MapHistory `json:"search"`
|
||||
}
|
||||
|
||||
type HMIMapHistory struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
Searches []MapHistory `json:"searches"`
|
||||
}
|
||||
7
pkg/common/memory_region.go
Normal file
7
pkg/common/memory_region.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
type MemoryRegion struct {
|
||||
ID int64 `json:"-"`
|
||||
Offset uint64 `json:"offset"`
|
||||
Length uint64 `json:"length" validate:"gt=0"`
|
||||
}
|
||||
35
pkg/common/message.go
Normal file
35
pkg/common/message.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Message is the generic structure for messages sent over redis and kafka
|
||||
type Message struct {
|
||||
Handler string `json:"handler"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type MessageString struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// MessageRawJSON is Message (see above) but keeps payload as raw data
|
||||
type MessageRawJSON struct {
|
||||
Handler string `json:"handler"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Verify string `json:"verify,omitempty"`
|
||||
}
|
||||
|
||||
func (m *MessageRawJSON) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*m)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (m *MessageRawJSON) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, m)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
6
pkg/common/missing_flashpack.go
Normal file
6
pkg/common/missing_flashpack.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package common
|
||||
|
||||
type MissingFlashpack struct {
|
||||
VIN string
|
||||
FlashPackVersion string // Equivalent to OS Version
|
||||
}
|
||||
267
pkg/common/mobile_rx_test.go
Normal file
267
pkg/common/mobile_rx_test.go
Normal file
@@ -0,0 +1,267 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func TestMobileRXMessages(t *testing.T) {
|
||||
schema := "file://" + testhelper.GetSchemaDirPath() + "/mobile/RXMessage.json"
|
||||
now := time.Now()
|
||||
|
||||
percent, maxMiles, temp, chTime, stateOfCharge := 100, 350, -11, 43, 40
|
||||
typ := "AC"
|
||||
|
||||
tests := []testhelper.SchemaTest{
|
||||
{
|
||||
Name: "TestMobileCarLocations",
|
||||
Object: common.Message{
|
||||
Handler: "car_locations",
|
||||
Data: []common.JSONCarLocation{
|
||||
{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Altitude: 1,
|
||||
Latitude: 2,
|
||||
Longitude: 3,
|
||||
},
|
||||
{
|
||||
VIN: "1F15K3R45N3456789",
|
||||
Altitude: 100,
|
||||
Latitude: 200,
|
||||
Longitude: 300,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileDigitalTwin",
|
||||
Object: common.Message{
|
||||
Handler: "digital_twin",
|
||||
Data: common.JSONDigitalTwin{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Online: true,
|
||||
OnlineHMI: true,
|
||||
VehicleSpeed: &common.VehicleSpeed{
|
||||
Speed: 123.4,
|
||||
},
|
||||
Battery: &common.JSONBattery{
|
||||
Percent: &percent,
|
||||
MaxMiles: &maxMiles,
|
||||
AvgCellTemperature: &temp,
|
||||
ChargeType: &typ,
|
||||
RemainingChargingTime: &chTime,
|
||||
StateOfCharge: &stateOfCharge,
|
||||
},
|
||||
Doors: &common.Doors{
|
||||
LeftFront: true,
|
||||
LeftRear: true,
|
||||
RightFront: false,
|
||||
RightRear: false,
|
||||
},
|
||||
Location: &common.Location{
|
||||
Altitude: 1000,
|
||||
Longitude: 1,
|
||||
Latitude: -1,
|
||||
},
|
||||
Locks: &common.Locks{
|
||||
Driver: true,
|
||||
All: false,
|
||||
},
|
||||
UpdatedAt: &time.Time{},
|
||||
Windows: &common.JSONWindows{
|
||||
LeftFront: 0,
|
||||
LeftRear: 25,
|
||||
LeftRearQuarter: 100,
|
||||
RightFront: 50,
|
||||
RightRear: 75,
|
||||
RightRearQuarter: 100,
|
||||
RearWindshield: 100,
|
||||
Sunroof: 100,
|
||||
},
|
||||
ClimateControl: &common.JSONClimateControl{
|
||||
CabinTemperature: 70,
|
||||
RearDefrost: false,
|
||||
DriverSeatHeat: 0,
|
||||
PassengerSeatHeat: 0,
|
||||
SteeringWheelHeat: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileProfiles",
|
||||
Object: common.Message{
|
||||
Handler: "profiles",
|
||||
Data: []common.JSONMobileProfile{
|
||||
{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
DriverRole: "OWNER",
|
||||
Settings: []common.CarSetting{{
|
||||
Name: "TESTSETTING",
|
||||
Value: "TESTVALUE",
|
||||
Type: "string",
|
||||
DBModelBase: dbbasemodel.DBModelBase{
|
||||
UpdatedAt: &now,
|
||||
},
|
||||
}},
|
||||
Subscriptions: []common.Subscription{{
|
||||
Name: "test",
|
||||
Destination: "ICC",
|
||||
Expires: time.Date(2021, 10, 30, 8, 35, 0, 0, time.UTC),
|
||||
}},
|
||||
},
|
||||
{
|
||||
VIN: "1F15K3R45N3456789",
|
||||
DriverRole: "DRIVER",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileSettingsUpdate",
|
||||
Object: common.Message{
|
||||
Handler: "settings_update",
|
||||
Data: common.MobileSettingsUpdate{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Settings: []common.CarSetting{{
|
||||
Name: "TESTSETTING",
|
||||
Value: "TESTVALUE",
|
||||
Type: "string",
|
||||
DBModelBase: dbbasemodel.DBModelBase{
|
||||
UpdatedAt: &now,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileStoreInventory",
|
||||
Object: common.Message{
|
||||
Handler: "store_inventory",
|
||||
Data: []common.SubscriptionType{{
|
||||
ID: uuid.New(),
|
||||
Name: "TESTSUB",
|
||||
Destination: "ICC",
|
||||
Price: 100,
|
||||
Currency: "USD",
|
||||
Description: "this is a test",
|
||||
DurationValue: 5,
|
||||
DurationUnit: "Hours",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileSubscriptionsUpdate",
|
||||
Object: common.Message{
|
||||
Handler: "subscriptions_update",
|
||||
Data: common.MobileSubscriptionUpdate{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Subscriptions: []common.Subscription{{
|
||||
Name: "test",
|
||||
Destination: "ICC",
|
||||
Expires: time.Date(2021, 10, 30, 8, 35, 0, 0, time.UTC),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileStorePurchaseError",
|
||||
Object: common.Message{
|
||||
Handler: "store_purchase_error",
|
||||
Data: common.JSONError{
|
||||
Error: "test error",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileStoreInventoryError",
|
||||
Object: common.Message{
|
||||
Handler: "store_inventory_error",
|
||||
Data: common.JSONError{
|
||||
Error: "test error",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestUpdates",
|
||||
Object: common.Message{
|
||||
Handler: "updates",
|
||||
Data: []common.ApprovalUpdate{
|
||||
{
|
||||
ID: 1000,
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Name: "Update name",
|
||||
Version: "Update version",
|
||||
Description: "Update description",
|
||||
ReleaseNotes: "http://releasenotes.com",
|
||||
},
|
||||
{
|
||||
ID: 1001,
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Name: "Update name",
|
||||
Version: "Update version",
|
||||
Description: "Update description",
|
||||
ReleaseNotes: "http://releasenotes.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestPOIs",
|
||||
Object: common.Message{
|
||||
Handler: "user_pois",
|
||||
Data: []common.PointOfInterest{
|
||||
{
|
||||
Name: "TestWorkPOI",
|
||||
Location: common.POILocation{
|
||||
Latitude: 1.11,
|
||||
Longitude: 2.22,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHomePOI",
|
||||
Location: common.POILocation{
|
||||
Latitude: 8.88,
|
||||
Longitude: 9.99,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileError",
|
||||
Object: common.Message{
|
||||
Handler: "error",
|
||||
Data: common.MessageString{
|
||||
Message: "disconnected by duplicate ID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileManualIssue",
|
||||
Object: common.Message{
|
||||
Handler: "manual_issue",
|
||||
Data: common.AddIssueRequest{
|
||||
Issue: common.Issue{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Title: "HMI not working",
|
||||
Description: "Black screen on HMI",
|
||||
Timestamp: time.Now(),
|
||||
},
|
||||
Images: [][]byte{
|
||||
{72, 101, 108, 108, 111},
|
||||
{72, 101, 108, 108, 49},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testhelper.NewSchemaTestHelper(t, schema).
|
||||
RunSchemaTests(tests)
|
||||
}
|
||||
10
pkg/common/mobile_session.go
Normal file
10
pkg/common/mobile_session.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package common
|
||||
|
||||
type MobileSessionMessage struct {
|
||||
Handler string `json:"handler" validate:"required"`
|
||||
Data MobileSessionData `json:"data"`
|
||||
}
|
||||
|
||||
type MobileSessionData struct {
|
||||
Token string `json:"token" validate:"jwt"`
|
||||
}
|
||||
204
pkg/common/mobile_tx_test.go
Normal file
204
pkg/common/mobile_tx_test.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/common"
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
"github.com/google/uuid"
|
||||
"fiskerinc.com/modules/utils/elptr"
|
||||
)
|
||||
|
||||
func TestMobileTXMessages(t *testing.T) {
|
||||
schema := "file://" + testhelper.GetSchemaDirPath() + "/mobile/TXMessage.json"
|
||||
now := time.Now()
|
||||
|
||||
tests := []testhelper.SchemaTest{
|
||||
{
|
||||
Name: "TestTRexRemoteCommand",
|
||||
Object: common.Message{
|
||||
Handler: "remote_command",
|
||||
Data: common.RemoteCommandRequest{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
RemoteCommandSource: common.RemoteCommandSource{
|
||||
Command: "temp_cabin",
|
||||
Data: elptr.ElPtr("20"),
|
||||
Start: elptr.ElPtr(time.Date(
|
||||
2020, 1, 1,
|
||||
0, 0, 0, 0, time.UTC)),
|
||||
End: elptr.ElPtr(time.Date(
|
||||
2020, 1, 1,
|
||||
0, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileCarLocationsRequest",
|
||||
Object: common.Message{
|
||||
Handler: "car_locations",
|
||||
Data: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileDigitalTwinRequest",
|
||||
Object: common.Message{
|
||||
Handler: "digital_twin",
|
||||
Data: common.DigitalTwinRequest{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileMapDestinationRequest",
|
||||
Object: common.Message{
|
||||
Handler: "map_destination",
|
||||
Data: common.MapDestinationRequest{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Name: "Fisker Inc",
|
||||
Address: &common.TomTomAddress{
|
||||
StreetNumber: "3030",
|
||||
StreetName: "17th St",
|
||||
LocalName: "San Francisco",
|
||||
PostalCode: "94110",
|
||||
CountrySubdivisionName: "California",
|
||||
CountryCodeIso3: "USA",
|
||||
},
|
||||
Coordinates: common.MapCoordinates{
|
||||
Latitude: 37.0,
|
||||
Longitude: -122.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileMapRouteRequest",
|
||||
Object: common.Message{
|
||||
Handler: "map_route",
|
||||
Data: common.MapRouteRequest{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Waypoints: []common.MapWaypoint{
|
||||
{
|
||||
Type: "Restaurant",
|
||||
Title: "Test Restaurant",
|
||||
MapCoordinates: common.MapCoordinates{
|
||||
Latitude: 111,
|
||||
Longitude: 222,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "Park",
|
||||
Title: "Test Park",
|
||||
MapCoordinates: common.MapCoordinates{
|
||||
Latitude: 333.333,
|
||||
Longitude: 444.444,
|
||||
},
|
||||
},
|
||||
},
|
||||
Route: []common.MapCoordinates{
|
||||
{
|
||||
Latitude: 123,
|
||||
Longitude: 456,
|
||||
},
|
||||
{
|
||||
Latitude: 999,
|
||||
Longitude: 888,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileProfilesRequest",
|
||||
Object: common.Message{
|
||||
Handler: "profiles",
|
||||
Data: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileSettingsUpdate",
|
||||
Object: common.Message{
|
||||
Handler: "settings_update",
|
||||
Data: common.MobileSettingsUpdate{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Settings: []common.CarSetting{{
|
||||
Name: "TESTSETTING",
|
||||
Value: "TESTVALUE",
|
||||
Type: "string",
|
||||
DBModelBase: dbbasemodel.DBModelBase{
|
||||
UpdatedAt: &now,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestStoreInventoryRequest",
|
||||
Object: common.Message{
|
||||
Handler: "store_inventory",
|
||||
Data: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobileStorePurchase",
|
||||
Object: common.Message{
|
||||
Handler: "store_purchase",
|
||||
Data: common.StorePurchases{
|
||||
VIN: "1F15K3R45N1234567",
|
||||
Purchases: []common.StorePurchaseItem{{
|
||||
ID: uuid.New(),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIPOIs",
|
||||
Object: common.Message{
|
||||
Handler: "user_pois_get",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestHMIPOICreate",
|
||||
Object: common.Message{
|
||||
Handler: "user_poi_create",
|
||||
Data: common.PointOfInterest{
|
||||
Name: "TestPOI",
|
||||
Location: common.POILocation{
|
||||
Latitude: 1.23,
|
||||
Longitude: 4.56,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobilePOIEdit",
|
||||
Object: common.Message{
|
||||
Handler: "user_poi_edit",
|
||||
Data: common.MobilePOIEditMessage{
|
||||
OldName: "TestPOI",
|
||||
UserPOI: common.PointOfInterest{
|
||||
Name: "TestPOI",
|
||||
Location: common.POILocation{
|
||||
Latitude: 8.88,
|
||||
Longitude: 9.99,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestMobilePOIDelete",
|
||||
Object: common.Message{
|
||||
Handler: "user_poi_delete",
|
||||
Data: common.MobilePOIDeleteMessage{
|
||||
Name: "TestPOI",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testhelper.NewSchemaTestHelper(t, schema).
|
||||
RunSchemaTests(tests)
|
||||
}
|
||||
17
pkg/common/odx_vod_cds_request.go
Normal file
17
pkg/common/odx_vod_cds_request.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package common
|
||||
|
||||
type VODCDSRequest struct {
|
||||
ModelYear string `json:"modelyear"`
|
||||
VersionDModelYear string `json:"versionDModelYear"`
|
||||
FeatureCodes []string `json:"sapCodingStringArray"`
|
||||
PDXIndexVersion string `json:"pdXindexversion"`
|
||||
VersionInfo string `json:"versionInfo"` // Hex string representation of something "000000000000000000"
|
||||
ByteFiller string `json:"bytefield_2"` // Hex string filler to make VOD 255 bytes long "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
VehicleVIN string `json:"vehicleVIN"` // Human readable string for the vehicle's vin
|
||||
}
|
||||
|
||||
func NewVODCDSRequest() (srct VODCDSRequest) {
|
||||
srct.VersionInfo = "000000000000000000"
|
||||
srct.ByteFiller = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
return
|
||||
}
|
||||
231
pkg/common/order_updated.go
Normal file
231
pkg/common/order_updated.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"time"
|
||||
|
||||
"fiskerinc.com/modules/grpc/kafka_grpc"
|
||||
)
|
||||
|
||||
type OrderUpdated struct {
|
||||
XMLName xml.Name `xml:"VehicleOrderReplicate"`
|
||||
Header HeaderArea `xml:"HeaderArea" validate:"required"`
|
||||
VehicleOrder VehicleOrder `xml:"DataArea>VehicleOrder" validate:"required"`
|
||||
}
|
||||
|
||||
type HeaderArea struct {
|
||||
CreationDateTime CreationDateTime `xml:"CreationDateTime" json:"creation_date_time"`
|
||||
MessageIdentifier string `xml:"MessageIdentifier" json:"message_identifier"`
|
||||
InterfaceID int `xml:"InterfaceId" json:"interface_id"`
|
||||
SourceSystem string `xml:"Sender>SourceSystem" json:"source_system"`
|
||||
TargetSystem string `xml:"Sender>TargetSystem" json:"target_system"`
|
||||
}
|
||||
|
||||
type VehicleOrder struct {
|
||||
SpecID int `xml:"SpecId" json:"spec_id"`
|
||||
OrderNumber int `xml:"VehicleOrderNumber" json:"order_number"`
|
||||
MessageIdentifier string `xml:"-" json:"message_identifier"`
|
||||
VehicleSpecification VehicleSpecification `xml:"VehicleSpecification" json:"vehicle_specification"`
|
||||
}
|
||||
|
||||
type VehicleSpecification struct {
|
||||
OrderIndicator string `xml:"OrderIndicator" json:"order_indicator"`
|
||||
FleetOrderIndicator string `xml:"FleetOrderIndicator" json:"fleet_order_indicator"`
|
||||
ProductionPhaseIndicator string `xml:"ProductionPhaseIndicator" json:"production_phase_indicator"`
|
||||
VehicleIndicator string `xml:"VehicleIndicator" json:"vehicle_indicator"`
|
||||
ManufacturingPlant string `xml:"ManufacturingPlant" json:"manufacturing_plant"`
|
||||
ExpectedReferenceDate ExpectedReferenceDate `xml:"ExpectedReferenceDate" json:"expected_reference_date"`
|
||||
ModelType string `xml:"ModelType" json:"model_type"`
|
||||
ModelYearIndicator int `xml:"ModelYearIndicator" json:"model_year_indicator"`
|
||||
ModelYear int `xml:"ModelYear" json:"model_year"`
|
||||
SequenceNumber string `xml:"SequenceNumber" json:"sequence_number"`
|
||||
VersionDuringModelYear int `xml:"VersionDuringModelYear" json:"version_during_model_year"`
|
||||
VehicleModel string `xml:"VehicleModel" json:"vehicle_model"`
|
||||
VinPrefix string `xml:"VinPrefix" json:"vin_prefix"`
|
||||
VehicleFeatures []FeatureCodes `xml:"VehicleFeatures>FeatureCodes" json:"vehicle_features"`
|
||||
DestinationCountry string `xml:"DestinationCountry" json:"destination_country"`
|
||||
}
|
||||
|
||||
type FeatureCodes struct {
|
||||
FamilyCode string `xml:"FamilyCode" json:"family_code"`
|
||||
FeatureCode string `xml:"FeatureCode" json:"feature_code"`
|
||||
}
|
||||
|
||||
type ExpectedReferenceDate struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (t *ExpectedReferenceDate) UnmarshalText(data []byte) error {
|
||||
// Fractional seconds are handled implicitly by Parse.
|
||||
var err error
|
||||
t.Time, err = time.Parse("2006-01-02", string(data))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t ExpectedReferenceDate) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
s := t.Format("2006-01-02")
|
||||
return e.EncodeElement(s, start)
|
||||
}
|
||||
|
||||
type CreationDateTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (t *CreationDateTime) UnmarshalText(data []byte) error {
|
||||
// Fractional seconds are handled implicitly by Parse.
|
||||
var err error
|
||||
t.Time, err = time.Parse("2006-01-02T15:04", string(data))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t CreationDateTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
s := t.Format("2006-01-02T15:04")
|
||||
return e.EncodeElement(s, start)
|
||||
}
|
||||
|
||||
// OrderUpdatedSwag is used ONLY to represent OrderUpdated in swagger.
|
||||
type OrderUpdatedSwag struct {
|
||||
Header struct {
|
||||
CreationDateTime CreationDateTime `json:"CreationDateTime" swaggertype:"string"`
|
||||
MessageIdentifier string `json:"MessageIdentifier"`
|
||||
InterfaceID int `json:"InterfaceId"`
|
||||
Sender struct {
|
||||
SourceSystem string `json:"SourceSystem"`
|
||||
TargetSystem string `json:"TargetSystem"`
|
||||
} `json:"Sender"`
|
||||
} `json:"HeaderArea" validate:"required"`
|
||||
DataArea struct {
|
||||
VehicleOrder struct {
|
||||
SpecID int `json:"SpecId"`
|
||||
OrderNumber int `json:"VehicleOrderNumber"`
|
||||
VehicleSpecification struct {
|
||||
OrderIndicator string `json:"OrderIndicator"`
|
||||
FleetOrderIndicator string `json:"FleetOrderIndicator"`
|
||||
ProductionPhaseIndicator string `json:"ProductionPhaseIndicator"`
|
||||
VehicleIndicator string `json:"VehicleIndicator"`
|
||||
ManufacturingPlant string `json:"ManufacturingPlant"`
|
||||
ExpectedReferenceDate ExpectedReferenceDate `json:"ExpectedReferenceDate" swaggertype:"string"`
|
||||
ModelType string `json:"ModelType"`
|
||||
ModelYearIndicator int `json:"ModelYearIndicator"`
|
||||
SequenceNumber string `json:"SequenceNumber"`
|
||||
VehicleModel string `json:"VehicleModel"`
|
||||
VinPrefix string `json:"VinPrefix"`
|
||||
VehicleFeatures []struct {
|
||||
FeatureCodes struct {
|
||||
FamilyCode string `json:"FamilyCode" `
|
||||
FeatureCode string `json:"FeatureCode"`
|
||||
} `json:"FeatureCodes"`
|
||||
} `json:"VehicleFeatures"`
|
||||
DestinationCountry string `json:"DestinationCountry"`
|
||||
} `json:"VehicleSpecification"`
|
||||
} `json:"VehicleOrder" validate:"required"`
|
||||
} `json:"DataArea"`
|
||||
} // @name VehicleOrderReplicate
|
||||
|
||||
func OrderToGRPC(input MessageRawJSON) *kafka_grpc.GRPC_AttendantPayload {
|
||||
grpcPayload := &kafka_grpc.GRPC_AttendantPayload{
|
||||
Handler: input.Handler,
|
||||
}
|
||||
var order VehicleOrder
|
||||
err := json.Unmarshal(input.Data, &order)
|
||||
if err != nil {
|
||||
return grpcPayload
|
||||
}
|
||||
orderObj := &kafka_grpc.Order{
|
||||
VehicleSpecification: &kafka_grpc.VehicleSpecification{},
|
||||
}
|
||||
orderObj.VehicleSpecification.Date = order.VehicleSpecification.ExpectedReferenceDate.UnixMilli()
|
||||
orderObj.MsgIdentifier = order.MessageIdentifier
|
||||
orderObj.SpecId = int64(order.SpecID)
|
||||
orderObj.VehicleSpecification.OrderId = order.VehicleSpecification.OrderIndicator
|
||||
orderObj.VehicleSpecification.FOrderId = order.VehicleSpecification.FleetOrderIndicator
|
||||
orderObj.VehicleSpecification.MfPlant = order.VehicleSpecification.ManufacturingPlant
|
||||
orderObj.VehicleSpecification.DestCon = order.VehicleSpecification.DestinationCountry
|
||||
orderObj.VehicleSpecification.ModelType = order.VehicleSpecification.ModelType
|
||||
orderObj.VehicleSpecification.ProductId = order.VehicleSpecification.ProductionPhaseIndicator
|
||||
orderObj.VehicleSpecification.Sn = order.VehicleSpecification.SequenceNumber
|
||||
orderObj.VehicleSpecification.VehicleId = order.VehicleSpecification.VehicleIndicator
|
||||
orderObj.VehicleSpecification.Model = order.VehicleSpecification.VehicleModel
|
||||
orderObj.VehicleSpecification.VinPre = order.VehicleSpecification.VinPrefix
|
||||
orderObj.VehicleSpecification.ModelYear = int64(order.VehicleSpecification.ModelYear)
|
||||
orderObj.VehicleSpecification.ModelId = int64(order.VehicleSpecification.ModelYearIndicator)
|
||||
orderObj.VehicleSpecification.Version = int64(order.VehicleSpecification.VersionDuringModelYear)
|
||||
|
||||
if len(order.VehicleSpecification.VehicleFeatures) > 0 {
|
||||
var features []*kafka_grpc.FeatureCodes
|
||||
for _, feature := range order.VehicleSpecification.VehicleFeatures {
|
||||
features = append(features, &kafka_grpc.FeatureCodes{
|
||||
FamilyCode: feature.FamilyCode,
|
||||
FeatureCode: feature.FeatureCode,
|
||||
})
|
||||
}
|
||||
orderObj.VehicleSpecification.Feature = features
|
||||
}
|
||||
|
||||
payload := &kafka_grpc.GRPC_AttendantPayload_Order{
|
||||
Order: orderObj,
|
||||
}
|
||||
grpcPayload.Data = payload
|
||||
return grpcPayload
|
||||
}
|
||||
|
||||
func GRPCToOrder(payload *kafka_grpc.GRPC_AttendantPayload) *VehicleOrder {
|
||||
if payload.Data == nil {
|
||||
return nil
|
||||
}
|
||||
data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_Order)
|
||||
order := &VehicleOrder{}
|
||||
order.OrderNumber = int(data.Order.OrderNo)
|
||||
order.MessageIdentifier = data.Order.MsgIdentifier
|
||||
order.SpecID = int(data.Order.SpecId)
|
||||
order.VehicleSpecification = VehicleSpecification{}
|
||||
if data.Order.VehicleSpecification != nil {
|
||||
order.VehicleSpecification.DestinationCountry = data.Order.VehicleSpecification.DestCon
|
||||
order.VehicleSpecification.FleetOrderIndicator = data.Order.VehicleSpecification.FOrderId
|
||||
order.VehicleSpecification.FleetOrderIndicator = data.Order.VehicleSpecification.FOrderId
|
||||
|
||||
order.VehicleSpecification.ManufacturingPlant = data.Order.VehicleSpecification.MfPlant
|
||||
|
||||
order.VehicleSpecification.ModelType = data.Order.VehicleSpecification.ModelType
|
||||
|
||||
order.VehicleSpecification.ModelYear = int(data.Order.VehicleSpecification.ModelYear)
|
||||
|
||||
order.VehicleSpecification.ModelYearIndicator = int(data.Order.VehicleSpecification.ModelId)
|
||||
|
||||
order.VehicleSpecification.OrderIndicator = data.Order.VehicleSpecification.OrderId
|
||||
|
||||
order.VehicleSpecification.ProductionPhaseIndicator = data.Order.VehicleSpecification.ProductId
|
||||
|
||||
order.VehicleSpecification.FleetOrderIndicator = data.Order.VehicleSpecification.FOrderId
|
||||
|
||||
order.VehicleSpecification.SequenceNumber = data.Order.VehicleSpecification.Sn
|
||||
|
||||
order.VehicleSpecification.VehicleIndicator = data.Order.VehicleSpecification.VehicleId
|
||||
|
||||
order.VehicleSpecification.VehicleModel = data.Order.VehicleSpecification.Model
|
||||
|
||||
order.VehicleSpecification.VinPrefix = data.Order.VehicleSpecification.VinPre
|
||||
|
||||
order.VehicleSpecification.VersionDuringModelYear = int(data.Order.VehicleSpecification.Version)
|
||||
|
||||
seconds := data.Order.VehicleSpecification.Date / 1000
|
||||
nanoseconds := (data.Order.VehicleSpecification.Date % 1000) * int64(time.Millisecond)
|
||||
order.VehicleSpecification.ExpectedReferenceDate = ExpectedReferenceDate{time.Unix(seconds, nanoseconds)}
|
||||
|
||||
if data.Order.VehicleSpecification.Feature != nil && len(data.Order.VehicleSpecification.Feature) > 0 {
|
||||
var features []FeatureCodes
|
||||
for _, feature := range data.Order.VehicleSpecification.Feature {
|
||||
features = append(features, FeatureCodes{
|
||||
FamilyCode: feature.FamilyCode,
|
||||
FeatureCode: feature.FeatureCode,
|
||||
})
|
||||
}
|
||||
order.VehicleSpecification.VehicleFeatures = features
|
||||
}
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
8
pkg/common/ota_install.go
Normal file
8
pkg/common/ota_install.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
type MobileOtaInstall struct {
|
||||
Time *time.Time `json:"time,omitempty"`
|
||||
CarUpdate int64 `json:"id" validate:"required"`
|
||||
}
|
||||
23
pkg/common/payload.go
Normal file
23
pkg/common/payload.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Payload is the generic structure for payloads sent within Kafka messages
|
||||
type Payload struct {
|
||||
Handler string `json:"handler"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
func (p *Payload) Marshal() ([]byte, error) {
|
||||
data, err := json.Marshal(*p)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (p *Payload) Unmarshal(data []byte) error {
|
||||
err := json.Unmarshal(data, p)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
49
pkg/common/points_of_interest.go
Normal file
49
pkg/common/points_of_interest.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package common
|
||||
|
||||
type PointOfInterest struct {
|
||||
Name string `json:"name"`
|
||||
Location POILocation `json:"location"`
|
||||
}
|
||||
|
||||
type POILocation struct {
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
}
|
||||
|
||||
type MobilePOIEditMessage struct {
|
||||
OldName string `json:"old_name"`
|
||||
UserPOI PointOfInterest `json:"user_poi"`
|
||||
}
|
||||
|
||||
type MobilePOIDeleteMessage struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type MobilePOIsMessage struct {
|
||||
UserPOI []PointOfInterest `json:"user_pois"`
|
||||
}
|
||||
|
||||
type HMIPOIRequestMessage struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
}
|
||||
|
||||
type HMIPOIMessage struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
UserPOI PointOfInterest `json:"user_poi"`
|
||||
}
|
||||
|
||||
type HMIPOIEditMessage struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
OldName string `json:"old_name"`
|
||||
UserPOI PointOfInterest `json:"user_poi"`
|
||||
}
|
||||
|
||||
type HMIPOIDeleteMessage struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type HMIPOIsMessage struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
UserPOIs []PointOfInterest `json:"user_pois"`
|
||||
}
|
||||
17
pkg/common/product.go
Normal file
17
pkg/common/product.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
type Product struct {
|
||||
ID string `pg:",pk,unique" json:"id" validate:"required,max=256"`
|
||||
Name string `json:"name"`
|
||||
Price int `json:"price"`
|
||||
ReservationPrice int `json:"reservation_price"`
|
||||
MonthlyPaymentPrice int `json:"monthlypayment_price"`
|
||||
Currency string `json:"currency"`
|
||||
Locale string `json:"locale"`
|
||||
Code string `json:"code"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
6
pkg/common/product_search.go
Normal file
6
pkg/common/product_search.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package common
|
||||
|
||||
type ProductSearch struct {
|
||||
Search string `json:"search" validate:"max=1024"`
|
||||
Product
|
||||
}
|
||||
5
pkg/common/profile_photo.go
Normal file
5
pkg/common/profile_photo.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package common
|
||||
|
||||
type ProfilePhotoUpdate struct {
|
||||
Photo string `json:"photo" validate:"base64,max=50000"`
|
||||
}
|
||||
90
pkg/common/profiles.go
Normal file
90
pkg/common/profiles.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package common
|
||||
|
||||
type JSONMobileProfile struct {
|
||||
VIN string `json:"vin"`
|
||||
DriverRole string `json:"role"`
|
||||
Consent bool `json:"consent,omitempty"`
|
||||
BLEKey string `json:"ble_key,omitempty"`
|
||||
Settings []CarSetting `json:"settings"`
|
||||
Subscriptions []Subscription `json:"subscriptions"`
|
||||
}
|
||||
|
||||
func (p *JSONMobileProfile) Populate(driver CarToDriver) {
|
||||
p.VIN = driver.VIN
|
||||
p.DriverRole = driver.DriverRole
|
||||
p.BLEKey = driver.BLEKey
|
||||
|
||||
if driver.Settings == nil {
|
||||
p.Settings = make([]CarSetting, 0)
|
||||
} else {
|
||||
p.Settings = driver.Settings
|
||||
for i := range p.Settings {
|
||||
p.Settings[i].ClearCreatedAt()
|
||||
}
|
||||
}
|
||||
|
||||
if driver.Subscriptions == nil {
|
||||
p.Subscriptions = make([]Subscription, 0)
|
||||
} else {
|
||||
p.Subscriptions = driver.Subscriptions
|
||||
for i := range p.Subscriptions {
|
||||
p.Subscriptions[i].ClearDates()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type JSONHMIProfile struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
DriverRole string `json:"role"`
|
||||
User UserProfile `json:"user"`
|
||||
Settings []CarSetting `json:"settings"`
|
||||
Subscriptions []Subscription `json:"subscriptions"`
|
||||
}
|
||||
|
||||
func (p *JSONHMIProfile) Populate(user JSONUserProfile, driver CarToDriver) {
|
||||
p.User = UserProfile{
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
p.DriverID = driver.DriverID
|
||||
p.DriverRole = driver.DriverRole
|
||||
|
||||
if driver.Settings == nil {
|
||||
p.Settings = make([]CarSetting, 0)
|
||||
} else {
|
||||
p.Settings = driver.Settings
|
||||
for i := range p.Settings {
|
||||
p.Settings[i].ClearCreatedAt()
|
||||
}
|
||||
}
|
||||
|
||||
if driver.Subscriptions == nil {
|
||||
p.Subscriptions = make([]Subscription, 0)
|
||||
} else {
|
||||
p.Subscriptions = driver.Subscriptions
|
||||
for i := range p.Subscriptions {
|
||||
p.Subscriptions[i].ClearDates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type JSONHMIUpdateProfile struct {
|
||||
DriverID string `json:"driver_id"`
|
||||
DriverRole string `json:"role"`
|
||||
User UserProfile `json:"user"`
|
||||
}
|
||||
|
||||
type JSONHMIDeleteProfile struct {
|
||||
DriverID string `json:"driver_id" validate:"required,max=100"`
|
||||
}
|
||||
|
||||
type JSONUserProfile struct {
|
||||
UserName string `json:"username"`
|
||||
FirstName string `json:"firstname"`
|
||||
LastName string `json:"lastname"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phonenumber"`
|
||||
}
|
||||
15
pkg/common/rate_plan_mobile.go
Normal file
15
pkg/common/rate_plan_mobile.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
type RatePlanTMobile struct {
|
||||
tableName struct{} `pg:"rate_plan_tmobile,alias:r"`
|
||||
|
||||
Country string `json:"country" pg:"country,pk"`
|
||||
ProductID string `json:"product_id" pg:"product_id"`
|
||||
PlanName string `json:"plan_name" pg:"plan_name"`
|
||||
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
79
pkg/common/remote_command.go
Normal file
79
pkg/common/remote_command.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
PositionLeftFront string = "left_front"
|
||||
PositionRightFront string = "right_front"
|
||||
PositionLeftRear string = "left_rear"
|
||||
PositionRightRear string = "right_rear"
|
||||
PositionLeftRearQuarter string = "left_rear_quarter"
|
||||
PositionRightRearQuarter string = "right_rear_quarter"
|
||||
PositionRearWindshield string = "rear_windshield"
|
||||
PositionTrunk string = "trunk"
|
||||
)
|
||||
|
||||
type RemoteCommandSource struct {
|
||||
Command string `json:"command" validate:"required,max=100"`
|
||||
Data *string `json:"data,omitempty"`
|
||||
Start *time.Time `json:"start,omitempty"`
|
||||
End *time.Time `json:"end,omitempty"`
|
||||
}
|
||||
|
||||
type RemoteReadVersionsCommandArgs struct {
|
||||
ECUName string `json:"ecu_name" validate:"required,max=100"`
|
||||
}
|
||||
|
||||
type RemoteResetDiagnosticCommandArgs struct {
|
||||
ECUName string `json:"ecu_name" validate:"required,max=100"`
|
||||
UDSKeys *ECCKeys `json:"uds_keys,omitempty"`
|
||||
}
|
||||
|
||||
type RemoteCANNetworkCommandArgs struct {
|
||||
Action string `json:"action" validate:"required,oneof=on off"`
|
||||
Timeout int32 `json:"timeout"`
|
||||
}
|
||||
|
||||
type RemoteIgnitionCommandArgs struct {
|
||||
Action string `json:"action" validate:"required,oneof=on off"`
|
||||
Timeout int32 `json:"timeout"`
|
||||
}
|
||||
|
||||
type RemoteUpdateSecOCCommandArgs struct {
|
||||
ECUs []string `json:"ecus" validate:"required"`
|
||||
UDSKeys []ECCKeys `json:"uds_keys" validate:"required"`
|
||||
KeyBase64 string `json:"key_base64" validate:"required"`
|
||||
}
|
||||
|
||||
type RemoteDiagnosticCommandRequest struct {
|
||||
VINs []string `json:"vins,omitempty" validate:"required,max=1000,dive,vin"`
|
||||
Command string `json:"command" validate:"required,oneof=remote_reset can_network remote_ignition read_ecu_versions write_secoc_key"`
|
||||
ECU string `json:"ecu_name,omitempty"`
|
||||
CANNetAction string `json:"can_net_action,omitempty"`
|
||||
IgnitionAction string `json:"ignition_action,omitempty"`
|
||||
Timeout int32 `json:"timeout"`
|
||||
}
|
||||
type RemoteCommandRequest struct {
|
||||
VIN string `json:"vin"`
|
||||
Source string `json:"source"`
|
||||
SentAt *time.Time `json:"time,omitempty"`
|
||||
WaitDuration int `json:"wait_dur"`
|
||||
RemoteCommandSource
|
||||
}
|
||||
|
||||
func (r *RemoteCommandRequest) IsExpired(commandExpiration time.Duration) bool {
|
||||
if r.SentAt == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
expiresAt := r.SentAt.Add(commandExpiration * time.Second)
|
||||
return expiresAt.Before(time.Now())
|
||||
}
|
||||
|
||||
func (r *RemoteCommandRequest) GetSentAt() *time.Time {
|
||||
return r.SentAt
|
||||
}
|
||||
|
||||
func (r *RemoteCommandRequest) SetSentAt(sentAt time.Time) {
|
||||
r.SentAt = &sentAt
|
||||
}
|
||||
18
pkg/common/sap.go
Normal file
18
pkg/common/sap.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package common
|
||||
|
||||
type SAPResponse struct {
|
||||
ModelYear int `json:"modelYear"`
|
||||
VersionDuringModelYear string `json:"versionDuringModelYear"`
|
||||
ModelType string `json:"modelType"`
|
||||
Features []SAPFeature `json:"features"`
|
||||
}
|
||||
|
||||
type SAPFeature struct {
|
||||
FamilyCode string `json:"familyCode"`
|
||||
FeatureCode string `json:"featureCode"`
|
||||
}
|
||||
|
||||
type CarConfigData struct {
|
||||
Vin string `json:"vin"`
|
||||
ConfigData string `json:"config_data"`
|
||||
}
|
||||
11
pkg/common/signed_image.go
Normal file
11
pkg/common/signed_image.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fiskerinc.com/modules/common/dbbasemodel"
|
||||
)
|
||||
|
||||
type SignedImage struct {
|
||||
Signature *BinaryHex `json:"signature" pg:"signature,pk,type:bytea" swaggertype:"string" format:"hex" example:"ecba97e8c5a064e333ae35728f2147f41e64734f381826f55d1a9261962d704e"`
|
||||
Email string `json:"email,omitempty" pg:"email"`
|
||||
dbbasemodel.DBModelBase
|
||||
}
|
||||
8
pkg/common/signing_cert_algohash_types.go
Normal file
8
pkg/common/signing_cert_algohash_types.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package common
|
||||
|
||||
type AlgoHash string
|
||||
|
||||
const (
|
||||
AlogHashNone AlgoHash = "none"
|
||||
AlgoHashSHA256 AlgoHash = "sha256"
|
||||
)
|
||||
15
pkg/common/signing_cert_algosign_types.go
Normal file
15
pkg/common/signing_cert_algosign_types.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package common
|
||||
|
||||
type AlgoSignType string
|
||||
|
||||
const (
|
||||
AlgoSignRSAX509Raw AlgoSignType = "raw"
|
||||
AlgoSignRSAPkcs1 AlgoSignType = "pkcs1"
|
||||
AlgoSignECDSA AlgoSignType = "prime256v1"
|
||||
)
|
||||
|
||||
var AlgoMapper map[AlgoSignType]int = map[AlgoSignType]int{
|
||||
AlgoSignRSAX509Raw: 0,
|
||||
AlgoSignRSAPkcs1: 1,
|
||||
AlgoSignECDSA: 2,
|
||||
}
|
||||
10
pkg/common/signing_cert_key_types.go
Normal file
10
pkg/common/signing_cert_key_types.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package common
|
||||
|
||||
type KeyType string
|
||||
|
||||
const (
|
||||
KeySBC4096 KeyType = "sbc_key_4096"
|
||||
KeyVerified4096 KeyType = "verified_rsa4096_key"
|
||||
KeySBCRoot KeyType = "sbc_root_key"
|
||||
KeyBundle KeyType = "bundle_key"
|
||||
)
|
||||
9
pkg/common/signing_cert_request.go
Normal file
9
pkg/common/signing_cert_request.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package common
|
||||
|
||||
type JSONRPCSignImageRequest struct {
|
||||
Supplier string `validate:"required"`
|
||||
KeyCert KeyType `validate:"required,oneof=bundle_key sbc_key_4096 sbc_root_key verified_rsa4096_key"`
|
||||
AlgoHash AlgoHash `validate:"required,oneof=none sha256"`
|
||||
AlgoSign AlgoSignType `validate:"required,oneof=pkcs1 prime256v1 raw"`
|
||||
Data []byte `validate:"required"`
|
||||
}
|
||||
33
pkg/common/sold_status.go
Normal file
33
pkg/common/sold_status.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package common
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type SoldStatusDataRequest struct {
|
||||
XMLName xml.Name `xml:"VehicleSoldStatusDataRequestReplicate"`
|
||||
Header HeaderArea `xml:"HeaderArea" validate:"required"`
|
||||
SoldStatusData SoldStatusData `xml:"DataArea>SoldStatusData" validate:"required"`
|
||||
}
|
||||
|
||||
type SoldStatusData struct {
|
||||
VIN string `xml:"VIN"`
|
||||
SoldStatus string `xml:"SoldStatus"`
|
||||
}
|
||||
|
||||
// SoldStatusDataRequestSwag is used ONLY to represent SoldStatusDataRequest in swagger.
|
||||
type SoldStatusDataRequestSwag struct {
|
||||
Header struct {
|
||||
CreationDateTime CreationDateTime `json:"CreationDateTime" swaggertype:"string"`
|
||||
MessageIdentifier string `json:"MessageIdentifier"`
|
||||
InterfaceID int `json:"InterfaceId"`
|
||||
Sender struct {
|
||||
SourceSystem string `json:"SourceSystem"`
|
||||
TargetSystem string `json:"TargetSystem"`
|
||||
} `json:"Sender"`
|
||||
} `json:"HeaderArea" validate:"required"`
|
||||
DataArea struct {
|
||||
SoldStatusData struct {
|
||||
VIN string `json:"vin"`
|
||||
SoldStatus string `json:"sold_status"`
|
||||
} `json:"SoldStatusData" validate:"required"`
|
||||
} `json:"DataArea"`
|
||||
} // @name VehicleSoldStatusDataRequestReplicate
|
||||
3
pkg/common/staticerrors/README.md
Normal file
3
pkg/common/staticerrors/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
A place to store any static errors we are creating
|
||||
This will allow us to specifically handle them inside the logger without import cycles
|
||||
Maybe it will also let us keep track of our error messaging better?
|
||||
3
pkg/common/staticerrors/sap.go
Normal file
3
pkg/common/staticerrors/sap.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package staticerrors
|
||||
|
||||
var ErrorSAPFailedCall = "calling SAP failed"
|
||||
11
pkg/common/status_manifest.go
Normal file
11
pkg/common/status_manifest.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
type StatusManifest struct {
|
||||
tableName struct{} `pg:"update_manifests,discard_unknown_columns"`
|
||||
UpdateManifest
|
||||
Status string `json:"status,omitempty" pg:"status"`
|
||||
StatusUpdatedAt *time.Time `json:"status_updated,omitempty" pg:"status_updated"`
|
||||
ManifestCreatedAt *time.Time `json:"manifest_created,omitempty" pg:"manifest_created"`
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user