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 Kind Kind
} }
type CANFrame interface {
Id() uint32
Data() []byte
Type() Kind
}
//go:generate stringer -output=frame_kind.go -type Kind //go:generate stringer -output=frame_kind.go -type Kind
// Kind is the type of the can Frame // 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 occured. While this may be less optimal than protocol-level streaming, it is
far simpler. far simpler.
# Generic Helper Functions
The idiomatic way to use mprpc is to use the generic functions that are provided 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 as helpers. They allow the programmer to easily wrap existing functions in a
automatically encodes and decodes the parameters and results to their closure that automatically encodes and decodes the parameters and results to
MessagePack representations. See the Make* generic functions for more information. their MessagePack representations. See the Make* generic functions for more
information.
// Assume myParam and myResult are MessagePack-enabled structs. // Assume myParam and myResult are MessagePack-enabled structs.
// Use `msgp` to generate the required functions for them. // 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 generic functions allow for flexiblity and elegant code while still keeping
the underlying implementation reflect-free. For more complex functions (i.e the underlying implementation reflect-free. For more complex functions (i.e
multiple parameters or return types), a second layer of indirection can be used. 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 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) { func (rpc *RPCConn) dispatch(req Request) {
result, err := rpc.handlers[req.Method](req.Params) result, err := rpc.handlers[req.Method](req.Params)
@ -172,6 +179,9 @@ func (rpc *RPCConn) dispatch(req Request) {
response.EncodeMsg(w) 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) { func (rpc *RPCConn) dispatchNotif(req Notification) {
_, err := rpc.handlers[req.Method](req.Params) _, 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) { return func(p msgp.Raw) (msgp.Raw, error) {
// decode the raw data into a new underlying type. // decode the raw data into a new underlying type.
var params T var params T
// TODO: handler errors
_, err := params.UnmarshalMsg(p) _, err := params.UnmarshalMsg(p)
if err != nil { 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) { func MakeCaller[T, R msgpackObject]() func(string, T, *RPCConn) (R, error) {
return func(method string, param T, rpc *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) rawResponse, err := rpc.Call(method, rawParam)
if err != nil { if err != nil {
var emtpyR R var emtpyR R
return emtpyR, err return emtpyR, err

View file

@ -1,3 +1,6 @@
/*
socketcan provides a wrapper around the Linux socketCAN interface.
*/
package socketcan package socketcan
import ( import (
@ -18,12 +21,13 @@ type CanSocket struct {
fd int fd int
} }
// standardFrameSize is the full size in bytes of the default CAN frame.
const standardFrameSize = unix.CAN_MTU 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 // 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 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) { func NewCanSocket(ifname string) (*CanSocket, error) {
var sck CanSocket var sck CanSocket
@ -52,17 +56,17 @@ func NewCanSocket(ifname string) (*CanSocket, error) {
return &sck, nil return &sck, nil
} }
// Closes the socket. // Close 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. // Name returns the name of the socket.
func (sck *CanSocket) Name() string { func (sck *CanSocket) Name() string {
return sck.iface.Name 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 { func (sck *CanSocket) SetErrFilter(shouldFilter bool) error {
var err 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 { func (sck *CanSocket) Send(msg *can.Frame) error {
buf := make([]byte, fdFrameSize) buf := make([]byte, fdFrameSize)

View file

@ -6,35 +6,8 @@ import (
"testing" "testing"
"github.com/kschamplin/gotelem/internal/can" "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) { func TestCanSocket(t *testing.T) {
if _, err := net.InterfaceByName("vcan0"); err != nil { 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 // 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. // 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 { type Frameable interface {
// returns the API identifier for this frame. // returns the API identifier for this frame.
// encodes this frame correctly. // 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. // 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 // 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 // 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. // 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. // 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. // 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. // next is the provided frame identifier, used for callbacks.
buf.WriteByte(idx) buf.WriteByte(idx)
cmd := at.Cmd()
buf.WriteByte(byte(cmd[0])) buf.WriteByte(byte(cmd[0]))
buf.WriteByte(byte(cmd[1])) buf.WriteByte(byte(cmd[1]))
// the payload can be empty. This would make it a query. // the payload can be empty. This would make it a query.
p := at.Payload()
buf.Write(p) buf.Write(p)
return buf.Bytes() 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. // let's actually define some AT commands now.
// TODO: should we just use a function. // 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)
}
})
}
}