finish socketcan rework, make xbee helpers

This commit is contained in:
saji 2023-04-16 02:46:41 -05:00
parent 01a47aebea
commit ba288f2959
6 changed files with 238 additions and 125 deletions

View file

@ -1,8 +1,8 @@
package can package can
type Frame struct { type Frame struct {
ID uint32 Id uint32
Data []uint8 Data []byte
Kind Kind Kind Kind
} }
@ -10,16 +10,20 @@ type Frame struct {
type Kind uint8 type Kind uint8
const ( const (
SFF Kind = iota // Standard Frame Format SFF Kind = iota // Standard ID Frame
EFF // Extended Frame EFF // Extended ID Frame
RTR // remote transmission requests RTR // Remote Transmission Request Frame
ERR // Error frame. ERR // Error Frame
) )
// for routing flexibility type CanFilter struct {
Id uint32
Mask uint32
Inverted bool
}
type CanSink interface { type CanSink interface {
Send(Frame) error Send(*Frame) error
} }
type CanSource interface { type CanSource interface {

View file

@ -1,7 +1,6 @@
package socketcan package socketcan
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
@ -11,86 +10,20 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
// this file implements a simple wrapper around linux socketCAN // A CanSocket is a CAN device that uses the socketCAN linux drivers to write to real
// CAN hardware.
type CanSocket struct { type CanSocket struct {
iface *net.Interface iface *net.Interface
addr *unix.SockaddrCAN addr *unix.SockaddrCAN
fd int fd int
} }
//internal frame structure for socketcan with padding
type stdFrame struct {
ID uint32
Len uint8
//lint:ignore U1000 these are to make serialization easier
_pad, _res1 uint8 // padding
Dlc uint8
Data [8]uint8
}
func socketCanMarshal(f can.Frame) (*bytes.Buffer, error) {
if len(f.Data) > 8 && f.Kind == can.SFF {
return nil, errors.New("data too large for std frame")
}
if len(f.Data) > 64 && f.Kind == can.EFF {
return nil, errors.New("data too large for extended frame")
}
var idflags uint32 = f.ID
if f.Kind == can.EFF {
idflags = idflags | unix.CAN_EFF_FLAG
} else if f.Kind == can.RTR {
idflags = idflags | unix.CAN_RTR_FLAG
} else if f.Kind == can.ERR {
idflags = idflags | unix.CAN_ERR_FLAG
}
var d [8]uint8
for i := 0; i < len(f.Data); i++ {
d[i] = f.Data[i]
}
var unixFrame stdFrame = stdFrame{
ID: idflags, Len: uint8(len(f.Data)),
Data: d,
}
// finally, write our bytes buffer.
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, unixFrame)
if err != nil {
return nil, err
}
return buf, nil
}
func Unmarshal(f *Frame, buf *bytes.Buffer) error {
return nil
}
// helper function to make a filter.
// id and mask are straightforward, if inverted is true, the filter
// will reject anything that matches.
func MakeFilter(id, mask uint32, inverted bool) *unix.CanFilter {
f := &unix.CanFilter{Id: id, Mask: mask}
if inverted {
f.Id = f.Id | unix.CAN_INV_FILTER
}
return f
}
const standardFrameSize = unix.CAN_MTU const standardFrameSize = unix.CAN_MTU
// the hack. // we use the base CAN_MTU since the FD MTU is not in sys/unix. but we know it's +64-8 bytes
const extendedFrameSize = unix.CAN_MTU + 56 const fdFrameSize = unix.CAN_MTU + 56
// Constructs a new CanSocket and creates a file descriptor for it. // Constructs a new CanSocket and binds it to the interface given by ifname
func NewCanSocket(ifname string) (*CanSocket, error) { func NewCanSocket(ifname string) (*CanSocket, error) {
var sck CanSocket var sck CanSocket
@ -119,12 +52,12 @@ func NewCanSocket(ifname string) (*CanSocket, error) {
return &sck, nil return &sck, nil
} }
// close the socket file descriptor, freeing it from the system. // Closes the socket.
func (sck *CanSocket) Close() error { func (sck *CanSocket) Close() error {
return unix.Close(sck.fd) return unix.Close(sck.fd)
} }
// get the name of the socket, or nil if it hasn't been bound yet. // get the name of the socket.
func (sck *CanSocket) Name() string { func (sck *CanSocket) Name() string {
return sck.iface.Name return sck.iface.Name
} }
@ -139,33 +72,86 @@ func (sck *CanSocket) SetErrFilter(shouldFilter bool) error {
} }
err = unix.SetsockoptInt(sck.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_ERR_FILTER, errmask) err = unix.SetsockoptInt(sck.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_ERR_FILTER, errmask)
if err != nil {
return err return err
}
return nil
} }
// set the filters for the can device. // SetFDMode enables or disables the transmission of CAN FD packets.
func (sck *CanSocket) SetFilters(filters []unix.CanFilter) error { func (sck *CanSocket) SetFDMode(enable bool) error {
return unix.SetsockoptCanRawFilter(sck.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_FILTER, filters) var val int
if enable {
val = 1
} else {
val = 0
}
err := unix.SetsockoptInt(sck.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_FD_FRAMES, val)
return err
} }
func (sck *CanSocket) Send(msg can.Frame) error { // SetFilters will set the socketCAN filters based on a standard CAN filter list.
// convert our abstract frame into a real unix frame and then push it. func (sck *CanSocket) SetFilters(filters []can.CanFilter) error {
// check return value to raise errors.
buf, err := socketCanMarshal(msg)
if err != nil { // helper function to make a filter.
return fmt.Errorf("error sending frame: %w", err) // id and mask are straightforward, if inverted is true, the filter
// will reject anything that matches.
makeFilter := func(filter can.CanFilter) unix.CanFilter {
f := unix.CanFilter{Id: filter.Id, Mask: filter.Mask}
if filter.Inverted {
f.Id = f.Id | unix.CAN_INV_FILTER
}
return f
} }
if buf.Len() != unix.CAN_MTU { convertedFilters := make([]unix.CanFilter, len(filters))
return fmt.Errorf("socket send: buffer size mismatch %d", buf.Len()) for i, filt := range filters {
convertedFilters[i] = makeFilter(filt)
} }
return unix.SetsockoptCanRawFilter(sck.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_FILTER, convertedFilters)
}
func (sck *CanSocket) Send(msg *can.Frame) error {
buf := make([]byte, fdFrameSize)
idToWrite := msg.Id
switch msg.Kind {
case can.SFF:
idToWrite &= unix.CAN_SFF_MASK
case can.EFF:
idToWrite &= unix.CAN_EFF_MASK
idToWrite |= unix.CAN_EFF_FLAG
case can.RTR:
idToWrite |= unix.CAN_RTR_FLAG
default:
return errors.New("you can't send error frames")
}
binary.LittleEndian.PutUint32(buf[:4], idToWrite)
// write the length, it's one byte, so do it directly.
payloadLength := len(msg.Data)
buf[4] = byte(payloadLength)
if payloadLength > 64 {
return fmt.Errorf("payload too large: %d", payloadLength)
}
// copy in the data now.
copy(buf[8:], msg.Data)
// send the buffer using unix syscalls! // send the buffer using unix syscalls!
var err error
err = unix.Send(sck.fd, buf.Bytes(), 0) if payloadLength > 8 {
err = unix.Send(sck.fd, buf, 0)
} else {
err = unix.Send(sck.fd, buf[:standardFrameSize], 0)
}
if err != nil { if err != nil {
return fmt.Errorf("error sending frame: %w", err) return fmt.Errorf("error sending frame: %w", err)
} }
@ -176,10 +162,30 @@ func (sck *CanSocket) Send(msg can.Frame) error {
func (sck *CanSocket) Recv() (*can.Frame, error) { func (sck *CanSocket) Recv() (*can.Frame, error) {
// todo: support extended frames. // todo: support extended frames.
buf := make([]byte, standardFrameSize) buf := make([]byte, fdFrameSize)
unix.Read(sck.fd, buf) _, err := unix.Read(sck.fd, buf)
if err != nil {
return nil, err
}
id := binary.LittleEndian.Uint32(buf[0:4])
var k can.Kind
if id&unix.CAN_EFF_FLAG != 0 {
// extended id frame
k = can.EFF
} else {
// it's a normal can frame
k = can.SFF
}
dataLength := uint8(buf[4])
result := &can.Frame{
Id: id & unix.CAN_EFF_MASK,
Kind: k,
Data: buf[8 : dataLength+8],
}
return result, nil
stdF := &stdFrame{}
binary.Read(bytes.NewBuffer(buf), binary.LittleEndian, stdF)
return nil, errors.New("not implemented")
} }

View file

@ -1,8 +1,11 @@
package socketcan package socketcan
import ( import (
"bytes"
"net"
"testing" "testing"
"github.com/kschamplin/gotelem/can"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -10,7 +13,7 @@ func TestMakeFilter(t *testing.T) {
t.Run("non-invert", func(t *testing.T) { t.Run("non-invert", func(t *testing.T) {
filter := MakeFilter(0x123, 0x11, false) filter := can.CanFilter{Id: 0x123, Mask: 0x11, Inverted: true}
if filter.Id != 0x123 { if filter.Id != 0x123 {
t.Errorf("expected %d, got %d", 0x123, filter.Id) t.Errorf("expected %d, got %d", 0x123, filter.Id)
@ -21,7 +24,7 @@ func TestMakeFilter(t *testing.T) {
}) })
t.Run("invert", func(t *testing.T) { t.Run("invert", func(t *testing.T) {
filter := MakeFilter(0x123, 0x11, true) filter := can.CanFilter{Id: 0x123, Mask: 0x11, Inverted: true}
if filter.Id != 0x123|unix.CAN_INV_FILTER { if filter.Id != 0x123|unix.CAN_INV_FILTER {
t.Errorf("expected %d, got %d", 0x123|unix.CAN_INV_FILTER, filter.Id) t.Errorf("expected %d, got %d", 0x123|unix.CAN_INV_FILTER, filter.Id)
@ -34,6 +37,10 @@ func TestMakeFilter(t *testing.T) {
func TestCanSocket(t *testing.T) { func TestCanSocket(t *testing.T) {
if _, err := net.InterfaceByName("vcan0"); err != nil {
t.Skipf("missing vcan0, skipping socket tests: %v", err)
}
t.Run("test construction and destruction", func(t *testing.T) { t.Run("test construction and destruction", func(t *testing.T) {
sock, err := NewCanSocket("vcan0") sock, err := NewCanSocket("vcan0")
if err != nil { if err != nil {
@ -49,10 +56,54 @@ func TestCanSocket(t *testing.T) {
t.Run("test name", func(t *testing.T) { t.Run("test name", func(t *testing.T) {
sock, _ := NewCanSocket("vcan0") sock, _ := NewCanSocket("vcan0")
defer sock.Close()
if sock.Name() != "vcan0" { if sock.Name() != "vcan0" {
t.Errorf("incorrect interface name: got %s, expected %s", sock.Name(), "vcan0") t.Errorf("incorrect interface name: got %s, expected %s", sock.Name(), "vcan0")
} }
}) })
t.Run("test sending can 2.0 packet", func(t *testing.T) {
sock, _ := NewCanSocket("vcan0")
defer sock.Close()
// make a packet.
testFrame := &can.Frame{
Id: 0x123,
Kind: can.SFF,
Data: []byte{0, 1, 2, 3, 4, 5, 6, 7},
}
err := sock.Send(testFrame)
if err != nil {
t.Error(err)
}
})
t.Run("test receiving a can 2.0 packet", func(t *testing.T) {
sock, _ := NewCanSocket("vcan0")
rsock, _ := NewCanSocket("vcan0")
defer sock.Close()
defer rsock.Close()
testFrame := &can.Frame{
Id: 0x234,
Kind: can.SFF,
Data: []byte{0, 1, 2, 3, 4, 5, 6, 7},
}
_ = sock.Send(testFrame)
rpkt, err := rsock.Recv()
if err != nil {
t.Error(err)
}
if len(rpkt.Data) != 8 {
t.Errorf("length mismatch: got %d expected 8", len(rpkt.Data))
}
if !bytes.Equal(testFrame.Data, rpkt.Data) {
t.Error("data corrupted")
}
})
} }

71
xbee/api_frame.go Normal file
View file

@ -0,0 +1,71 @@
package xbee
import "encoding/binary"
// the frames have an outer shell - we will make a function that takes
// an inner frame element and wraps it in the appropriate headers.
// first, we should make it take the frame directly, so we make an interface
// that represents "framable" things. note that bytes.Buffer also fulfils this.
type Frameable interface {
Bytes() []byte
}
// now we can describe our function that takes a framable and contains it + calculates checksums.
func calculateChecksum(data []byte) byte {
var sum byte
for _, v := range data {
sum += v
}
return 0xFF - sum
}
func makeXbeeApiFrame(cmd Frameable) ([]byte, error) {
dataBuf := cmd.Bytes()
frameBuf := make([]byte, len(dataBuf)+4)
// move data and construct the frame
frameBuf[0] = 0x7E // start delimiter
// length
// todo: check endiannes (0x7e, msb lsb)
binary.LittleEndian.PutUint16(frameBuf[1:3], uint16(len(dataBuf)))
copy(frameBuf[3:], dataBuf)
chksum := calculateChecksum(dataBuf)
frameBuf[len(frameBuf)-1] = chksum
return frameBuf, nil
}
// now we can describe frames in other files that implement Frameable. this makes trasmission complete.
// the remaining challenge is reception and actual API frames.
// xbee uses the first byte of the "frame data" as the API identifier or command.
//go:generate stringer -output=api_frame_cmd.go -type xbeeCmd
type xbeeCmd byte
const (
// commands sent to the xbee s3b
ATCmd xbeeCmd = 0x08 // AT Command
ATCmdQueuePVal xbeeCmd = 0x09 // AT Command - Queue Parameter Value
TxReq xbeeCmd = 0x10 // TX Request
TxReqExpl xbeeCmd = 0x11 // Explicit TX Request
RemoteCmdReq xbeeCmd = 0x17 // Remote Command Request
// commands recieved from the xbee
ATCmdResponse xbeeCmd = 0x88 // AT Command Response
ModemStatus xbeeCmd = 0x8A // Modem Status
TxStatus xbeeCmd = 0x8B // Transmit Status
RouteInfoPkt xbeeCmd = 0x8D // Route information packet
AddrUpdate xbeeCmd = 0x8E // Aggregate Addressing Update
RxPkt xbeeCmd = 0x90 // RX Indicator (AO=0)
RxPktExpl xbeeCmd = 0x91 // Explicit RX Indicator (AO=1)
IOSample xbeeCmd = 0x92 // Data Sample RX Indicator
NodeId xbeeCmd = 0x95 // Note Identification Indicator
RemoteCmdResp xbeeCmd = 0x97 // Remote Command Response
)

View file

@ -1,19 +0,0 @@
package xbee
const (
// commands sent to the xbee s3b
ATCmd = 0x08
ATCmdQueueParameterValue = 0x09
TxReq = 0x10
TxReqExpl = 0x11
RemoteCmdReq = 0x17
// commands recieved from the xbee
ATCmdResponse = 0x88
ModemStatus = 0x8A
TxStatus = 0x8B
RouteInfoPkt = 0x8D
AddrUpdate = 0x8E
RxPkt = 0x90
RxPktExpl = 0x91
IOSample = 0x92
)