initial commit
This commit is contained in:
commit
39ccfb0ff2
186
can/can.go
Normal file
186
can/can.go
Normal file
|
@ -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")
|
||||
}
|
58
can/can_test.go
Normal file
58
can/can_test.go
Normal file
|
@ -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")
|
||||
}
|
||||
})
|
||||
|
||||
}
|
22
cmd/root.go
Normal file
22
cmd/root.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
11
cmd/server.go
Normal file
11
cmd/server.go
Normal file
|
@ -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",
|
||||
}
|
14
go.mod
Normal file
14
go.mod
Normal file
|
@ -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
|
||||
)
|
10
go.sum
Normal file
10
go.sum
Normal file
|
@ -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=
|
9
main.go
Normal file
9
main.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/kschamplin/gotelem/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
0
xbee/at.go
Normal file
0
xbee/at.go
Normal file
19
xbee/frame.go
Normal file
19
xbee/frame.go
Normal file
|
@ -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
|
||||
)
|
0
xbee/frame_test.go
Normal file
0
xbee/frame_test.go
Normal file
Loading…
Reference in a new issue