gotelem/xbee/at.go

147 lines
3.9 KiB
Go
Raw Normal View History

2023-04-15 15:03:35 +00:00
package xbee
2023-04-25 21:29:49 +00:00
import (
"bytes"
"encoding/binary"
2023-05-04 19:05:37 +00:00
"errors"
2023-04-25 21:29:49 +00:00
)
2023-04-29 15:58:56 +00:00
// This code is handled slightly differently. Rather than use structs to represent
// the frame data, we instead use an interface that represents an AT command, and
// standalone functions that convert the AT command to frame data.
// this way, we can add command support without needing to compose structs.
// The downside is that it's more difficult to deal with.
//
// AT command responses are handled with a struct to get the response status code.
// an ATCmd is anything that has a Payload function that returns the given payload
// pre-formatted to be send over the wire, and a Cmd command that returns the 2-character
// ATcommand itself. It must also have a parse response function that takes the response struct.
type ATCmd interface {
Payload() []byte // converts the AT command options to the binary argument format
Cmd() [2]rune // returns the AT command string.
Parse(*ATCmdResponse) error // takes a command response and parses the data into itself.
2023-04-25 21:29:49 +00:00
}
2023-04-29 15:58:56 +00:00
// The AT command, in its raw format. we don't handle parameters since it's dealt with by
// the interface.
type RawATCmd []byte
2023-04-25 21:29:49 +00:00
2023-04-29 15:58:56 +00:00
// implements frameable. this is kinda stupid but makes it more safe.
func (b RawATCmd) Bytes() []byte {
return b
}
2023-04-25 21:29:49 +00:00
2023-04-29 15:58:56 +00:00
// 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
2023-05-03 15:38:37 +00:00
func encodeATCommand(cmd [2]rune, p []byte, idx uint8, queued bool) RawATCmd {
2023-04-29 15:58:56 +00:00
// 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.
2023-04-25 21:29:49 +00:00
2023-04-29 15:58:56 +00:00
buf := new(bytes.Buffer)
// we change the frame type based on if it's queued.
if queued {
buf.WriteByte(byte(ATCmdQueueType))
} else {
buf.WriteByte(byte(ATCmdType))
2023-04-25 21:29:49 +00:00
}
2023-04-29 15:58:56 +00:00
// next is the provided frame identifier, used for callbacks.
buf.WriteByte(idx)
buf.WriteByte(byte(cmd[0]))
buf.WriteByte(byte(cmd[1]))
// the payload can be empty. This would make it a query.
buf.Write(p)
return buf.Bytes()
}
type ATCmdResponse struct {
Cmd string
Status ATCmdStatus
Data []byte
}
2023-04-25 21:29:49 +00:00
2023-04-29 15:58:56 +00:00
func ParseATCmdResponse(p []byte) (*ATCmdResponse, error) {
if p[0] != 0x88 {
2023-05-04 19:05:37 +00:00
return nil, errors.New("not an AT command response")
}
if len(p) < 5 {
return nil, errors.New("received partial AT command response")
2023-04-29 15:58:56 +00:00
}
resp := &ATCmdResponse{
Cmd: string(p[2:4]),
Status: ATCmdStatus(p[4]),
2023-05-04 19:05:37 +00:00
Data: p[5:],
2023-04-25 21:29:49 +00:00
}
2023-04-29 15:58:56 +00:00
return resp, nil
2023-04-25 21:29:49 +00:00
}
2023-04-29 15:58:56 +00:00
//go:generate stringer -output=at_cmd_status.go -type ATCmdStatus
type ATCmdStatus uint8
const (
ATCmdStatusOK ATCmdStatus = 0
ATCmdStatusErr ATCmdStatus = 1
ATCmdStatusInvalidCmd ATCmdStatus = 2
ATCmdStatusInvalidParam ATCmdStatus = 3
)
2023-04-25 21:29:49 +00:00
type RemoteATCmdReq struct {
Destination uint64
Options uint8
}
2023-04-29 15:58:56 +00:00
func encodeRemoteATCommand(at ATCmd, idx uint8, queued bool, destination uint64) RawATCmd {
// sizing take from
2023-04-25 21:29:49 +00:00
buf := new(bytes.Buffer)
2023-04-29 15:58:56 +00:00
buf.WriteByte(byte(RemoteCmdReqType))
buf.WriteByte(idx)
binary.Write(buf, binary.BigEndian, destination)
2023-04-25 21:29:49 +00:00
2023-04-29 15:58:56 +00:00
binary.Write(buf, binary.BigEndian, uint16(0xFFFE))
2023-04-25 21:29:49 +00:00
2023-04-29 15:58:56 +00:00
// set remote command options. if idx = 0, we set bit zero (disable ack)
// if queued is true, we clear bit one (if false we set it)
2023-04-25 21:29:49 +00:00
2023-04-29 15:58:56 +00:00
var options uint8 = 0
if idx == 0 {
options = options | 0x1
}
if !queued {
options = options | 0x2
}
2023-05-01 14:49:47 +00:00
buf.WriteByte(options)
2023-04-25 21:29:49 +00:00
2023-04-29 15:58:56 +00:00
// write AT command
cmd := at.Cmd()
buf.WriteByte(byte(cmd[0]))
buf.WriteByte(byte(cmd[1]))
2023-04-25 21:29:49 +00:00
2023-04-29 15:58:56 +00:00
// write payload.
buf.Write(at.Payload())
2023-04-25 21:29:49 +00:00
2023-04-29 15:58:56 +00:00
return buf.Bytes()
2023-04-25 21:29:49 +00:00
}
2023-04-29 15:58:56 +00:00
// let's actually define some AT commands now.
2023-05-01 14:49:47 +00:00
2023-05-11 00:02:57 +00:00
type NetworkDevice struct {
Addr XBeeAddr
Identifier string
DeviceType byte
ProfileNum uint16
MfrId uint16
RSSI byte
}