commit 39ccfb0ff2819fa0b720d78f225369aa2f6cd52c Author: saji Date: Fri Apr 14 15:42:16 2023 -0500 initial commit diff --git a/can/can.go b/can/can.go new file mode 100644 index 0000000..a2c283c --- /dev/null +++ b/can/can.go @@ -0,0 +1,186 @@ +package can + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "net" + + "golang.org/x/sys/unix" +) + +// this file implements a simple wrapper around linux socketCAN +type CanSocket struct { + iface *net.Interface + addr *unix.SockaddrCAN + fd int +} + +type Frame struct { + ID uint32 + Data []uint8 + Kind Kind +} + +//internal frame structure for socketcan with padding + +type stdFrame struct { + ID uint32 + Len uint8 + _pad, _res1 uint8 // padding + Dlc uint8 + Data [8]uint8 +} + +func Marshal(f Frame) (*bytes.Buffer, error) { + + if len(f.Data) > 8 && f.Kind == SFF { + return nil, errors.New("data too large for std frame") + } + if len(f.Data) > 64 && f.Kind == EFF { + return nil, errors.New("data too large for extended frame") + } + + var idflags uint32 = f.ID + if f.Kind == EFF { + idflags = idflags | unix.CAN_EFF_FLAG + } else if f.Kind == RTR { + idflags = idflags | unix.CAN_RTR_FLAG + } else if f.Kind == 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 +} + +//go:generate stringer -output=frame_kind.go -type Kind +type Kind uint8 + +const ( + SFF Kind = iota // Standard Frame Format + EFF // Extended Frame + RTR // remote transmission requests + ERR // Error frame. +) + +// 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 + +// the hack. +const extendedFrameSize = unix.CAN_MTU + 56 + +// Constructs a new CanSocket and creates a file descriptor for it. +func NewCanSocket(ifname string) (*CanSocket, error) { + + var sck CanSocket + fd, err := unix.Socket(unix.AF_CAN, unix.SOCK_RAW, unix.CAN_RAW) + if err != nil { + return nil, err + } + + sck.fd = fd + + iface, err := net.InterfaceByName(ifname) + + if err != nil { + return nil, err + } + + sck.iface = iface + + sck.addr = &unix.SockaddrCAN{Ifindex: sck.iface.Index} + + err = unix.Bind(sck.fd, sck.addr) + if err != nil { + return nil, err + } + + return &sck, nil +} + +// close the socket file descriptor, freeing it from the system. +func (sck *CanSocket) Close() error { + return unix.Close(sck.fd) +} + +// get the name of the socket, or nil if it hasn't been bound yet. +func (sck *CanSocket) Name() string { + return sck.iface.Name +} + +// should we log errors? +func (sck *CanSocket) SetErrFilter(shouldFilter bool) error { + + var err error + var errmask = 0 + if shouldFilter { + errmask = unix.CAN_ERR_MASK + } + + err = unix.SetsockoptInt(sck.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_ERR_FILTER, errmask) + if err != nil { + return err + } + return nil +} + +func (sck *CanSocket) SetFilters(filters []unix.CanFilter) error { + return unix.SetsockoptCanRawFilter(sck.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_FILTER, filters) + +} + +func (sck *CanSocket) Send(msg Frame) error { + // convert our abstract frame into a real unix frame and then push it. + // check return value to raise errors. + buf, err := Marshal(msg) + + if err != nil { + return fmt.Errorf("error sending frame: %w", err) + } + + if buf.Len() != unix.CAN_MTU { + return fmt.Errorf("socket send: buffer size mismatch %d", buf.Len()) + } + // send the buffer using unix syscalls! + + err = unix.Send(sck.fd, buf.Bytes(), 0) + if err != nil { + return fmt.Errorf("error sending frame: %w", err) + } + + return nil +} + +func (sck *CanSocket) Recv() (*Frame, error) { + return nil, errors.New("not implemented") +} diff --git a/can/can_test.go b/can/can_test.go new file mode 100644 index 0000000..7bdf117 --- /dev/null +++ b/can/can_test.go @@ -0,0 +1,58 @@ +package can + +import ( + "testing" + + "golang.org/x/sys/unix" +) + +func TestMakeFilter(t *testing.T) { + + t.Run("non-invert", func(t *testing.T) { + + filter := MakeFilter(0x123, 0x11, false) + + if filter.Id != 0x123 { + t.Errorf("expected %d, got %d", 0x123, filter.Id) + } + if filter.Mask != 0x11 { + t.Errorf("expected %d, got %d", 0x11, filter.Mask) + } + }) + t.Run("invert", func(t *testing.T) { + + filter := MakeFilter(0x123, 0x11, true) + + if filter.Id != 0x123|unix.CAN_INV_FILTER { + t.Errorf("expected %d, got %d", 0x123|unix.CAN_INV_FILTER, filter.Id) + } + if filter.Mask != 0x11 { + t.Errorf("expected %d, got %d", 0x11, filter.Mask) + } + }) +} + +func TestCanSocket(t *testing.T) { + + t.Run("test construction and destruction", func(t *testing.T) { + sock, err := NewCanSocket("vcan0") + if err != nil { + t.Errorf("could not make socket: %v", err) + } + if sock.fd == 0 { + t.Errorf("socket was not made: expected non-zero, got %d", sock.fd) + } + if err := sock.Close(); err != nil { + t.Errorf("could not close socket") + } + }) + + t.Run("test name", func(t *testing.T) { + sock, _ := NewCanSocket("vcan0") + + if sock.Name() != "vcan0" { + t.Errorf("incorrect interface name: got %s, expected %s", sock.Name(), "vcan0") + } + }) + +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..c9fefcb --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func Execute() { + app := &cli.App{ + Name: "gotelem", + Usage: "see everything", + Commands: []*cli.Command{ + serveCmd, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 0000000..2bf0058 --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "github.com/urfave/cli/v2" +) + +var serveCmd = &cli.Command{ + Name: "serve", + Aliases: []string{"server", "s"}, + Usage: "Start a telemetry server", +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..286a5ee --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/kschamplin/gotelem + +go 1.20 + +require ( + github.com/urfave/cli/v2 v2.25.1 + golang.org/x/sys v0.7.0 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..eec3cde --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw= +github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main b/main new file mode 100755 index 0000000..5bc3ed9 Binary files /dev/null and b/main differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..7dd5a2c --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/kschamplin/gotelem/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/xbee/at.go b/xbee/at.go new file mode 100644 index 0000000..e69de29 diff --git a/xbee/frame.go b/xbee/frame.go new file mode 100644 index 0000000..4204e4d --- /dev/null +++ b/xbee/frame.go @@ -0,0 +1,19 @@ +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 +) diff --git a/xbee/frame_test.go b/xbee/frame_test.go new file mode 100644 index 0000000..e69de29