Refactor kafka to pure Go (franz-go), fix DBC stubs, update Dockerfile
This commit is contained in:
231
pkg/can-go/internal/generate/compile.go
Normal file
231
pkg/can-go/internal/generate/compile.go
Normal 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))
|
||||
}
|
||||
306
pkg/can-go/internal/generate/compile_test.go
Normal file
306
pkg/can-go/internal/generate/compile_test.go
Normal 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)
|
||||
}
|
||||
338
pkg/can-go/internal/generate/example_test.go
Normal file
338
pkg/can-go/internal/generate/example_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
976
pkg/can-go/internal/generate/file.go
Normal file
976
pkg/can-go/internal/generate/file.go
Normal 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
|
||||
}
|
||||
18
pkg/can-go/internal/generate/file_test.go
Normal file
18
pkg/can-go/internal/generate/file_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user