cleanup docs, tests, housekeeping

This commit is contained in:
saji 2023-05-03 10:38:37 -05:00
parent aa5cd0b393
commit 59760b79dd
7 changed files with 146 additions and 58 deletions

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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 {

View file

@ -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.

View file

@ -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
}

108
internal/xbee/at_test.go Normal file
View file

@ -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)
}
})
}
}