Skip to content

Commit

Permalink
Update SDO canopen implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabian Petersen committed Nov 10, 2021
1 parent 8f397a2 commit 6a687db
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 147 deletions.
8 changes: 1 addition & 7 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ package canopen
import (
"github.com/FabianPetersen/can"
"github.com/jpillora/maplock"
"strconv"
"time"
)

var m = maplock.New()
var Lock = maplock.New()

// A Client handles message communication by sending a request
// and waiting for the response.
Expand All @@ -19,11 +18,6 @@ type Client struct {
// Do sends a request and waits for a response.
// If the response frame doesn't arrive on time, an error is returned.
func (c *Client) Do(req *Request) (*Response, error) {
// Do not allow multiple messages for the same device
key := strconv.Itoa(int(req.ResponseID))
m.Lock(key)
defer m.Unlock(key)

rch := can.Wait(c.Bus, req.ResponseID, c.Timeout)

if err := c.Bus.Publish(req.Frame.CANFrame()); err != nil {
Expand Down
216 changes: 129 additions & 87 deletions sdo/download.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
package sdo

import (
"github.com/FabianPetersen/can"
"github.com/FabianPetersen/canopen"
"strconv"

"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/FabianPetersen/can"
"github.com/FabianPetersen/canopen"
"log"
"time"
)

const (
ClientIntiateDownload = 0x20 // 0010 0000
ClientSegmentDownload = 0x00 // 0110 0000
DownloadInitiateRequest = 0x20 // 0010 0000
DownloadInitiateResponse = 0x60 // 0110 0000

ServerInitiateDownload = 0x60 // 0110 0000
ServerSegmentDownload = 0x20 // 0010 0000
DownloadSegmentRequest = 0x00 // 0000 0000
DownloadSegmentResponse = 0x20 // 0010 0000
)

// Download represents a SDO download process to write data to a CANopen
Expand All @@ -30,105 +31,146 @@ type Download struct {
}

func (download Download) Do(bus *can.Bus) error {
c := &canopen.Client{bus, time.Second * 2}
data := download.Data

e := TransferExpedited
s := TransferSizeIndicated
n := byte(0)
if size := int32(len(data)); size > 4 {
e = 0
n = 0

var buf bytes.Buffer
if err := binary.Write(&buf, binary.LittleEndian, size); err != nil {
return err
} else {
data = buf.Bytes()
}
} else {
n = byte(size)
}
// Do not allow multiple messages for the same device
key := strconv.Itoa(int(download.RequestCobID))
canopen.Lock.Lock(key)
defer canopen.Lock.Unlock(key)

// If valid "n" indicates the number of bytes in d that do not contain data. Hence 4 -
bytes := []byte{
byte(ClientIntiateDownload | e | s | (((4 - int(n)) << 2) & TransferMaskSize)),
download.ObjectIndex.Index.B0, download.ObjectIndex.Index.B1,
download.ObjectIndex.SubIndex,
if err := download.doInit(bus); err != nil {
return err
}

// CiA301 Standard expects all (8) bytes to be sent
for len(data) < 4 {
data = append(data, 0x0)
}
return download.doSegments(bus)
}

// Initiate
frame := canopen.Frame{
CobID: download.RequestCobID,
Data: append(bytes[:], data[:]...),
func (download Download) doInit(bus *can.Bus) error {
frame, err := download.initFrame()
if err != nil {
return err
}

req := canopen.NewRequest(frame, uint32(download.ResponseCobID))
c := &canopen.Client{bus, time.Second * 2}
resp, err := c.Do(req)
if err != nil {
log.Print(err)
return err
}

frame = resp.Frame
b0 := frame.Data[0] // == 0100 nnes
scs := b0 & TransferMaskCommandSpecifier
switch scs {
case ServerInitiateDownload:
break
case TransferAbort:
return errors.New("Server aborted download")
switch scs := frame.Data[0] >> 5; scs {
case 3: // response
return nil
case 4: // abort
return errors.New("server aborted download initialization")
default:
log.Fatalf("Unexpected server command specifier %X", scs)
return fmt.Errorf("unexpected server command specifier %X (expected %X)", scs, 3)
}
}

// initFrame returns the initial frame of the download.
// If the download data is less than 4 bytes, the init frame data contains all download data.
// If the download data is more than 4 bytes, the init frame data contains the overall length of the download data.
func (download Download) initFrame() (frame canopen.Frame, err error) {
fdata := make([]byte, 8)

// css = 1 (download init request)
fdata[0] = setBit(fdata[0], 5)
fdata[1] = download.ObjectIndex.Index.B0
fdata[2] = download.ObjectIndex.Index.B1
fdata[3] = download.ObjectIndex.SubIndex

n := uint8(len(download.Data) & 0x3)
if n <= 4 { // does download data fit into one frame?
// e = 1 (expedited)
fdata[0] = setBit(fdata[0], 1)
// s = 1
fdata[0] = setBit(fdata[0], 0)
// n = number of unused bytes in frame.Data
fdata[0] |= (4 - n) << 2
// copy all download data into frame data
copy(fdata[3:], download.Data)
} else {
// e = 0
// n = 0 (frame.Data contains the overall )
// s = 1
fdata[0] = setBit(fdata[0], 0)

var buf bytes.Buffer
if err = binary.Write(&buf, binary.LittleEndian, uint32(n)); err != nil {
return
}

// copy overall length of download data into frame data
copy(fdata[:4], buf.Bytes())
}

if e == 0 {
junks := splitN(download.Data, 7)
t := 0
for i, junk := range junks {
cmd := byte(ClientSegmentDownload)

if t%2 == 1 {
cmd |= TransferSegmentToggle
}

t += 1

// is last segmented
if i == len(junks)-1 {
cmd |= 0x1
}

// CiA301 Standard expects all (8) bytes to be sent
for len(junk) < 7 {
data = append(junk, 0x0)
}

frame = canopen.Frame{
CobID: download.RequestCobID,
Data: append([]byte{cmd}, junk[:]...),
}

req = canopen.NewRequest(frame, uint32(download.ResponseCobID))
resp, err = c.Do(req)

if err != nil {
return err
}

// Segment response
frame := resp.Frame
if scs := frame.Data[0] & TransferMaskCommandSpecifier; scs != ServerSegmentDownload {
return fmt.Errorf("Invalid scs %X != %X\n", scs, ServerSegmentDownload)
}
frame.CobID = download.RequestCobID
frame.Data = fdata

return
}

func (download Download) doSegments(bus *can.Bus) error {
frames := download.segmentFrames()

c := &canopen.Client{bus, time.Second * 2}
for _, frame := range frames {
req := canopen.NewRequest(frame, uint32(download.ResponseCobID))
resp, err := c.Do(req)
if err != nil {
return err
}

switch scs := resp.Frame.Data[0] >> 5; scs {
case 1:
break
case 4:
return errors.New("server aborted download")
default:
return fmt.Errorf("unexpected server command specifier %X (expected %X)", scs, 1)
}

// check toggle bit
if hasBit(frame.Data[0], 4) != hasBit(resp.Frame.Data[0], 4) {
return fmt.Errorf("unexpected toggle bit %t", hasBit(resp.Frame.Data[0], 4))
}
}

return nil
}

func (download Download) segmentFrames() (frames []canopen.Frame) {
if len(download.Data) <= 4 {
return
}

junks := splitN(download.Data, 7)
for i, junk := range junks {
fdata := make([]byte, 8)

if len(junk) < 7 {
fdata[0] |= uint8(7-len(junk)) << 1
}

if i%2 == 1 {
// toggle bit 5
fdata[0] = setBit(fdata[0], 4)
}

if i == len(junks)-1 {
// c = 1 (no more segments to download)
fdata[0] = setBit(fdata[0], 0)
}

copy(fdata[1:], junk)

frame := canopen.Frame{
CobID: download.RequestCobID,
Data: fdata,
}

frames = append(frames, frame)
}

return
}
10 changes: 5 additions & 5 deletions sdo/download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ func (rw *downloadReadWriteCloser) Write(b []byte) (n int, err error) {
return 0, err
}

switch frm.Data[0] & TransferMaskCommandSpecifier {
case ClientIntiateDownload:
switch frm.Data[0] >> 5 {
case 1: // initiate
frm = downloadInitiateFrame
case ClientSegmentDownload:
case 0: // segment
frm = downloadSegmentFrame
default:
log.Fatal("Unknown command")
Expand All @@ -49,7 +49,7 @@ var downloadInitiateFrame = can.Frame{
Res0: 0x0,
Res1: 0x0,
Data: [can.MaxFrameDataLength]uint8{
ServerInitiateDownload,
3 << 5,
0xBB, 0xAA,
0xCC,
0x0, 0x0, 0x0, 0x0},
Expand All @@ -62,7 +62,7 @@ var downloadSegmentFrame = can.Frame{
Res0: 0x0,
Res1: 0x0,
Data: [can.MaxFrameDataLength]uint8{
ServerSegmentDownload,
1 << 5, /* scs */
0xBB, 0xAA,
0xCC,
0x0, 0x0, 0x0, 0x0},
Expand Down
Loading

0 comments on commit 6a687db

Please sign in to comment.