diff --git a/dbc/ast.go b/dbc/ast.go index 4c3ab65..19b6582 100644 --- a/dbc/ast.go +++ b/dbc/ast.go @@ -2,6 +2,8 @@ // It is based on the version 1.0.5 of the DBC file format. package dbc +import "fmt" + // FileExtension is the extension of a DBC file. const FileExtension = ".dbc" @@ -15,6 +17,18 @@ type Location struct { Col int } +func (l *Location) String() string { + return fmt.Sprintf("%s:%d:%d", l.Filename, l.Line, l.Col) +} + +type withLocation struct { + loc *Location +} + +func (wl *withLocation) Location() *Location { + return wl.loc +} + // File definition: // // The DBC file describes the communication of a single CAN network. @@ -93,7 +107,7 @@ type BitTiming struct { // // node_name = DBC_identifier ; type Nodes struct { - Location *Location + withLocation Names []string } @@ -125,7 +139,7 @@ type ValueTable struct { // // value_description = unsigned_integer char_string ; type ValueDescription struct { - Location *Location + withLocation ID uint32 Name string @@ -194,7 +208,7 @@ type ValueEncoding struct { // If the massage shall have no sender, the string 'Vector__XXX' has to be given // here. type Message struct { - Location *Location + withLocation ID uint32 Name string @@ -297,7 +311,7 @@ const ( // be defined in the set of node names in the node section. If the signal shall have no // receiver, the string 'Vector__XXX' has to be given here. type Signal struct { - Location *Location + withLocation Name string IsMultiplexor bool diff --git a/dbc/parser.go b/dbc/parser.go index 5a8f7be..3d17c00 100644 --- a/dbc/parser.go +++ b/dbc/parser.go @@ -396,7 +396,7 @@ func (p *Parser) parseNodes() (*Nodes, error) { p.foundNode = true node := new(Nodes) - node.Location = p.getLocation() + node.withLocation.loc = p.getLocation() if err := p.expectPunct(punctColon); err != nil { return nil, err @@ -416,7 +416,7 @@ func (p *Parser) parseNodes() (*Nodes, error) { func (p *Parser) parseValueDescription() (*ValueDescription, error) { valDesc := new(ValueDescription) - valDesc.Location = p.getLocation() + valDesc.withLocation.loc = p.getLocation() t := p.scan() if !t.isNumber() { @@ -485,7 +485,7 @@ func (p *Parser) parseMessageID() (uint32, error) { func (p *Parser) parseMessage() (*Message, error) { msg := new(Message) - msg.Location = p.getLocation() + msg.withLocation.loc = p.getLocation() id, err := p.parseMessageID() if err != nil { @@ -544,7 +544,7 @@ func (p *Parser) parseSignalName() (string, error) { func (p *Parser) parseSignal() (*Signal, error) { sig := new(Signal) - sig.Location = p.getLocation() + sig.withLocation.loc = p.getLocation() name, err := p.parseSignalName() if err != nil { diff --git a/dbc/scanner.go b/dbc/scanner.go index d9e81a1..4199953 100644 --- a/dbc/scanner.go +++ b/dbc/scanner.go @@ -15,7 +15,7 @@ func isEOF(ch rune) bool { } func isSpace(ch rune) bool { - return ch == ' ' || ch == '\t' || ch == '\n' + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' } func isLetter(ch rune) bool { diff --git a/dbc/writer.go b/dbc/writer.go index 6b36a65..259293a 100644 --- a/dbc/writer.go +++ b/dbc/writer.go @@ -28,7 +28,10 @@ type writer struct { } func (w *writer) print(format string, a ...any) { - fmt.Fprintf(w.f, format, a...) + _, err := fmt.Fprintf(w.f, format, a...) + if err != nil { + panic(err) + } } func (w *writer) println(format string, a ...any) { diff --git a/importer.go b/importer.go new file mode 100644 index 0000000..a77ff0f --- /dev/null +++ b/importer.go @@ -0,0 +1,230 @@ +package acmelib + +import ( + "fmt" + + "github.com/FerroO2000/acmelib/dbc" +) + +func ImportDBCFile(dbcFile *dbc.File) (*Bus, error) { + importer := newImporter() + return importer.importFile(dbcFile) +} + +type dbcFileLocator interface { + Location() *dbc.Location +} + +type importer struct { + bus *Bus + + busDesc string + nodeDesc map[string]string + msgDesc map[MessageCANID]string + sigDesc map[string]string + + signalEnums map[string]*SignalEnum +} + +func newImporter() *importer { + return &importer{ + busDesc: "", + nodeDesc: make(map[string]string), + msgDesc: make(map[MessageCANID]string), + sigDesc: make(map[string]string), + + signalEnums: make(map[string]*SignalEnum), + } +} + +func (i *importer) errorf(dbcLoc dbcFileLocator, err error) error { + return fmt.Errorf("%s : %w", dbcLoc.Location(), err) +} + +func (i *importer) getSignalKey(dbcMsgID uint32, sigName string) string { + return fmt.Sprintf("%d_%s", dbcMsgID, sigName) +} + +func (i *importer) importFile(dbcFile *dbc.File) (*Bus, error) { + bus := NewBus(dbcFile.Location.Filename) + i.bus = bus + + i.importComments(dbcFile.Comments) + bus.SetDesc(i.busDesc) + + for _, dbcValEnc := range dbcFile.ValueEncodings { + if err := i.importValueEncoding(dbcValEnc); err != nil { + return nil, err + } + } + + if err := i.importNodes(dbcFile.Nodes); err != nil { + return nil, err + } + + for _, dbcMsg := range dbcFile.Messages { + if err := i.importMessage(dbcMsg); err != nil { + return nil, err + } + } + + return bus, nil +} + +func (i *importer) importComments(dbcComments []*dbc.Comment) { + for _, dbcComm := range dbcComments { + switch dbcComm.Kind { + case dbc.CommentGeneral: + i.busDesc = dbcComm.Text + + case dbc.CommentNode: + i.nodeDesc[dbcComm.NodeName] = dbcComm.Text + + case dbc.CommentMessage: + i.msgDesc[MessageCANID(dbcComm.MessageID)] = dbcComm.Text + + case dbc.CommentSignal: + key := i.getSignalKey(dbcComm.MessageID, dbcComm.SignalName) + i.sigDesc[key] = dbcComm.Text + } + } +} + +func (i *importer) importValueEncoding(dbcValEnc *dbc.ValueEncoding) error { + if dbcValEnc.Kind != dbc.ValueEncodingSignal { + return nil + } + + sigName := dbcValEnc.SignalName + sigEnum := NewSignalEnum(fmt.Sprintf("%s_Enum", sigName)) + for _, dbcVal := range dbcValEnc.Values { + if err := sigEnum.AddValue(NewSignalEnumValue(dbcVal.Name, int(dbcVal.ID))); err != nil { + return i.errorf(dbcVal, err) + } + } + + i.signalEnums[i.getSignalKey(dbcValEnc.MessageID, sigName)] = sigEnum + + return nil +} + +func (i *importer) importNodes(dbcNodes *dbc.Nodes) error { + for idx, nodeName := range dbcNodes.Names { + tmpNode := NewNode(nodeName, NodeID(idx)) + + if desc, ok := i.nodeDesc[nodeName]; ok { + tmpNode.SetDesc(desc) + } + + if err := i.bus.AddNode(tmpNode); err != nil { + return i.errorf(dbcNodes, err) + } + } + + if err := i.bus.AddNode(NewNode(dbc.DummyNode, 1024)); err != nil { + return i.errorf(dbcNodes, err) + } + + return nil +} + +func (i *importer) importMessage(dbcMsg *dbc.Message) error { + msg := NewMessage(dbcMsg.Name, int(dbcMsg.Size)) + + msgID := MessageCANID(dbcMsg.ID) + msg.SetCANID(msgID) + + if desc, ok := i.msgDesc[msgID]; ok { + msg.SetDesc(desc) + } + + receivers := make(map[string]bool) + for _, dbcSig := range dbcMsg.Signals { + tmpSig, err := i.importSignal(dbcSig, dbcMsg.ID) + if err != nil { + return err + } + + startBit := int(dbcSig.StartBit) + if tmpSig.ByteOrder() == SignalByteOrderBigEndian { + startBit -= (tmpSig.GetSize() - 1) + } + + if err := msg.InsertSignal(tmpSig, startBit); err != nil { + return i.errorf(dbcSig, err) + } + + for _, rec := range dbcSig.Receivers { + receivers[rec] = true + } + } + + for recName := range receivers { + if recName == dbc.DummyNode { + continue + } + + recNode, err := i.bus.GetNodeByName(recName) + if err != nil { + return i.errorf(dbcMsg, err) + } + + msg.AddReceiver(recNode) + } + + sendNode, err := i.bus.GetNodeByName(dbcMsg.Transmitter) + if err != nil { + return i.errorf(dbcMsg, err) + } + + if err := sendNode.AddMessage(msg); err != nil { + return i.errorf(dbcMsg, err) + } + + return nil +} + +func (i *importer) importSignal(dbcSig *dbc.Signal, dbcMsgID uint32) (Signal, error) { + var sig Signal + + sigName := dbcSig.Name + sigKey := i.getSignalKey(dbcMsgID, sigName) + + if sigEnum, ok := i.signalEnums[sigKey]; ok { + enumSig, err := NewEnumSignal(sigName, sigEnum) + if err != nil { + return nil, i.errorf(dbcSig, err) + } + sig = enumSig + + } else if dbcSig.IsMultiplexor { + // mux signal + } else { + signed := false + if dbcSig.ValueType == dbc.SignalSigned { + signed = true + } + + sigType, err := NewIntegerSignalType(fmt.Sprintf("%s_Type", sigName), int(dbcSig.Size), signed) + if err != nil { + return nil, i.errorf(dbcSig, err) + } + + stdSig, err := NewStandardSignal(sigName, sigType) + if err != nil { + return nil, i.errorf(dbcSig, err) + } + + sig = stdSig + } + + if desc, ok := i.sigDesc[sigKey]; ok { + sig.SetDesc(desc) + } + + if dbcSig.ByteOrder == dbc.SignalBigEndian { + sig.SetByteOrder(SignalByteOrderBigEndian) + } + + return sig, nil +} diff --git a/importer_test.go b/importer_test.go new file mode 100644 index 0000000..cd1ad33 --- /dev/null +++ b/importer_test.go @@ -0,0 +1,27 @@ +package acmelib + +import ( + "os" + "testing" + + "github.com/FerroO2000/acmelib/dbc" + "github.com/stretchr/testify/assert" +) + +func Test_ImportDBCFile(t *testing.T) { + assert := assert.New(t) + + file, err := os.ReadFile(cmpExportBusFilename) + assert.NoError(err) + + parser := dbc.NewParser("test_ExportBus", file) + dbcFile, err := parser.Parse() + assert.NoError(err) + + t.Log(dbcFile) + + // bus, err := ImportDBCFile(dbcFile) + // assert.NoError(err) + + // t.Log(bus) +} diff --git a/signal.go b/signal.go index 8a95ba0..528081f 100644 --- a/signal.go +++ b/signal.go @@ -58,6 +58,8 @@ type Signal interface { EntityID() EntityID // Name returns the name of the signal. Name() string + // SetDesc stes the description of the signal. + SetDesc(desc string) // Desc returns the description of the signal. Desc() string // CreateTime returns the creation time of the signal.