diff --git a/internal/can/frame.go b/internal/can/frame.go index 7f26ca3..87803b5 100644 --- a/internal/can/frame.go +++ b/internal/can/frame.go @@ -14,6 +14,12 @@ type Frame struct { Kind Kind } +type CANFrame interface { + Id() uint32 + Data() []byte + Type() Kind +} + //go:generate stringer -output=frame_kind.go -type Kind // Kind is the type of the can Frame diff --git a/internal/mprpc/rpc.go b/internal/mprpc/rpc.go index 82cea36..6165f20 100644 --- a/internal/mprpc/rpc.go +++ b/internal/mprpc/rpc.go @@ -20,10 +20,13 @@ Notify() the callback service with the new event as an argument every time it occured. While this may be less optimal than protocol-level streaming, it is far simpler. +# Generic Helper Functions + The idiomatic way to use mprpc is to use the generic functions that are provided -as helpers. They allow the programmer to easily wrap functions in a closure that -automatically encodes and decodes the parameters and results to their -MessagePack representations. See the Make* generic functions for more information. +as helpers. They allow the programmer to easily wrap existing functions in a +closure that automatically encodes and decodes the parameters and results to +their MessagePack representations. See the Make* generic functions for more +information. // Assume myParam and myResult are MessagePack-enabled structs. // Use `msgp` to generate the required functions for them. @@ -37,6 +40,9 @@ MessagePack representations. See the Make* generic functions for more informatio The generic functions allow for flexiblity and elegant code while still keeping the underlying implementation reflect-free. For more complex functions (i.e multiple parameters or return types), a second layer of indirection can be used. + +There is also a `MakeCaller` function that can make a stub function that handles +encoding the arguments and decoding the response for a remote procedure. */ package mprpc @@ -155,6 +161,7 @@ func (rpc *RPCConn) Serve() { } } +// dispatch is an internal method used to execute a Request sent by the remote:w func (rpc *RPCConn) dispatch(req Request) { result, err := rpc.handlers[req.Method](req.Params) @@ -172,6 +179,9 @@ func (rpc *RPCConn) dispatch(req Request) { response.EncodeMsg(w) } + +// dispatchNotif is like dispatch, but for Notifications. This means that it never replies, +// even if there is an error. func (rpc *RPCConn) dispatchNotif(req Notification) { _, err := rpc.handlers[req.Method](req.Params) @@ -201,7 +211,7 @@ func MakeService[T, R msgpackObject](fn func(T) (R, error)) ServiceFunc { return func(p msgp.Raw) (msgp.Raw, error) { // decode the raw data into a new underlying type. var params T - // TODO: handler errors + _, err := params.UnmarshalMsg(p) if err != nil { @@ -236,8 +246,13 @@ func MakeService[T, R msgpackObject](fn func(T) (R, error)) ServiceFunc { func MakeCaller[T, R msgpackObject]() func(string, T, *RPCConn) (R, error) { return func(method string, param T, rpc *RPCConn) (R, error) { - rawParam, _ := param.MarshalMsg([]byte{}) + rawParam, err := param.MarshalMsg([]byte{}) + if err != nil { + var emtpyR R + return emtpyR, err + } rawResponse, err := rpc.Call(method, rawParam) + if err != nil { var emtpyR R return emtpyR, err diff --git a/internal/socketcan/socketcan.go b/internal/socketcan/socketcan.go index 13a10c3..9a60797 100644 --- a/internal/socketcan/socketcan.go +++ b/internal/socketcan/socketcan.go @@ -1,3 +1,6 @@ +/* +socketcan provides a wrapper around the Linux socketCAN interface. +*/ package socketcan import ( @@ -18,12 +21,13 @@ type CanSocket struct { fd int } +// standardFrameSize is the full size in bytes of the default CAN frame. const standardFrameSize = unix.CAN_MTU // we use the base CAN_MTU since the FD MTU is not in sys/unix. but we know it's +64-8 bytes const fdFrameSize = unix.CAN_MTU + 56 -// Constructs a new CanSocket and binds it to the interface given by ifname +// NewCanSocket constructs a new CanSocket and binds it to the interface given by ifname func NewCanSocket(ifname string) (*CanSocket, error) { var sck CanSocket @@ -52,17 +56,17 @@ func NewCanSocket(ifname string) (*CanSocket, error) { return &sck, nil } -// Closes the socket. +// Close closes the socket. func (sck *CanSocket) Close() error { return unix.Close(sck.fd) } -// get the name of the socket. +// Name returns the name of the socket. func (sck *CanSocket) Name() string { return sck.iface.Name } -// Sets if error packets should be sent upstream +// SetErrFilter sets if error packets should be sent upstream func (sck *CanSocket) SetErrFilter(shouldFilter bool) error { var err error @@ -114,6 +118,7 @@ func (sck *CanSocket) SetFilters(filters []can.CanFilter) error { } +// Send sends a CAN frame func (sck *CanSocket) Send(msg *can.Frame) error { buf := make([]byte, fdFrameSize) diff --git a/internal/socketcan/socketcan_test.go b/internal/socketcan/socketcan_test.go index f039074..fc16013 100644 --- a/internal/socketcan/socketcan_test.go +++ b/internal/socketcan/socketcan_test.go @@ -6,35 +6,8 @@ import ( "testing" "github.com/kschamplin/gotelem/internal/can" - "golang.org/x/sys/unix" ) -func TestMakeFilter(t *testing.T) { - - t.Run("non-invert", func(t *testing.T) { - - filter := can.CanFilter{Id: 0x123, Mask: 0x11, Inverted: true} - - 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 := can.CanFilter{Id: 0x123, Mask: 0x11, Inverted: 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) { if _, err := net.InterfaceByName("vcan0"); err != nil { diff --git a/internal/xbee/api_frame.go b/internal/xbee/api_frame.go index d3ac97b..4b2ef0a 100644 --- a/internal/xbee/api_frame.go +++ b/internal/xbee/api_frame.go @@ -20,6 +20,8 @@ import ( // 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. +// Frameable is an object that can be sent in an XBee Frame. An XBee Frame +// consists of a start delimiter, length, the payload, and a checksum. type Frameable interface { // returns the API identifier for this frame. // encodes this frame correctly. diff --git a/internal/xbee/at.go b/internal/xbee/at.go index 33545fb..09f1cdc 100644 --- a/internal/xbee/at.go +++ b/internal/xbee/at.go @@ -36,7 +36,7 @@ func (b RawATCmd) Bytes() []byte { // EncodeATCommand takes an AT command and encodes it in the payload format. // it takes the frame index (which can be zero) as well as if it should be queued or // not. It encodes the AT command to be framed and sent over the wire and returns the packet -func encodeATCommand(at ATCmd, idx uint8, queued bool) RawATCmd { +func encodeATCommand(cmd [2]rune, p []byte, idx uint8, queued bool) RawATCmd { // we encode a new byte slice that contains the cmd + payload concatenated correclty. // this is then used to make the command frame, which contains ID/Type/Queued or not. // the ATCmdFrame can be converted to bytes to be sent over the wire once framed. @@ -53,11 +53,9 @@ func encodeATCommand(at ATCmd, idx uint8, queued bool) RawATCmd { // next is the provided frame identifier, used for callbacks. buf.WriteByte(idx) - cmd := at.Cmd() buf.WriteByte(byte(cmd[0])) buf.WriteByte(byte(cmd[1])) // the payload can be empty. This would make it a query. - p := at.Payload() buf.Write(p) return buf.Bytes() @@ -139,22 +137,3 @@ func encodeRemoteATCommand(at ATCmd, idx uint8, queued bool, destination uint64) // let's actually define some AT commands now. // TODO: should we just use a function. -// the AT command for the ID (Network ID). -// the network identifier is used to communicate with other devices. It must match. -type ATCmdID struct { - id uint32 - isQuery bool -} - -func (cmd ATCmdID) Cmd() [2]rune { - return [2]rune{'I', 'D'} -} - -func (cmd ATCmdID) Payload() []byte { - if cmd.isQuery { - return []byte{} - } - res := make([]byte, 0) - res = binary.BigEndian.AppendUint32(res, cmd.id) - return res -} diff --git a/internal/xbee/at_test.go b/internal/xbee/at_test.go new file mode 100644 index 0000000..85749f3 --- /dev/null +++ b/internal/xbee/at_test.go @@ -0,0 +1,108 @@ +package xbee + +import ( + "reflect" + "testing" +) + +func TestParseATCmdResponse(t *testing.T) { + type args struct { + p []byte + } + tests := []struct { + name string + args args + want *ATCmdResponse + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseATCmdResponse(tt.args.p) + if (err != nil) != tt.wantErr { + t.Errorf("ParseATCmdResponse() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseATCmdResponse() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_encodeRemoteATCommand(t *testing.T) { + type args struct { + at ATCmd + idx uint8 + queued bool + destination uint64 + } + tests := []struct { + name string + args args + want RawATCmd + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := encodeRemoteATCommand(tt.args.at, tt.args.idx, tt.args.queued, tt.args.destination); !reflect.DeepEqual(got, tt.want) { + t.Errorf("encodeRemoteATCommand() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_encodeATCommand(t *testing.T) { + type args struct { + cmd [2]rune + p []byte + idx uint8 + queued bool + } + tests := []struct { + name string + args args + want RawATCmd + }{ + // These test cases are from digi's documentation on the 900HP/XSC modules. + { + name: "Setting AT Command", + args: args{ + cmd: [2]rune{'N', 'I'}, + idx: 0xA1, + p: []byte{0x45, 0x6E, 0x64, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65}, + queued: false, + }, + want: []byte{0x08, 0xA1, 0x4E, 0x49, 0x45, 0x6E, 0x64, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65}, + }, + { + name: "Query AT Command", + args: args{ + cmd: [2]rune{'T', 'P'}, + idx: 0x17, + p: nil, + queued: false, + }, + want: []byte{0x08, 0x17, 0x54, 0x50}, + }, + { + name: "Queue Local AT Command", + args: args{ + cmd: [2]rune{'B', 'D'}, + idx: 0x53, + p: []byte{0x07}, + queued: true, + }, + want: []byte{0x09, 0x53, 0x42, 0x44, 0x07}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := encodeATCommand(tt.args.cmd, tt.args.p, tt.args.idx, tt.args.queued); !reflect.DeepEqual(got, tt.want) { + t.Errorf("encodeATCommand() = %v, want %v", got, tt.want) + } + }) + } +}