Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds flexible data rate CAN payload to account for 64 byte CANFD and > 64 byte ethernet payloads #21

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4e93a88
Support canfd (#1)
jshiv Mar 1, 2021
047b82f
Assign byte array directly to big.Int for big and little endian signa…
jshiv Mar 2, 2021
9986b66
Removed payload.Length property and calculate length with len(payload…
jshiv Mar 2, 2021
b87d69e
Added Decode and DecodePayload signal methods for decoding without sh…
Mar 22, 2021
592beb4
Added unit tests for Decode() to signal_test.go
Mar 23, 2021
fd0c28c
Added unit tests to test Decode() and DecodePayload()
Mar 23, 2021
f845a4a
Added unit tests for UnmarshalPhysical() and UnmarshalPhysicalPayload()
Mar 24, 2021
51c1b50
Merge pull request #4 from jshiv/fix/do_not_shift_ranges_signal_decode
naveenv92 Mar 24, 2021
36ce676
Added message.Decode method
Mar 25, 2021
cdee1a2
Updated message.Decode to use signal.Decode internally
Mar 25, 2021
6e5dbc8
Merge pull request #5 from jshiv/feature/message_decode
naveenv92 Mar 26, 2021
cddec59
Updated README with example of decoding and dbc file with >8 byte mes…
naveenv92 Apr 1, 2021
ec1debd
Merge remote-tracking branch 'upstream/master'
Apr 1, 2021
a2d4186
Merged with upstream/master
Apr 1, 2021
ef9ceb3
Removed example_payload.dbc and added a string dbc to the example in …
Apr 1, 2021
0248a67
Shortened DBC in the README and fixed linting issues
Apr 1, 2021
f697a62
Removed global for linter
Apr 1, 2021
16b04f8
Fixed linting issues - passes all golint tests locally
Apr 1, 2021
0ec95e8
go mod tidy
Apr 1, 2021
b67ca94
Updated generated go code from new Message struct format to avoid mal…
Apr 1, 2021
623b114
Merge branch 'master' of github.com:jshiv/can-go
Apr 1, 2021
194a970
init
pvora27 Jul 30, 2021
eb9bd02
add long name metadata
pvora27 Jul 30, 2021
2514e7c
Merge pull request #7 from jshiv/support_long_signal_names
naveenv92 Aug 2, 2021
70928fe
Added multiplexing
Jul 22, 2022
ba99d84
Updated README
Jul 22, 2022
24b187a
Updated README
Jul 22, 2022
f5e2511
Fixed reverse function
Aug 2, 2022
2376779
Merge remote-tracking branch 'upstream/master'
Aug 2, 2022
d599b93
Removed copy from reverse function
Aug 2, 2022
f5d4371
Cleaned up code
Aug 2, 2022
2a78a25
Reverted to len(data) from len(reversedArray)
Aug 2, 2022
68bf611
Changed reverse to append logic and updated unit test
Aug 2, 2022
615c5a9
Merge pull request #9 from jshiv/fix/change_reverse
naveenv92 Aug 2, 2022
a76019b
Merge branch 'master' of github.com:jshiv/can-go
Aug 3, 2022
4d6ffd9
Merge pull request #8 from jshiv/feature/add_multiplex_decoding
naveenv92 Aug 3, 2022
7487488
Merge branch 'einride:master' into master
jshiv Nov 18, 2022
566b73c
Fixed broken UnmarshalValueDescriptionPayload (#10)
jshiv Nov 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,112 @@ can-go makes use of the Linux SocketCAN abstraction for CAN communication. (See
Examples
--------

### Decoding CAN messages

Decoding CAN messages from byte arrays can be done using `can.Payload`

```go
func main() {
// DBC file
var dbcFile = []byte(`
VERSION ""
NS_ :
BS_:
BU_: DBG DRIVER IO MOTOR SENSOR

BO_ 400 MOTOR_STATUS: 3 MOTOR
SG_ MOTOR_STATUS_wheel_error : 0|1@1+ (1,0) [0|0] "" DRIVER,IO
SG_ MOTOR_STATUS_speed_kph : 8|16@1+ (0.001,0) [0|0] "kph" DRIVER,IO

BO_ 200 SENSOR_SONARS: 8 SENSOR
SG_ SENSOR_SONARS_mux M : 0|4@1+ (1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_err_count : 4|12@1+ (1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_left m0 : 16|12@1+ (0.1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_middle m0 : 28|12@1+ (0.1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_right m0 : 40|12@1+ (0.1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_rear m0 : 52|12@1+ (0.1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_no_filt_left m1 : 16|12@1+ (0.1,0) [0|0] "" DBG
SG_ SENSOR_SONARS_no_filt_middle m1 : 28|12@1+ (0.1,0) [0|0] "" DBG
SG_ SENSOR_SONARS_no_filt_right m1 : 40|12@1+ (0.1,0) [0|0] "" DBG
SG_ SENSOR_SONARS_no_filt_rear m1 : 52|12@1+ (0.1,0) [0|0] "" DBG
`)

// Create payload from hex string
byteStringHex := "004faf"
p, _ := can.PayloadFromHex(byteStringHex)

// Load example dbc file
c, _ := generate.Compile("test.dbc", dbcFile)
db := *c.Database

// Decode message frame ID 400
message, _ := db.Message(uint32(400))
decodedSignals := message.Decode(&p)
for _, signal := range decodedSignals {
fmt.Printf("Signal: %s, Value: %f, Description: %s\n", signal.Signal.Name, signal.Value, signal.Description)
}
}
```

```
Signal: MOTOR_STATUS_wheel_error, Value: 0.000000, Description:
Signal: MOTOR_STATUS_speed_kph, Value: 44.879000, Description:
```

#### Multiplexed Signals

```go
func main() {
// DBC file
var dbcFile = []byte(`
VERSION ""
NS_ :
BS_:
BU_: DBG DRIVER IO MOTOR SENSOR

BO_ 400 MOTOR_STATUS: 3 MOTOR
SG_ MOTOR_STATUS_wheel_error : 0|1@1+ (1,0) [0|0] "" DRIVER,IO
SG_ MOTOR_STATUS_speed_kph : 8|16@1+ (0.001,0) [0|0] "kph" DRIVER,IO

BO_ 200 SENSOR_SONARS: 8 SENSOR
SG_ SENSOR_SONARS_mux M : 0|4@1+ (1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_err_count : 4|12@1+ (1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_left m0 : 16|12@1+ (0.1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_middle m0 : 28|12@1+ (0.1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_right m0 : 40|12@1+ (0.1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_rear m0 : 52|12@1+ (0.1,0) [0|0] "" DRIVER,IO
SG_ SENSOR_SONARS_no_filt_left m1 : 16|12@1+ (0.1,0) [0|0] "" DBG
SG_ SENSOR_SONARS_no_filt_middle m1 : 28|12@1+ (0.1,0) [0|0] "" DBG
SG_ SENSOR_SONARS_no_filt_right m1 : 40|12@1+ (0.1,0) [0|0] "" DBG
SG_ SENSOR_SONARS_no_filt_rear m1 : 52|12@1+ (0.1,0) [0|0] "" DBG
`)

// Create payload from hex string
byteStringHex := "01af79f4aa3b459f"
p, _ := can.PayloadFromHex(byteStringHex)

// Load example dbc file
c, _ := generate.Compile("test.dbc", dbcFile)
db := *c.Database

// Decode message frame ID 200
message, _ := db.Message(uint32(200))
decodedSignals := message.Decode(&p)
for _, signal := range decodedSignals {
fmt.Printf("Signal: %s, Value: %f, Description: %s\n", signal.Signal.Name, signal.Value, signal.Description)
}
}
```

```
Signal: SENSOR_SONARS_mux, Value: 1.000000, Description:
Signal: SENSOR_SONARS_err_count, Value: 2800.000000, Description:
Signal: SENSOR_SONARS_no_filt_left, Value: 114.500000, Description:
Signal: SENSOR_SONARS_no_filt_middle, Value: 273.500000, Description:
Signal: SENSOR_SONARS_no_filt_right, Value: 133.900000, Description:
Signal: SENSOR_SONARS_no_filt_rear, Value: 254.800000, Description:
```

### Receiving CAN frames

Receiving CAN frames from a socketcan interface.
Expand Down
14 changes: 14 additions & 0 deletions data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,17 @@ func BenchmarkData_PackLittleEndian(b *testing.B) {
_ = data.PackLittleEndian()
}
}

func BenchmarkData_UnsignedBitsBigEndian(b *testing.B) {
var data Data
for i := 0; i < b.N; i++ {
_ = data.UnsignedBitsBigEndian(0, 16)
}
}

func BenchmarkData_UnsignedBitsLittleEndian(b *testing.B) {
var data Data
for i := 0; i < b.N; i++ {
_ = data.UnsignedBitsLittleEndian(0, 16)
}
}
6 changes: 3 additions & 3 deletions frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type Frame struct {
// ID is the CAN ID
ID uint32
// Length is the number of bytes of data in the frame.
Length uint8
Length uint16
// Data is the frame data.
Data Data
// IsRemote is true for remote frames.
Comment on lines 26 to 32

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In CAN FD the DLC field may be 16 bits but in our case we're only counting the number of bytes in a frame. The Frame struct is supposed to parse candump output, which shows the length of the frame in bytes (maximum of 64 bytes for CAN FD) and as such there is no need to modify the Length field to uint16.

Something that needs to be changed instead is the Data type, defined in data.go. There it is hard coded as a slice of length 8, but should be increased to 64 instead.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use an ethernet protocol that can actually send messages >255 bytes in length so the uint8 wasn't sufficient to hold the message length, which is why we changed this - open to suggestions on how you'd want to deal with this

Copy link

@hashemmm96 hashemmm96 Dec 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your ethernet protocol must be some kind of higher-layer protocol which utilizes CAN in the link layer. In that case, I think the best option would be that you implement a new library for your higher-layer protocol which depends on our library. There, you can wrap the Frame type in your own structs to fit your use case. As I see it this is a pure CAN library, thus we should limit the scope to the link-layer aspects and DBC file handling.

I assume you're using the descriptor library to parse signals within your protocol, meaning your signals can be more than 255 bits wide as well. This is something we can implement in this library, since signals don't really follow a standard size structure the same way the CAN and CAN FD protocols do. That will be a breaking change but would make sense to do if we're implementing CAN FD support.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, we actually may be able to extract the message length from the header in our ethernet protocol so we can potentially revert this back to a uint8 - the main issue with > 8 bytes (CAN-FD or arbitrary length) is that we're no longer able to use uint64 to pack the bits so we've resorted to using math.big to handle this which by nature would allows lengths of any size. We can refactor some of our decoding code to deal with a 64 byte payload, it just seems like a more limiting constraint. We see value in keeping the current can.Data struct and associated methods since it's very efficient for messages <= 8 bytes, and we currently use both.

Would your team be open to a meeting on Zoom, etc. to talk through some of our design choices? This may be much simpler than going back-and-forth on the comments here. @jshiv and I are both in PST which is 9 hours behind Sweden but we can work something out if that sounds good on your side.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a great idea to talk "face-to-face" 😄

I sent you a request on LinkedIn, let's exchange contact info there.

Expand Down Expand Up @@ -114,7 +114,7 @@ func (f *Frame) UnmarshalString(s string) error {
if err != nil {
return fmt.Errorf("invalid remote length: %v: %w", s, err)
}
frame.Length = uint8(dataLength)
frame.Length = uint16(dataLength)
}
*f = frame
return nil
Expand All @@ -123,7 +123,7 @@ func (f *Frame) UnmarshalString(s string) error {
if len(dataPart) > 16 || len(dataPart)%2 != 0 {
return fmt.Errorf("invalid data length: %v", s)
}
frame.Length = uint8(len(dataPart) / 2)
frame.Length = uint16(len(dataPart) / 2)
// Parse: Data
decodedData, err := hex.DecodeString(dataPart)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions frame_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
type jsonFrame struct {
ID uint32 `json:"id"`
Data *string `json:"data"`
Length *uint8 `json:"length"`
Length *uint16 `json:"length"`
Extended *bool `json:"extended"`
Remote *bool `json:"remote"`
}
Expand Down Expand Up @@ -69,7 +69,7 @@ func (f *Frame) UnmarshalJSON(jsonData []byte) error {
}
f.Data = Data{}
copy(f.Data[:], data)
f.Length = uint8(len(data))
f.Length = uint16(len(data))
} else {
f.Data = Data{}
f.Length = 0
Expand Down
2 changes: 1 addition & 1 deletion frame_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (Frame) Generate(rand *rand.Rand, size int) reflect.Value {
} else {
f.ID = rand.Uint32() & MaxID
}
f.Length = uint8(rand.Intn(9))
f.Length = uint16(rand.Intn(9))
if !f.IsRemote {
_, _ = rand.Read(f.Data[:f.Length])
}
Expand Down
9 changes: 6 additions & 3 deletions internal/generate/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (c *compiler) collectDescriptors() {
Name: string(def.Name),
ID: def.MessageID.ToCAN(),
IsExtended: def.MessageID.IsExtended(),
Length: uint8(def.Size),
Length: uint16(def.Size),
SenderNode: string(def.Transmitter),
}
for _, signalDef := range def.Signals {
Expand All @@ -73,8 +73,8 @@ func (c *compiler) collectDescriptors() {
IsMultiplexer: signalDef.IsMultiplexerSwitch,
IsMultiplexed: signalDef.IsMultiplexed,
MultiplexerValue: uint(signalDef.MultiplexerSwitch),
Start: uint8(signalDef.StartBit),
Length: uint8(signalDef.Size),
Start: uint16(signalDef.StartBit),
Length: uint16(signalDef.Size),
Scale: signalDef.Factor,
Offset: signalDef.Offset,
Min: signalDef.Minimum,
Expand Down Expand Up @@ -173,6 +173,9 @@ func (c *compiler) addMetadata() {
if def.AttributeName == "GenSigStartValue" {
sig.DefaultValue = int(def.IntValue)
}
if def.AttributeName == "SystemSignalLongSymbol" {
sig.LongName = def.StringValue
}
}
}
}
Expand Down
Loading