cleanup docs, tests, housekeeping
This commit is contained in:
parent
aa5cd0b393
commit
59760b79dd
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
108
internal/xbee/at_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue