gotelem/xbee/api_frame.go

138 lines
4.9 KiB
Go
Raw Normal View History

2023-04-20 20:26:29 +00:00
package xbee
import (
"bytes"
"encoding/binary"
2023-04-25 21:29:49 +00:00
"errors"
2023-04-20 20:26:29 +00:00
"io"
)
2023-05-03 15:38:37 +00:00
// 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.
2023-04-20 20:26:29 +00:00
type Frameable interface {
// returns the API identifier for this frame.
// encodes this frame correctly.
2023-04-29 15:58:56 +00:00
Bytes() []byte
2023-04-20 20:26:29 +00:00
}
2023-04-26 22:24:11 +00:00
// calculateChecksum is a helper function to calculate the 1-byte checksum of a data range.
// the data range does not include the start delimiter, or the length uint16 (only the frame payload)
2023-04-20 20:26:29 +00:00
func calculateChecksum(data []byte) byte {
var sum byte
for _, v := range data {
sum += v
}
return 0xFF - sum
}
2023-04-29 15:58:56 +00:00
// writeXBeeFrame takes some bytes and wraps it in an XBee frame.
//
// An XBee frame has a start delimiter, followed by the length of the payload,
// then the payload itself, and finally a checksum.
func writeXBeeFrame(w io.Writer, data []byte) (n int, err error) {
2023-04-20 20:26:29 +00:00
2023-04-29 15:58:56 +00:00
frame := make([]byte, len(data)+4)
2023-04-20 20:26:29 +00:00
frame[0] = 0x7E
2023-04-29 15:58:56 +00:00
binary.BigEndian.PutUint16(frame[1:], uint16(len(data)))
2023-04-20 20:26:29 +00:00
2023-04-29 15:58:56 +00:00
copy(frame[3:], data)
2023-04-20 20:26:29 +00:00
2023-04-29 15:58:56 +00:00
chk := calculateChecksum(data)
2023-04-20 20:26:29 +00:00
frame[len(frame)-1] = chk
return w.Write(frame)
}
// now we can describe frames in other files that implement Frameable.
// the remaining challenge is reception and actual API frames.
// xbee uses the first byte of the "frame data" as the API identifier or command.
//go:generate stringer -output=api_frame_cmd.go -type xbeeCmd
2023-05-04 19:06:05 +00:00
// XBeeCmd is the frame command type.
2023-04-20 20:26:29 +00:00
type XBeeCmd byte
const (
// commands sent to the xbee s3b
2023-04-29 15:58:56 +00:00
ATCmdType XBeeCmd = 0x08 // AT Command
ATCmdQueueType XBeeCmd = 0x09 // AT Command - Queue Parameter Value
TxReqType XBeeCmd = 0x10 // TX Request
TxReqExplType XBeeCmd = 0x11 // Explicit TX Request
RemoteCmdReqType XBeeCmd = 0x17 // Remote Command Request
2023-04-20 20:26:29 +00:00
// commands recieved from the xbee
2023-04-29 15:58:56 +00:00
ATCmdResponseType XBeeCmd = 0x88 // AT Command Response
ModemStatusType XBeeCmd = 0x8A // Modem Status
TxStatusType XBeeCmd = 0x8B // Transmit Status
RouteInfoType XBeeCmd = 0x8D // Route information packet
AddrUpdateType XBeeCmd = 0x8E // Aggregate Addressing Update
RxPktType XBeeCmd = 0x90 // RX Indicator (AO=0)
RxPktExplType XBeeCmd = 0x91 // Explicit RX Indicator (AO=1)
IOSampleType XBeeCmd = 0x92 // Data Sample RX Indicator
NodeIdType XBeeCmd = 0x95 // Note Identification Indicator
RemoteCmdRespType XBeeCmd = 0x97 // Remote Command Response
2023-04-20 20:26:29 +00:00
)
// Now we will implement receiving packets from a character stream.
// we first need to make a thing that produces frames from a stream using a scanner.
2023-05-04 19:06:05 +00:00
// xbeeFrameSplit is a split function for bufio.scanner. It makes it easier to handle the FSM
2023-04-20 20:26:29 +00:00
// for extracting data from a stream. For the Xbee, this means that we must
// find the magic start character, (check that it's escaped), read the length,
// and then ensure we have enough length to finish the token, requesting more data
// if we do not.
//
// see https://pkg.go.dev/bufio#SplitFunc for more info
// https://medium.com/golangspec/in-depth-introduction-to-bufio-scanner-in-golang-55483bb689b4
func xbeeFrameSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
// there's no data, request more.
return 0, nil, nil
}
if startIdx := bytes.IndexByte(data, 0x7E); startIdx >= 0 {
2023-04-26 22:24:11 +00:00
// we have a start character. see if we can get the length.
2023-04-20 20:26:29 +00:00
// we add 4 since start delimiter (1) + length (2) + checksum (1).
// the length inside the packet represents the frame data only.
2023-04-26 22:24:11 +00:00
if len(data[startIdx:]) < 3 {
2023-05-03 05:29:02 +00:00
// since we don't have enough bytes to get the length, instead we
// will discard all data before the start index
2023-04-26 22:24:11 +00:00
return startIdx, nil, nil
}
// FIXME: add bounds checking! this can panic.
2023-04-29 15:58:56 +00:00
var frameLen = int(binary.BigEndian.Uint16(data[startIdx+1:startIdx+3])) + 4
if len(data[startIdx:]) < frameLen {
2023-04-20 20:26:29 +00:00
// we got the length, but there's not enough data for the frame. we can trim the
// data that came before the start, but not return a token.
return startIdx, nil, nil
}
2023-05-04 19:06:05 +00:00
// there is enough data to pull a frame, so return it.
2023-04-29 15:58:56 +00:00
return startIdx + frameLen, data[startIdx : startIdx+frameLen], nil
2023-04-20 20:26:29 +00:00
}
// we didn't find a start character in our data, so request more. trash everythign given to us
return len(data), nil, nil
}
2023-04-26 22:24:11 +00:00
2023-05-03 05:29:02 +00:00
// parseFrame takes a framed packet and returns the contents after checking the
// checksum and start delimiter.
2023-04-26 22:24:11 +00:00
func parseFrame(frame []byte) ([]byte, error) {
if frame[0] != 0x7E {
return nil, errors.New("incorrect start delimiter")
}
fsize := len(frame)
if calculateChecksum(frame[3:fsize-1]) != frame[fsize-1] {
2023-04-26 22:24:11 +00:00
return nil, errors.New("checksum mismatch")
}
2023-04-29 15:58:56 +00:00
return frame[3 : fsize-1], nil
2023-04-26 22:24:11 +00:00
}
// stackup
// low level readwriter (serial or IP socket)
// XBee library layer (frame encoding/decoding to/from structs)
2023-05-03 05:29:02 +00:00
// AT command layer (for setup/control)
2023-04-26 22:24:11 +00:00
// xbee conn-like layer (ReadWriter + custom control functions)
// application marshalling format (msgpack or json or gob)
2023-04-29 15:58:56 +00:00
// application