Refactor kafka to pure Go (franz-go), fix DBC stubs, update Dockerfile

This commit is contained in:
Chris Rai
2026-01-31 00:05:47 -05:00
parent fbb820d7b3
commit b5bec57dfa
776 changed files with 18945 additions and 2052 deletions

View File

@@ -0,0 +1,231 @@
package generate
import (
"crypto/sha256"
"fmt"
"regexp"
"sort"
"time"
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/dbc"
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
)
type CompileResult struct {
Hash string
Database *descriptor.Database
Warnings []error
}
func Compile(sourceFile string, data []byte) (result *CompileResult, err error) {
p := dbc.NewParser(sourceFile, data)
if err := p.Parse(); err != nil {
return nil, fmt.Errorf("failed to parse DBC source file: %w", err)
}
defs := p.Defs()
c := &compiler{
db: &descriptor.Database{SourceFile: sourceFile, ECUs: make(map[string]bool)},
defs: defs,
}
c.collectDescriptors()
c.addMetadata()
c.sortDescriptors()
hash := ComputeHash(data)
return &CompileResult{Hash: hash, Database: c.db, Warnings: c.warnings}, nil
}
type compileError struct {
def dbc.Def
reason string
}
func (e *compileError) Error() string {
return fmt.Sprintf("failed to compile: %v (%v)", e.reason, e.def)
}
type compiler struct {
db *descriptor.Database
defs []dbc.Def
warnings []error
}
func (c *compiler) addWarning(warning error) {
c.warnings = append(c.warnings, warning)
}
func (c *compiler) collectDescriptors() {
// find ECU names from messages
re := regexp.MustCompile(`^[0-9A-Za-z]+`)
for _, def := range c.defs {
switch def := def.(type) {
case *dbc.VersionDef:
c.db.Version = def.Version
case *dbc.MessageDef:
if def.MessageID == dbc.IndependentSignalsMessageID {
continue // don't compile
}
// add ECU names to set
ecu := re.FindString(string(def.Name))
if ecu != "" {
c.db.ECUs[ecu] = true
}
message := &descriptor.Message{
Name: string(def.Name),
ID: def.MessageID.ToCAN(),
IsExtended: def.MessageID.IsExtended(),
Length: uint16(def.Size),
SenderNode: string(def.Transmitter),
}
for _, signalDef := range def.Signals {
signal := &descriptor.Signal{
Name: string(signalDef.Name),
IsBigEndian: signalDef.IsBigEndian,
IsSigned: signalDef.IsSigned,
IsMultiplexer: signalDef.IsMultiplexerSwitch,
IsMultiplexed: signalDef.IsMultiplexed,
MultiplexerValue: uint(signalDef.MultiplexerSwitch),
Start: uint16(signalDef.StartBit),
Length: uint16(signalDef.Size),
Scale: signalDef.Factor,
Offset: signalDef.Offset,
Min: signalDef.Minimum,
Max: signalDef.Maximum,
Unit: signalDef.Unit,
}
for _, receiver := range signalDef.Receivers {
signal.ReceiverNodes = append(signal.ReceiverNodes, string(receiver))
}
message.Signals = append(message.Signals, signal)
}
c.db.Messages[message.ID] = message
case *dbc.NodesDef:
for _, node := range def.NodeNames {
c.db.Nodes = append(c.db.Nodes, &descriptor.Node{Name: string(node)})
}
}
}
}
func (c *compiler) addMetadata() {
for _, def := range c.defs {
switch def := def.(type) {
case *dbc.CommentDef:
switch def.ObjectType {
case dbc.ObjectTypeMessage:
if def.MessageID == dbc.IndependentSignalsMessageID {
continue // don't compile
}
message, ok := c.db.Message(def.MessageID.ToCAN())
if !ok {
c.addWarning(&compileError{def: def, reason: "no declared message"})
continue
}
message.Description = def.Comment
case dbc.ObjectTypeSignal:
if def.MessageID == dbc.IndependentSignalsMessageID {
continue // don't compile
}
signal, ok := c.db.Signal(def.MessageID.ToCAN(), string(def.SignalName))
if !ok {
c.addWarning(&compileError{def: def, reason: "no declared signal"})
continue
}
signal.Description = def.Comment
case dbc.ObjectTypeNetworkNode:
node, ok := c.db.Node(string(def.NodeName))
if !ok {
c.addWarning(&compileError{def: def, reason: "no declared node"})
continue
}
node.Description = def.Comment
}
case *dbc.ValueDescriptionsDef:
if def.MessageID == dbc.IndependentSignalsMessageID {
continue // don't compile
}
if def.ObjectType != dbc.ObjectTypeSignal {
continue // don't compile
}
signal, ok := c.db.Signal(def.MessageID.ToCAN(), string(def.SignalName))
if !ok {
c.addWarning(&compileError{def: def, reason: "no declared signal"})
continue
}
for _, valueDescription := range def.ValueDescriptions {
signal.ValueDescriptions = append(signal.ValueDescriptions, &descriptor.ValueDescription{
Description: valueDescription.Description,
Value: int(valueDescription.Value),
})
}
case *dbc.AttributeValueForObjectDef:
switch def.ObjectType {
case dbc.ObjectTypeMessage:
msg, ok := c.db.Message(def.MessageID.ToCAN())
if !ok {
c.addWarning(&compileError{def: def, reason: "no declared message"})
continue
}
switch def.AttributeName {
case "GenMsgSendType":
if err := msg.SendType.UnmarshalString(def.StringValue); err != nil {
c.addWarning(&compileError{def: def, reason: err.Error()})
continue
}
case "GenMsgCycleTime":
msg.CycleTime = time.Duration(def.IntValue) * time.Millisecond
case "GenMsgDelayTime":
msg.DelayTime = time.Duration(def.IntValue) * time.Millisecond
}
case dbc.ObjectTypeSignal:
sig, ok := c.db.Signal(def.MessageID.ToCAN(), string(def.SignalName))
if !ok {
c.addWarning(&compileError{def: def, reason: "no declared signal"})
}
if def.AttributeName == "GenSigStartValue" {
sig.DefaultValue = int(def.IntValue)
}
}
}
}
}
func (c *compiler) sortDescriptors() {
// Sort nodes by name
sort.Slice(c.db.Nodes, func(i, j int) bool {
return c.db.Nodes[i].Name < c.db.Nodes[j].Name
})
// Sort messages by ID
// sort.Slice(c.db.Messages, func(i, j int) bool {
// return c.db.Messages[i].ID < c.db.Messages[j].ID
// })
for _, m := range c.db.Messages {
if m == nil {
continue
}
m := m
// Sort signals by start (and multiplexer value)
sort.Slice(m.Signals, func(j, k int) bool {
if m.Signals[j].MultiplexerValue < m.Signals[k].MultiplexerValue {
return true
}
return m.Signals[j].Start < m.Signals[k].Start
})
// Sort value descriptions by value
for _, s := range m.Signals {
s := s
sort.Slice(s.ValueDescriptions, func(k, l int) bool {
return s.ValueDescriptions[k].Value < s.ValueDescriptions[l].Value
})
}
}
}
func ComputeHash(input []byte) string {
h := sha256.New()
h.Write(input)
return fmt.Sprintf("%x", h.Sum(nil))
}

View File

@@ -0,0 +1,306 @@
package generate
import (
"io/ioutil"
"testing"
"time"
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
examplecan "github.com/fiskerinc/cloud-services/pkg/can-go/testdata/gen/go/example"
"gotest.tools/v3/assert"
)
func TestCompile_ExampleDBC(t *testing.T) {
finish := runTestInDir(t, "../..")
defer finish()
const exampleDBCFile = "testdata/dbc/example/example.dbc"
exampleDatabase := &descriptor.Database{
SourceFile: exampleDBCFile,
Version: "",
Nodes: []*descriptor.Node{
{
Name: "DBG",
},
{
Name: "DRIVER",
Description: "The driver controller driving the car",
},
{
Name: "IO",
},
{
Name: "MOTOR",
Description: "The motor controller of the car",
},
{
Name: "SENSOR",
Description: "The sensor controller of the car",
},
},
}
exampleDatabase.Messages[1] = &descriptor.Message{
ID: 1,
Name: "EmptyMessage",
SenderNode: "DBG",
}
exampleDatabase.Messages[100] = &descriptor.Message{
ID: 100,
Name: "DriverHeartbeat",
Length: 1,
SenderNode: "DRIVER",
Description: "Sync message used to synchronize the controllers",
SendType: descriptor.SendTypeCyclic,
CycleTime: time.Second,
Signals: []*descriptor.Signal{
{
Name: "Command",
Start: 0,
Length: 8,
Scale: 1,
ReceiverNodes: []string{"SENSOR", "MOTOR"},
ValueDescriptions: []*descriptor.ValueDescription{
{Value: 0, Description: "None"},
{Value: 1, Description: "Sync"},
{Value: 2, Description: "Reboot"},
},
},
},
}
exampleDatabase.Messages[101] = &descriptor.Message{
ID: 101,
Name: "MotorCommand",
Length: 1,
SenderNode: "DRIVER",
SendType: descriptor.SendTypeCyclic,
CycleTime: 100 * time.Millisecond,
Signals: []*descriptor.Signal{
{
Name: "Steer",
Start: 0,
Length: 4,
IsSigned: true,
Scale: 1,
Offset: -5,
Min: -5,
Max: 5,
ReceiverNodes: []string{"MOTOR"},
},
{
Name: "Drive",
Start: 4,
Length: 4,
Scale: 1,
Max: 9,
ReceiverNodes: []string{"MOTOR"},
},
},
}
exampleDatabase.Messages[200] = &descriptor.Message{
ID: 200,
Name: "SensorSonars",
Length: 8,
SenderNode: "SENSOR",
SendType: descriptor.SendTypeCyclic,
CycleTime: 100 * time.Millisecond,
Signals: []*descriptor.Signal{
{
Name: "Mux",
IsMultiplexer: true,
Start: 0,
Length: 4,
Scale: 1,
ReceiverNodes: []string{"DRIVER", "IO"},
},
{
Name: "ErrCount",
Start: 4,
Length: 12,
Scale: 1,
ReceiverNodes: []string{"DRIVER", "IO"},
},
{
Name: "Left",
IsMultiplexed: true,
MultiplexerValue: 0,
Start: 16,
Length: 12,
Scale: 0.1,
ReceiverNodes: []string{"DRIVER", "IO"},
},
{
Name: "NoFiltLeft",
IsMultiplexed: true,
MultiplexerValue: 1,
Start: 16,
Length: 12,
Scale: 0.1,
ReceiverNodes: []string{"DBG"},
},
{
Name: "Middle",
IsMultiplexed: true,
MultiplexerValue: 0,
Start: 28,
Length: 12,
Scale: 0.1,
ReceiverNodes: []string{"DRIVER", "IO"},
},
{
Name: "NoFiltMiddle",
IsMultiplexed: true,
MultiplexerValue: 1,
Start: 28,
Length: 12,
Scale: 0.1,
ReceiverNodes: []string{"DBG"},
},
{
Name: "Right",
IsMultiplexed: true,
MultiplexerValue: 0,
Start: 40,
Length: 12,
Scale: 0.1,
ReceiverNodes: []string{"DRIVER", "IO"},
},
{
Name: "NoFiltRight",
IsMultiplexed: true,
MultiplexerValue: 1,
Start: 40,
Length: 12,
Scale: 0.1,
ReceiverNodes: []string{"DBG"},
},
{
Name: "Rear",
IsMultiplexed: true,
MultiplexerValue: 0,
Start: 52,
Length: 12,
Scale: 0.1,
ReceiverNodes: []string{"DRIVER", "IO"},
},
{
Name: "NoFiltRear",
IsMultiplexed: true,
MultiplexerValue: 1,
Start: 52,
Length: 12,
Scale: 0.1,
ReceiverNodes: []string{"DBG"},
},
},
}
exampleDatabase.Messages[400] = &descriptor.Message{
ID: 400,
Name: "MotorStatus",
Length: 3,
SenderNode: "MOTOR",
SendType: descriptor.SendTypeCyclic,
CycleTime: 100 * time.Millisecond,
Signals: []*descriptor.Signal{
{
Name: "WheelError",
Start: 0,
Length: 1,
Scale: 1,
ReceiverNodes: []string{"DRIVER", "IO"},
},
{
Name: "SpeedKph",
Start: 8,
Length: 16,
Scale: 0.001,
Unit: "km/h",
ReceiverNodes: []string{"DRIVER", "IO"},
},
},
}
exampleDatabase.Messages[500] = &descriptor.Message{
ID: 500,
Name: "IODebug",
Length: 6,
SenderNode: "IO",
SendType: descriptor.SendTypeEvent,
Signals: []*descriptor.Signal{
{
Name: "TestUnsigned",
Start: 0,
Length: 8,
Scale: 1,
ReceiverNodes: []string{"DBG"},
},
{
Name: "TestEnum",
Start: 8,
Length: 6,
Scale: 1,
ReceiverNodes: []string{"DBG"},
DefaultValue: int(examplecan.IODebug_TestEnum_Two),
ValueDescriptions: []*descriptor.ValueDescription{
{Value: 1, Description: "One"},
{Value: 2, Description: "Two"},
},
},
{
Name: "TestSigned",
Start: 16,
Length: 8,
IsSigned: true,
Scale: 1,
ReceiverNodes: []string{"DBG"},
},
{
Name: "TestFloat",
Start: 24,
Length: 8,
Scale: 0.5,
ReceiverNodes: []string{"DBG"},
},
{
Name: "TestBoolEnum",
Start: 32,
Length: 1,
Scale: 1,
ReceiverNodes: []string{"DBG"},
ValueDescriptions: []*descriptor.ValueDescription{
{Value: 0, Description: "Zero"},
{Value: 1, Description: "One"},
},
},
{
Name: "TestScaledEnum",
Start: 40,
Length: 2,
Scale: 2,
Min: 0,
Max: 6,
ReceiverNodes: []string{"DBG"},
ValueDescriptions: []*descriptor.ValueDescription{
{Value: 0, Description: "Zero"},
{Value: 1, Description: "Two"},
{Value: 2, Description: "Four"},
{Value: 3, Description: "Six"},
},
},
},
}
input, err := ioutil.ReadFile(exampleDBCFile)
assert.NilError(t, err)
result, err := Compile(exampleDBCFile, input)
if err != nil {
t.Fatal(err)
}
if len(result.Warnings) > 0 {
t.Fatal(result.Warnings)
}
assert.DeepEqual(t, exampleDatabase, result.Database)
}

View File

@@ -0,0 +1,338 @@
package generate
import (
"context"
"fmt"
"net"
"reflect"
"testing"
"time"
can "github.com/fiskerinc/cloud-services/pkg/can-go"
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/socketcan"
examplecan "github.com/fiskerinc/cloud-services/pkg/can-go/testdata/gen/go/example"
"golang.org/x/sync/errgroup"
"gotest.tools/v3/assert"
)
func TestExampleDatabase_MarshalUnmarshal(t *testing.T) {
for _, tt := range []struct {
name string
m can.Message
f can.Frame
}{
{
name: "IODebug",
m: examplecan.NewIODebug().
SetTestUnsigned(5).
SetTestEnum(examplecan.IODebug_TestEnum_Two).
SetTestSigned(-42).
SetTestFloat(61.5).
SetTestBoolEnum(examplecan.IODebug_TestBoolEnum_One).
SetRawTestScaledEnum(examplecan.IODebug_TestScaledEnum_Four),
f: can.Frame{
ID: 500,
Length: 6,
Data: can.Data{5, 2, 214, 123, 1, 2},
},
},
{
name: "MotorStatus1",
m: examplecan.NewMotorStatus().
SetSpeedKph(0.423).
SetWheelError(true),
f: can.Frame{
ID: 400,
Length: 3,
Data: can.Data{0x1, 0xa7, 0x1},
},
},
{
name: "MotorStatus2",
m: examplecan.NewMotorStatus().
SetSpeedKph(12),
f: can.Frame{
ID: 400,
Length: 3,
Data: can.Data{0x00, 0xe0, 0x2e},
},
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
f, err := tt.m.MarshalFrame()
assert.NilError(t, err)
assert.Equal(t, tt.f, f)
// allocate new message of same type as tt.m
msg := reflect.New(reflect.ValueOf(tt.m).Elem().Type()).Interface().(generated.Message)
assert.NilError(t, msg.UnmarshalFrame(f))
assert.Assert(t, reflect.DeepEqual(tt.m, msg))
})
}
}
func TestExampleDatabase_UnmarshalFrame_Error(t *testing.T) {
for _, tt := range []struct {
name string
f can.Frame
m generated.Message
err string
}{
{
name: "wrong ID",
f: can.Frame{ID: 11, Length: 8},
m: examplecan.NewSensorSonars(),
err: "unmarshal SensorSonars: expects ID 200 (got 00B#0000000000000000 with ID 11)",
},
{
name: "wrong length",
f: can.Frame{ID: 200, Length: 4},
m: examplecan.NewSensorSonars(),
err: "unmarshal SensorSonars: expects length 8 (got 0C8#00000000 with length 4)",
},
{
name: "remote frame",
f: can.Frame{ID: 200, Length: 8, IsRemote: true},
m: examplecan.NewSensorSonars(),
err: "unmarshal SensorSonars: expects non-remote frame (got remote frame 0C8#R8)",
},
{
name: "extended ID",
f: can.Frame{ID: 200, Length: 8, IsExtended: true},
m: examplecan.NewSensorSonars(),
err: "unmarshal SensorSonars: expects standard ID (got 000000C8#0000000000000000 with extended ID)",
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.err, tt.m.UnmarshalFrame(tt.f).Error())
})
}
}
func TestExampleDatabase_TestEnum_String(t *testing.T) {
assert.Equal(t, "One", examplecan.IODebug_TestEnum_One.String())
assert.Equal(t, "Two", examplecan.IODebug_TestEnum_Two.String())
assert.Equal(t, "IODebug_TestEnum(3)", examplecan.IODebug_TestEnum(3).String())
}
func TestExampleDatabase_Message_String(t *testing.T) {
const expected = "{WheelError: true, SpeedKph: 42km/h}"
msg := examplecan.NewMotorStatus().
SetSpeedKph(42).
SetWheelError(true)
assert.Equal(t, expected, msg.String())
assert.Equal(t, expected, fmt.Sprintf("%v", msg))
}
func TestExampleDatabase_OutOfBoundsValue(t *testing.T) {
const expected = examplecan.IODebug_TestEnum(63)
actual := examplecan.NewIODebug().SetTestEnum(255).TestEnum()
assert.Equal(t, expected, actual)
}
func TestExampleDatabase_MultiplexedSignals(t *testing.T) {
// Given a message with multiplexed signals
msg := examplecan.NewSensorSonars().
SetErrCount(1).
SetMux(1).
SetLeft(20).
SetMiddle(30).
SetRight(40).
SetRear(50).
SetNoFiltLeft(60).
SetNoFiltMiddle(70).
SetNoFiltRight(80).
SetNoFiltRear(90)
for _, tt := range []struct {
expectedMux uint8
expectedErrCount uint16
expectedLeft float64
expectedMiddle float64
expectedRight float64
expectedRear float64
expectedNoFiltLeft float64
expectedNoFiltMiddle float64
expectedNoFiltRight float64
expectedNoFiltRear float64
}{
{
expectedMux: 0,
expectedErrCount: 1,
expectedLeft: 20,
expectedMiddle: 30,
expectedRight: 40,
expectedRear: 50,
expectedNoFiltLeft: 0,
expectedNoFiltMiddle: 0,
expectedNoFiltRight: 0,
expectedNoFiltRear: 0,
},
{
expectedMux: 1,
expectedErrCount: 1,
expectedLeft: 0,
expectedMiddle: 0,
expectedRight: 0,
expectedRear: 0,
expectedNoFiltLeft: 60,
expectedNoFiltMiddle: 70,
expectedNoFiltRight: 80,
expectedNoFiltRear: 90,
},
} {
tt := tt
t.Run(fmt.Sprintf("mux=%v", tt.expectedMux), func(t *testing.T) {
unmarshal1 := examplecan.NewSensorSonars()
// When the multiplexer signal is 0 and we marshal the message
// to a CAN frame
msg.SetMux(tt.expectedMux)
f1, err := msg.MarshalFrame()
assert.NilError(t, err)
// When we unmarshal the CAN frame back to a message
assert.NilError(t, unmarshal1.UnmarshalFrame(f1))
// Then only the multiplexed signals with multiplexer value 0
// should be unmarshaled
assert.Equal(t, tt.expectedMux, unmarshal1.Mux(), "Mux")
assert.Equal(t, tt.expectedErrCount, unmarshal1.ErrCount(), "ErrCount")
assert.Equal(t, tt.expectedLeft, unmarshal1.Left(), "Left")
assert.Equal(t, tt.expectedMiddle, unmarshal1.Middle(), "Middle")
assert.Equal(t, tt.expectedRight, unmarshal1.Right(), "Right")
assert.Equal(t, tt.expectedRear, unmarshal1.Rear(), "Rear")
assert.Equal(t, tt.expectedNoFiltLeft, unmarshal1.NoFiltLeft(), "NoFiltLeft")
assert.Equal(t, tt.expectedNoFiltMiddle, unmarshal1.NoFiltMiddle(), "NoFiltMiddle")
assert.Equal(t, tt.expectedNoFiltRight, unmarshal1.NoFiltRight(), "NoFiltRight")
assert.Equal(t, tt.expectedNoFiltRear, unmarshal1.NoFiltRear(), "NoFiltRear")
})
}
}
func TestExampleDatabase_CopyFrom(t *testing.T) {
// Given: an original message
from := examplecan.NewIODebug().
SetRawTestScaledEnum(examplecan.IODebug_TestScaledEnum_Four).
SetTestBoolEnum(true).
SetTestFloat(0.1).
SetTestSigned(-10).
SetTestUnsigned(10)
// When: another message copies from the original message
to := examplecan.NewIODebug().CopyFrom(from)
// Then:
// all fields should be equal...
assert.Equal(t, from.String(), to.String())
assert.Equal(t, from.TestScaledEnum(), to.TestScaledEnum())
assert.Equal(t, from.TestBoolEnum(), to.TestBoolEnum())
assert.Equal(t, from.TestFloat(), to.TestFloat())
assert.Equal(t, from.TestSigned(), to.TestSigned())
assert.Equal(t, from.TestUnsigned(), to.TestUnsigned())
// ...and changes to the original should not affect the new message
from.SetTestUnsigned(100)
assert.Equal(t, uint8(10), to.TestUnsigned())
}
func TestExample_Nodes(t *testing.T) {
const testTimeout = 2 * time.Second
requireVCAN0(t)
// given a DRIVER node and a MOTOR node
motor := examplecan.NewMOTOR("can", "vcan0")
driver := examplecan.NewDRIVER("can", "vcan0")
// when starting them
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
return motor.Run(ctx)
})
g.Go(func() error {
return driver.Run(ctx)
})
// and the MOTOR node is configured to send a speed report
const expectedSpeedKph = 42
motor.Lock()
motor.Tx().MotorStatus().SetSpeedKph(expectedSpeedKph)
motor.Tx().MotorStatus().SetCyclicTransmissionEnabled(true)
motor.Unlock()
// and the DRIVER node is configured to send a steering command
const expectedSteer = -4
driver.Lock()
driver.Tx().MotorCommand().SetSteer(expectedSteer)
driver.Tx().MotorCommand().SetCyclicTransmissionEnabled(true)
driver.Unlock()
// and the MOTOR node is listening for the steering command
expectedSteerReceivedChan := make(chan struct{})
motor.Lock()
motor.Rx().MotorCommand().SetAfterReceiveHook(func(context.Context) error {
motor.Lock()
if motor.Rx().MotorCommand().Steer() == expectedSteer {
close(expectedSteerReceivedChan)
motor.Rx().MotorCommand().SetAfterReceiveHook(func(context.Context) error { return nil })
}
motor.Unlock()
return nil
})
motor.Unlock()
// and the DRIVER node is listening for the speed report
expectedSpeedReceivedChan := make(chan struct{})
driver.Lock()
driver.Rx().MotorStatus().SetAfterReceiveHook(func(context.Context) error {
driver.Lock()
if driver.Rx().MotorStatus().SpeedKph() == expectedSpeedKph {
close(expectedSpeedReceivedChan)
driver.Rx().MotorStatus().SetAfterReceiveHook(func(context.Context) error { return nil })
}
driver.Unlock()
return nil
})
driver.Unlock()
// then the steer command transmitted by DRIVER should be received by MOTOR
select {
case <-expectedSteerReceivedChan:
case <-ctx.Done():
t.Fatalf("expected steer not received: %v", expectedSteer)
}
// and the speed report transmitted by MOTOR should be received by DRIVER
select {
case <-expectedSpeedReceivedChan:
case <-ctx.Done():
t.Fatalf("expected speed not received: %v", expectedSpeedKph)
}
cancel()
assert.NilError(t, g.Wait())
}
func TestExample_Node_NoEmptyMessages(t *testing.T) {
const testTimeout = 2 * time.Second
requireVCAN0(t)
// given a DRIVER node and a MOTOR node
motor := examplecan.NewMOTOR("can", "vcan0")
// when starting them
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
handler := func(ctx context.Context) error {
motor.Lock()
motor.Tx().MotorStatus().SetSpeedKph(100).SetWheelError(true)
motor.Unlock()
return nil
}
motor.Tx().MotorStatus().SetBeforeTransmitHook(handler)
motor.Tx().MotorStatus().SetCyclicTransmissionEnabled(true)
c, err := socketcan.Dial("can", "vcan0")
r := socketcan.NewReceiver(c)
assert.NilError(t, err)
g := errgroup.Group{}
g.Go(func() error {
return motor.Run(ctx)
})
assert.Assert(t, r.Receive())
assert.Equal(t, examplecan.NewMotorStatus().SetSpeedKph(100).SetWheelError(true).Frame(), r.Frame())
cancel()
assert.NilError(t, g.Wait())
}
func requireVCAN0(t *testing.T) {
t.Helper()
if _, err := net.InterfaceByName("vcan0"); err != nil {
t.Skip("interface vcan0 does not exist")
}
}

View File

@@ -0,0 +1,976 @@
package generate
import (
"bytes"
"fmt"
"go/format"
"go/types"
"path"
"strings"
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
"github.com/shurcooL/go-goon"
)
type File struct {
buf bytes.Buffer
err error
}
func NewFile() *File {
f := &File{}
f.buf.Grow(1e5) // 100K
return f
}
func (f *File) Write(p []byte) (int, error) {
if f.err != nil {
return 0, f.err
}
n, err := f.buf.Write(p)
f.err = err
return n, err
}
func (f *File) P(v ...interface{}) {
for _, x := range v {
_, _ = fmt.Fprint(f, x)
}
_, _ = fmt.Fprintln(f)
}
func (f *File) Dump(v interface{}) {
_, _ = goon.Fdump(f, v)
}
func (f *File) Content() ([]byte, error) {
if f.err != nil {
return nil, fmt.Errorf("file content: %w", f.err)
}
formatted, err := format.Source(f.buf.Bytes())
if err != nil {
return nil, fmt.Errorf("file content: %s: %w", f.buf.String(), err)
}
return formatted, nil
}
func Database(h string, d *descriptor.Database) ([]byte, error) {
f := NewFile()
Package(f, d)
Imports(f)
Version(f, h, d.Version)
ListECUs(f, d)
for _, m := range d.Messages {
if m == nil {
continue
}
MessageType(f, m)
for _, s := range m.Signals {
if hasCustomType(s) {
SignalCustomType(f, m, s)
}
}
MarshalFrame(f, m)
UnmarshalFrame(f, m)
}
if hasSendType(d) { // only code-generate nodes for schemas with send types specified
for _, n := range d.Nodes {
Node(f, d, n)
}
}
Descriptors(f, d)
return f.Content()
}
func Package(f *File, d *descriptor.Database) {
packageName := strings.TrimSuffix(path.Base(d.SourceFile), path.Ext(d.SourceFile)) + "can"
f.P("// Package ", packageName, " provides primitives for encoding and decoding ", d.Name(), " CAN messages.")
f.P("//")
f.P("// Source: ", d.SourceFile)
f.P("package ", packageName)
f.P()
}
func Imports(f *File) {
f.P("import (")
f.P(`"context"`)
f.P(`"fmt"`)
f.P(`"net"`)
f.P(`"net/http"`)
f.P(`"sync"`)
f.P(`"time"`)
f.P()
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go"`)
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/socketcan"`)
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/candebug"`)
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/canrunner"`)
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"`)
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"`)
f.P(`"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/cantext"`)
f.P(")")
f.P()
// we could use goimports for this, but it significantly slows down code generation
f.P("// prevent unused imports")
f.P("var (")
f.P("_ = context.Background")
f.P("_ = fmt.Print")
f.P("_ = net.Dial")
f.P("_ = http.Error")
f.P("_ = sync.Mutex{}")
f.P("_ = time.Now")
f.P("_ = socketcan.Dial")
f.P("_ = candebug.ServeMessagesHTTP")
f.P("_ = canrunner.Run")
f.P(")")
f.P()
f.P("// Generated code. DO NOT EDIT.")
}
func Version(f *File, h string, v string) {
f.P()
f.P("// Hash used as versioning control for DBC")
f.P(`const Hash string = "`, h, `"`)
f.P("// Version is the version listed in the DBC")
f.P(`const Version string = "`, v, `"`)
f.P()
}
func ListECUs(f *File, d *descriptor.Database) {
f.P()
f.P("// ECUs parsed from DBC")
ecuList := "var ECUs = []string{"
i := 0
for ecu := range d.ECUs {
ecuList += fmt.Sprintf(`"%s"`, ecu)
i++
if i != len(d.ECUs) {
ecuList += ", "
}
}
ecuList += "}"
f.P(ecuList)
f.P()
}
func SignalCustomType(f *File, m *descriptor.Message, s *descriptor.Signal) {
f.P("// ", signalType(m, s), " models the ", s.Name, " signal of the ", m.Name, " message.")
f.P("type ", signalType(m, s), " ", signalPrimitiveType(s))
f.P()
// dtaylor@fiskerinc.com EDIT
// f.P("// Value descriptions for the ", s.Name, " signal of the ", m.Name, " message.")
// f.P("const (")
// for _, vd := range s.ValueDescriptions {
// switch {
// case s.Length == 1 && vd.Value == 1:
// f.P(signalType(m, s), "_", vd.Description, " ", signalType(m, s), " = true")
// case s.Length == 1 && vd.Value == 0:
// f.P(signalType(m, s), "_", vd.Description, " ", signalType(m, s), " = false")
// default:
// f.P(signalType(m, s), "_", vd.Description, " ", signalType(m, s), " = ", vd.Value)
// }
// }
// f.P(")")
// f.P()
f.P("func (v ", signalType(m, s), ") String() string {")
if s.Length == 1 {
f.P("switch bool(v) {")
for _, vd := range s.ValueDescriptions {
if vd.Value == 1 {
f.P("case true:")
} else {
f.P("case false:")
}
f.P(`return "`, vd.Description, `"`)
}
f.P("}")
f.P(`return fmt.Sprintf("`, signalType(m, s), `(%t)", v)`)
} else {
f.P("switch v {")
for _, vd := range s.ValueDescriptions {
f.P("case ", vd.Value, ":")
f.P(`return "`, vd.Description, `"`)
}
f.P("default:")
f.P(`return fmt.Sprintf("`, signalType(m, s), `(%d)", v)`)
f.P("}")
}
f.P("}")
}
func MessageType(f *File, m *descriptor.Message) {
f.P("// ", messageReaderInterface(m), " provides read access to a ", m.Name, " message.")
f.P("type ", messageReaderInterface(m), " interface {")
for _, s := range m.Signals {
if hasPhysicalRepresentation(s) {
f.P("// ", s.Name, " returns the physical value of the ", s.Name, " signal.")
f.P(s.Name, "() float64")
if len(s.ValueDescriptions) > 0 {
f.P()
f.P("// ", s.Name, " returns the raw (encoded) value of the ", s.Name, " signal.")
f.P("Raw", s.Name, "() ", signalType(m, s))
}
} else {
f.P("// ", s.Name, " returns the value of the ", s.Name, " signal.")
f.P(s.Name, "()", signalType(m, s))
}
}
f.P("}")
f.P()
f.P("// ", messageWriterInterface(m), " provides write access to a ", m.Name, " message.")
f.P("type ", messageWriterInterface(m), " interface {")
f.P("// CopyFrom copies all values from ", messageReaderInterface(m), ".")
f.P("CopyFrom(", messageReaderInterface(m), ") *", messageStruct(m))
for _, s := range m.Signals {
if hasPhysicalRepresentation(s) {
f.P("// Set", s.Name, " sets the physical value of the ", s.Name, " signal.")
f.P("Set", s.Name, "(float64) *", messageStruct(m))
if len(s.ValueDescriptions) > 0 {
f.P()
f.P("// SetRaw", s.Name, " sets the raw (encoded) value of the ", s.Name, " signal.")
f.P("SetRaw", s.Name, "(", signalType(m, s), ") *", messageStruct(m))
}
} else {
f.P("// Set", s.Name, " sets the value of the ", s.Name, " signal.")
f.P("Set", s.Name, "(", signalType(m, s), ") *", messageStruct(m))
}
}
f.P("}")
f.P()
f.P("type ", messageStruct(m), " struct {")
for _, s := range m.Signals {
f.P(signalField(s), " ", signalType(m, s))
}
f.P("}")
f.P()
f.P("func New", messageStruct(m), "() *", messageStruct(m), " {")
f.P("m := &", messageStruct(m), "{}")
f.P("m.Reset()")
f.P("return m")
f.P("}")
f.P()
f.P("func (m *", messageStruct(m), ") Reset() {")
for _, s := range m.Signals {
switch {
case s.Length == 1 && s.DefaultValue == 1:
f.P("m.", signalField(s), " = true")
case s.Length == 1:
f.P("m.", signalField(s), " = false")
default:
f.P("m.", signalField(s), " = ", s.DefaultValue)
}
}
f.P("}")
f.P()
f.P("func (m *", messageStruct(m), ") CopyFrom(o ", messageReaderInterface(m), ") *", messageStruct(m), "{")
for _, s := range m.Signals {
if hasPhysicalRepresentation(s) {
f.P("m.Set", s.Name, "(o.", s.Name, "())")
} else {
f.P("m.", signalField(s), " = o.", s.Name, "()")
}
}
f.P("return m")
f.P("}")
f.P()
f.P("// Descriptor returns the ", m.Name, " descriptor.")
f.P("func (m *", messageStruct(m), ") Descriptor() *descriptor.Message {")
f.P("return ", messageDescriptor(m), ".Message")
f.P("}")
f.P()
f.P("// String returns a compact string representation of the message.")
f.P("func(m *", messageStruct(m), ") String() string {")
f.P("return cantext.MessageString(m)")
f.P("}")
f.P()
for _, s := range m.Signals {
if !hasPhysicalRepresentation(s) {
f.P("func (m *", messageStruct(m), ") ", s.Name, "() ", signalType(m, s), " {")
f.P("return m.", signalField(s))
f.P("}")
f.P()
f.P("func (m *", messageStruct(m), ") Set", s.Name, "(v ", signalType(m, s), ") *", messageStruct(m), " {")
if s.Length == 1 {
f.P("m.", signalField(s), " = v")
} else {
f.P(
"m.", signalField(s), " = ", signalType(m, s), "(",
signalDescriptor(m, s), ".SaturatedCast", signalSuperType(s), "(",
signalPrimitiveSuperType(s), "(v)))",
)
}
f.P("return m")
f.P("}")
f.P()
continue
}
f.P("func (m *", messageStruct(m), ") ", s.Name, "() float64 {")
f.P("return ", signalDescriptor(m, s), ".ToPhysical(float64(m.", signalField(s), "))")
f.P("}")
f.P()
f.P("func (m *", messageStruct(m), ") Set", s.Name, "(v float64) *", messageStruct(m), " {")
f.P("m.", signalField(s), " = ", signalType(m, s), "(", signalDescriptor(m, s), ".FromPhysical(v))")
f.P("return m")
f.P("}")
f.P()
if len(s.ValueDescriptions) > 0 {
f.P("func (m *", messageStruct(m), ") Raw", s.Name, "() ", signalType(m, s), " {")
f.P("return m.", signalField(s))
f.P("}")
f.P()
f.P("func (m *", messageStruct(m), ") SetRaw", s.Name, "(v ", signalType(m, s), ") *", messageStruct(m), "{")
f.P(
"m.", signalField(s), " = ", signalType(m, s), "(",
signalDescriptor(m, s), ".SaturatedCast", signalSuperType(s), "(",
signalPrimitiveSuperType(s), "(v)))",
)
f.P("return m")
f.P("}")
f.P()
}
}
}
func Descriptors(f *File, d *descriptor.Database) {
f.P("// Nodes returns the ", d.Name(), " node descriptors.")
f.P("func Nodes() *NodesDescriptor {")
f.P("return nd")
f.P("}")
f.P()
f.P("// NodesDescriptor contains all ", d.Name(), " node descriptors.")
f.P("type NodesDescriptor struct{")
for _, n := range d.Nodes {
f.P(n.Name, " *descriptor.Node")
}
f.P("}")
f.P()
f.P("// Messages returns the ", d.Name(), " message descriptors.")
f.P("func Messages() *MessagesDescriptor {")
f.P("return md")
f.P("}")
f.P()
f.P("// MessagesDescriptor contains all ", d.Name(), " message descriptors.")
f.P("type MessagesDescriptor struct{")
for _, m := range d.Messages {
if m == nil {
continue
}
f.P(m.Name, " *", m.Name, "Descriptor")
}
f.P("}")
f.P()
f.P("// UnmarshalFrame unmarshals the provided ", d.Name(), " CAN frame.")
f.P("func (md *MessagesDescriptor) UnmarshalFrame(f can.Frame) (generated.Message, error) {")
f.P("switch f.ID {")
for _, m := range d.Messages {
if m == nil {
continue
}
f.P("case md.", m.Name, ".ID:")
f.P("var msg ", messageStruct(m))
f.P("if err := msg.UnmarshalFrame(f); err != nil {")
f.P(`return nil, fmt.Errorf("unmarshal `, d.Name(), ` frame: %w", err)`)
f.P("}")
f.P("return &msg, nil")
}
f.P("default:")
f.P(`return nil, fmt.Errorf("unmarshal `, d.Name(), ` frame: ID not in database: %d", f.ID)`)
f.P("}")
f.P("}")
f.P()
for _, m := range d.Messages {
if m == nil {
continue
}
f.P("type ", m.Name, "Descriptor struct{")
f.P("*descriptor.Message")
for _, s := range m.Signals {
f.P(s.Name, " *descriptor.Signal")
}
f.P("}")
f.P()
}
f.P("// Database returns the ", d.Name(), " database descriptor.")
f.P("func (md *MessagesDescriptor) Database() *descriptor.Database {")
f.P("return d")
f.P("}")
f.P()
f.P("var nd = &NodesDescriptor{")
for ni, n := range d.Nodes {
f.P(n.Name, ": d.Nodes[", ni, "],")
}
f.P("}")
f.P()
f.P("var md = &MessagesDescriptor{")
for mi, m := range d.Messages {
if m == nil {
continue
}
f.P(m.Name, ": &", m.Name, "Descriptor{")
f.P("Message: d.Messages[", mi, "],")
for si, s := range m.Signals {
f.P(s.Name, ": d.Messages[", mi, "].Signals[", si, "],")
}
f.P("},")
}
f.P("}")
f.P()
f.P("var d = ")
f.Dump(d)
f.P()
}
func MarshalFrame(f *File, m *descriptor.Message) {
f.P("// Frame returns a CAN frame representing the message.")
f.P("func (m *", messageStruct(m), ") Frame() can.Frame {")
f.P("md := ", messageDescriptor(m))
f.P("f := can.Frame{ID: md.ID, IsExtended: md.IsExtended, Length: md.Length}")
for _, s := range m.Signals {
if s.IsMultiplexed {
continue
}
f.P(
"md.", s.Name, ".Marshal", signalSuperType(s),
"(&f.Data, ", signalPrimitiveSuperType(s), "(m.", signalField(s), "))",
)
}
if mux, ok := m.MultiplexerSignal(); ok {
for _, s := range m.Signals {
if !s.IsMultiplexed {
continue
}
f.P("if m.", signalField(mux), " == ", s.MultiplexerValue, " {")
f.P(
"md.", s.Name, ".Marshal", signalSuperType(s), "(&f.Data, ", signalPrimitiveSuperType(s),
"(m.", signalField(s), "))",
)
f.P("}")
}
}
f.P("return f")
f.P("}")
f.P()
f.P("// MarshalFrame encodes the message as a CAN frame.")
f.P("func (m *", messageStruct(m), ") MarshalFrame() (can.Frame, error) {")
f.P("return m.Frame(), nil")
f.P("}")
f.P()
}
func UnmarshalFrame(f *File, m *descriptor.Message) {
f.P("// UnmarshalFrame decodes the message from a CAN frame.")
f.P("func (m *", messageStruct(m), ") UnmarshalFrame(f can.Frame) error {")
f.P("md := ", messageDescriptor(m))
// generate frame checks
id := func(isExtended bool) string {
if isExtended {
return "extended ID"
}
return "standard ID"
}
f.P("switch {")
f.P("case f.ID != md.ID:")
f.P(`return fmt.Errorf(`)
f.P(`"unmarshal `, m.Name, `: expects ID `, m.ID, ` (got %s with ID %d)", f.String(), f.ID,`)
f.P(`)`)
f.P("case f.Length != md.Length:")
f.P(`return fmt.Errorf(`)
f.P(`"unmarshal `, m.Name, `: expects length `, m.Length, ` (got %s with length %d)", f.String(), f.Length,`)
f.P(`)`)
f.P("case f.IsRemote:")
f.P(`return fmt.Errorf(`)
f.P(`"unmarshal `, m.Name, `: expects non-remote frame (got remote frame %s)", f.String(),`)
f.P(`)`)
f.P("case f.IsExtended != md.IsExtended:")
f.P(`return fmt.Errorf(`)
f.P(`"unmarshal `, m.Name, `: expects `, id(m.IsExtended), ` (got %s with `, id(!m.IsExtended), `)", f.String(),`)
f.P(`)`)
f.P("}")
if len(m.Signals) == 0 {
f.P("return nil")
f.P("}")
return
}
// generate non-multiplexed signal unmarshaling
for _, s := range m.Signals {
if s.IsMultiplexed {
continue
}
f.P("m.", signalField(s), " = ", signalType(m, s), "(md.", s.Name, ".Unmarshal", signalSuperType(s), "(f.Data))")
}
// generate multiplexed signal unmarshaling
if mux, ok := m.MultiplexerSignal(); ok {
for _, s := range m.Signals {
if !s.IsMultiplexed {
continue
}
f.P("if m.", signalField(mux), " == ", s.MultiplexerValue, " {")
f.P("m.", signalField(s), " = ", signalType(m, s), "(md.", s.Name, ".Unmarshal", signalSuperType(s), "(f.Data))")
f.P("}")
}
}
f.P("return nil")
f.P("}")
f.P()
}
func Node(f *File, d *descriptor.Database, n *descriptor.Node) {
rxMessages := collectRxMessages(d, n)
txMessages := collectTxMessages(d, n)
f.P("type ", nodeInterface(n), " interface {")
f.P("sync.Locker")
f.P("Tx() ", txGroupInterface(n))
f.P("Rx() ", rxGroupInterface(n))
f.P("Run(ctx context.Context) error")
f.P("}")
f.P()
f.P("type ", rxGroupInterface(n), " interface {")
f.P("http.Handler // for debugging")
for _, m := range rxMessages {
f.P(m.Name, "() ", rxMessageInterface(n, m))
}
f.P("}")
f.P()
f.P("type ", txGroupInterface(n), " interface {")
f.P("http.Handler // for debugging")
for _, m := range txMessages {
f.P(m.Name, "() ", txMessageInterface(n, m))
}
f.P("}")
f.P()
for _, m := range rxMessages {
f.P("type ", rxMessageInterface(n, m), " interface {")
f.P(messageReaderInterface(m))
f.P("ReceiveTime() time.Time")
f.P("SetAfterReceiveHook(h func(context.Context) error)")
f.P("}")
f.P()
}
for _, m := range txMessages {
f.P("type ", txMessageInterface(n, m), " interface {")
f.P(messageReaderInterface(m))
f.P(messageWriterInterface(m))
f.P("TransmitTime() time.Time")
f.P("Transmit(ctx context.Context) error")
f.P("SetBeforeTransmitHook(h func(context.Context) error)")
if m.SendType == descriptor.SendTypeCyclic {
f.P("// SetCyclicTransmissionEnabled enables/disables cyclic transmission.")
f.P("SetCyclicTransmissionEnabled(bool)")
f.P("// IsCyclicTransmissionEnabled returns whether cyclic transmission is enabled/disabled.")
f.P("IsCyclicTransmissionEnabled() bool")
}
f.P("}")
f.P()
}
f.P("type ", nodeStruct(n), " struct {")
f.P("sync.Mutex // protects all node state")
f.P("network string")
f.P("address string")
f.P("rx ", rxGroupStruct(n))
f.P("tx ", txGroupStruct(n))
f.P("}")
f.P()
f.P("var _ ", nodeInterface(n), " = &", nodeStruct(n), "{}")
f.P("var _ canrunner.Node = &", nodeStruct(n), "{}")
f.P()
f.P("func New", nodeInterface(n), "(network, address string) ", nodeInterface(n), " {")
f.P("n := &", nodeStruct(n), "{network: network, address: address}")
f.P("n.rx.parentMutex = &n.Mutex")
f.P("n.tx.parentMutex = &n.Mutex")
for _, m := range rxMessages {
f.P("n.rx.", messageField(m), ".init()")
f.P("n.rx.", messageField(m), ".Reset()")
}
for _, m := range txMessages {
f.P("n.tx.", messageField(m), ".init()")
f.P("n.tx.", messageField(m), ".Reset()")
}
f.P("return n")
f.P("}")
f.P()
f.P("func (n *", nodeStruct(n), ") Run(ctx context.Context) error {")
f.P("return canrunner.Run(ctx, n)")
f.P("}")
f.P()
f.P("func (n *", nodeStruct(n), ") Rx() ", rxGroupInterface(n), " {")
f.P("return &n.rx")
f.P("}")
f.P()
f.P("func (n *", nodeStruct(n), ") Tx() ", txGroupInterface(n), " {")
f.P("return &n.tx")
f.P("}")
f.P()
f.P("type ", rxGroupStruct(n), " struct {")
f.P("parentMutex *sync.Mutex")
for _, m := range rxMessages {
f.P(messageField(m), " ", rxMessageStruct(n, m))
}
f.P("}")
f.P()
f.P("var _ ", rxGroupInterface(n), " = &", rxGroupStruct(n), "{}")
f.P()
f.P("func (rx *", rxGroupStruct(n), ") ServeHTTP(w http.ResponseWriter, r *http.Request) {")
f.P("rx.parentMutex.Lock()")
f.P("defer rx.parentMutex.Unlock()")
f.P("candebug.ServeMessagesHTTP(w, r, []generated.Message{")
for _, m := range rxMessages {
f.P("&rx.", messageField(m), ",")
}
f.P("})")
f.P("}")
f.P()
for _, m := range rxMessages {
f.P("func (rx *", rxGroupStruct(n), ") ", m.Name, "() ", rxMessageInterface(n, m), " {")
f.P("return &rx.", messageField(m))
f.P("}")
f.P()
}
f.P()
f.P("type ", txGroupStruct(n), " struct {")
f.P("parentMutex *sync.Mutex")
for _, m := range txMessages {
f.P(messageField(m), " ", txMessageStruct(n, m))
}
f.P("}")
f.P()
f.P("var _ ", txGroupInterface(n), " = &", txGroupStruct(n), "{}")
f.P()
f.P("func (tx *", txGroupStruct(n), ") ServeHTTP(w http.ResponseWriter, r *http.Request) {")
f.P("tx.parentMutex.Lock()")
f.P("defer tx.parentMutex.Unlock()")
f.P("candebug.ServeMessagesHTTP(w, r, []generated.Message{")
for _, m := range txMessages {
f.P("&tx.", messageField(m), ",")
}
f.P("})")
f.P("}")
f.P()
for _, m := range txMessages {
f.P("func (tx *", txGroupStruct(n), ") ", m.Name, "() ", txMessageInterface(n, m), " {")
f.P("return &tx.", messageField(m))
f.P("}")
f.P()
}
f.P()
f.P("func (n *", nodeStruct(n), ") Descriptor() *descriptor.Node {")
f.P("return ", nodeDescriptor(n))
f.P("}")
f.P()
f.P("func (n *", nodeStruct(n), ") Connect() (net.Conn, error) {")
f.P("return socketcan.Dial(n.network, n.address)")
f.P("}")
f.P()
f.P("func (n *", nodeStruct(n), ") ReceivedMessage(id uint32) (canrunner.ReceivedMessage, bool) {")
f.P("switch id {")
for _, m := range rxMessages {
f.P("case ", m.ID, ":")
f.P("return &n.rx.", messageField(m), ", true")
}
f.P("default:")
f.P("return nil, false")
f.P("}")
f.P("}")
f.P()
f.P("func (n *", nodeStruct(n), ") TransmittedMessages() []canrunner.TransmittedMessage {")
f.P("return []canrunner.TransmittedMessage{")
for _, m := range txMessages {
f.P("&n.tx.", messageField(m), ",")
}
f.P("}")
f.P("}")
f.P()
for _, m := range rxMessages {
f.P("type ", rxMessageStruct(n, m), " struct {")
f.P(messageStruct(m))
f.P("receiveTime time.Time")
f.P("afterReceiveHook func(context.Context) error")
f.P("}")
f.P()
f.P("func (m *", rxMessageStruct(n, m), ") init() {")
f.P("m.afterReceiveHook = func(context.Context) error { return nil }")
f.P("}")
f.P()
f.P("func (m *", rxMessageStruct(n, m), ") SetAfterReceiveHook(h func(context.Context) error) {")
f.P("m.afterReceiveHook = h")
f.P("}")
f.P()
f.P("func (m *", rxMessageStruct(n, m), ") AfterReceiveHook() func(context.Context) error {")
f.P("return m.afterReceiveHook")
f.P("}")
f.P()
f.P("func (m *", rxMessageStruct(n, m), ") ReceiveTime() time.Time {")
f.P("return m.receiveTime")
f.P("}")
f.P()
f.P("func (m *", rxMessageStruct(n, m), ") SetReceiveTime(t time.Time) {")
f.P("m.receiveTime = t")
f.P("}")
f.P()
f.P("var _ canrunner.ReceivedMessage = &", rxMessageStruct(n, m), "{}")
f.P()
}
for _, m := range txMessages {
f.P("type ", txMessageStruct(n, m), " struct {")
f.P(messageStruct(m))
f.P("transmitTime time.Time")
f.P("beforeTransmitHook func(context.Context) error")
f.P("isCyclicEnabled bool")
f.P("wakeUpChan chan struct{}")
f.P("transmitEventChan chan struct{}")
f.P("}")
f.P()
f.P("var _ ", txMessageInterface(n, m), " = &", txMessageStruct(n, m), "{}")
f.P("var _ canrunner.TransmittedMessage = &", txMessageStruct(n, m), "{}")
f.P()
f.P("func (m *", txMessageStruct(n, m), ") init() {")
f.P("m.beforeTransmitHook = func(context.Context) error { return nil }")
f.P("m.wakeUpChan = make(chan struct{}, 1)")
f.P("m.transmitEventChan = make(chan struct{})")
f.P("}")
f.P()
f.P("func (m *", txMessageStruct(n, m), ") SetBeforeTransmitHook(h func(context.Context) error) {")
f.P("m.beforeTransmitHook = h")
f.P("}")
f.P()
f.P("func (m *", txMessageStruct(n, m), ") BeforeTransmitHook() func(context.Context) error {")
f.P("return m.beforeTransmitHook")
f.P("}")
f.P()
f.P("func (m *", txMessageStruct(n, m), ") TransmitTime() time.Time {")
f.P("return m.transmitTime")
f.P("}")
f.P()
f.P("func (m *", txMessageStruct(n, m), ") SetTransmitTime(t time.Time) {")
f.P("m.transmitTime = t")
f.P("}")
f.P()
f.P("func (m *", txMessageStruct(n, m), ") IsCyclicTransmissionEnabled() bool {")
f.P("return m.isCyclicEnabled")
f.P("}")
f.P()
f.P("func (m *", txMessageStruct(n, m), ") SetCyclicTransmissionEnabled(b bool) {")
f.P("m.isCyclicEnabled = b")
f.P("select {")
f.P("case m.wakeUpChan <-struct{}{}:")
f.P("default:")
f.P("}")
f.P("}")
f.P()
f.P("func (m *", txMessageStruct(n, m), ") WakeUpChan() <-chan struct{} {")
f.P("return m.wakeUpChan")
f.P("}")
f.P()
f.P("func (m *", txMessageStruct(n, m), ") Transmit(ctx context.Context) error {")
f.P("select {")
f.P("case m.transmitEventChan <- struct{}{}:")
f.P("return nil")
f.P("case <-ctx.Done():")
f.P(`return fmt.Errorf("event-triggered transmit of `, m.Name, `: %w", ctx.Err())`)
f.P("}")
f.P("}")
f.P()
f.P("func (m *", txMessageStruct(n, m), ") TransmitEventChan() <-chan struct{} {")
f.P("return m.transmitEventChan")
f.P("}")
f.P()
f.P("var _ canrunner.TransmittedMessage = &", txMessageStruct(n, m), "{}")
f.P()
}
}
func txGroupInterface(n *descriptor.Node) string {
return n.Name + "_Tx"
}
func txGroupStruct(n *descriptor.Node) string {
return "xxx_" + n.Name + "_Tx"
}
func rxGroupInterface(n *descriptor.Node) string {
return n.Name + "_Rx"
}
func rxGroupStruct(n *descriptor.Node) string {
return "xxx_" + n.Name + "_Rx"
}
func rxMessageInterface(n *descriptor.Node, m *descriptor.Message) string {
return n.Name + "_Rx_" + m.Name
}
func rxMessageStruct(n *descriptor.Node, m *descriptor.Message) string {
return "xxx_" + n.Name + "_Rx_" + m.Name
}
func txMessageInterface(n *descriptor.Node, m *descriptor.Message) string {
return n.Name + "_Tx_" + m.Name
}
func txMessageStruct(n *descriptor.Node, m *descriptor.Message) string {
return "xxx_" + n.Name + "_Tx_" + m.Name
}
func collectTxMessages(d *descriptor.Database, n *descriptor.Node) []*descriptor.Message {
tx := make([]*descriptor.Message, 0, len(d.Messages))
for _, m := range d.Messages {
if m == nil {
continue
}
if m.SenderNode == n.Name && m.SendType != descriptor.SendTypeNone {
tx = append(tx, m)
}
}
return tx
}
func collectRxMessages(d *descriptor.Database, n *descriptor.Node) []*descriptor.Message {
rx := make([]*descriptor.Message, 0, len(d.Messages))
Loop:
for _, m := range d.Messages {
if m == nil {
continue
}
for _, s := range m.Signals {
for _, node := range s.ReceiverNodes {
if node != n.Name {
continue
}
rx = append(rx, m)
continue Loop
}
}
}
return rx
}
func hasPhysicalRepresentation(s *descriptor.Signal) bool {
hasScale := s.Scale != 0 && s.Scale != 1
hasOffset := s.Offset != 0
hasRange := s.Min != 0 || s.Max != 0
var hasConstrainedRange bool
if s.IsSigned {
hasConstrainedRange = s.Min > float64(s.MinSigned()) || s.Max < float64(s.MaxSigned())
} else {
hasConstrainedRange = s.Min > 0 || s.Max < float64(s.MaxUnsigned())
}
return hasScale || hasOffset || hasRange && hasConstrainedRange
}
func hasCustomType(s *descriptor.Signal) bool {
return len(s.ValueDescriptions) > 0
}
func hasSendType(d *descriptor.Database) bool {
for _, m := range d.Messages {
if m == nil {
continue
}
if m.SendType != descriptor.SendTypeNone {
return true
}
}
return false
}
func signalType(m *descriptor.Message, s *descriptor.Signal) string {
if hasCustomType(s) {
return m.Name + "_" + s.Name
}
return signalPrimitiveType(s).String()
}
func signalPrimitiveType(s *descriptor.Signal) types.Type {
var t types.BasicKind
switch {
case s.Length == 1:
t = types.Bool
case s.Length <= 8 && s.IsSigned:
t = types.Int8
case s.Length <= 8:
t = types.Uint8
case s.Length <= 16 && s.IsSigned:
t = types.Int16
case s.Length <= 16:
t = types.Uint16
case s.Length <= 32 && s.IsSigned:
t = types.Int32
case s.Length <= 32:
t = types.Uint32
case s.Length <= 64 && s.IsSigned:
t = types.Int64
default:
t = types.Uint64
}
return types.Typ[t]
}
func signalPrimitiveSuperType(s *descriptor.Signal) types.Type {
var t types.BasicKind
switch {
case s.Length == 1:
t = types.Bool
case s.IsSigned:
t = types.Int64
default:
t = types.Uint64
}
return types.Typ[t]
}
func signalSuperType(s *descriptor.Signal) string {
switch {
case s.Length == 1:
return "Bool"
case s.IsSigned:
return "Signed"
default:
return "Unsigned"
}
}
func nodeInterface(n *descriptor.Node) string {
return n.Name
}
func nodeStruct(n *descriptor.Node) string {
return "xxx_" + n.Name
}
func messageStruct(m *descriptor.Message) string {
return m.Name
}
func messageReaderInterface(m *descriptor.Message) string {
return m.Name + "Reader"
}
func messageWriterInterface(m *descriptor.Message) string {
return m.Name + "Writer"
}
func messageField(m *descriptor.Message) string {
return "xxx_" + m.Name
}
func signalField(s *descriptor.Signal) string {
return "xxx_" + s.Name
}
func nodeDescriptor(n *descriptor.Node) string {
return "Nodes()." + n.Name
}
func messageDescriptor(m *descriptor.Message) string {
return "Messages()." + m.Name
}
func signalDescriptor(m *descriptor.Message, s *descriptor.Signal) string {
return messageDescriptor(m) + "." + s.Name
}

View File

@@ -0,0 +1,18 @@
package generate
import (
"os"
"testing"
"gotest.tools/v3/assert"
)
func runTestInDir(t *testing.T, dir string) func() {
// change working directory to project root
wd, err := os.Getwd()
assert.NilError(t, err)
assert.NilError(t, os.Chdir(dir))
return func() {
assert.NilError(t, os.Chdir(wd))
}
}