From f58938ea6b8660d388e15cbb929903e34903f16b Mon Sep 17 00:00:00 2001 From: saji Date: Tue, 25 Apr 2023 16:29:49 -0500 Subject: [PATCH] update xbee --- internal/xbee/api_frame.go | 104 +++---------------------------------- internal/xbee/at.go | 70 +++++++++++++++++++++++++ internal/xbee/conntrack.go | 54 +++++++++++++++++++ internal/xbee/rxframe.go | 0 internal/xbee/txframe.go | 40 ++++++++++++++ 5 files changed, 170 insertions(+), 98 deletions(-) create mode 100644 internal/xbee/conntrack.go create mode 100644 internal/xbee/rxframe.go create mode 100644 internal/xbee/txframe.go diff --git a/internal/xbee/api_frame.go b/internal/xbee/api_frame.go index cd55c3c..6b49e32 100644 --- a/internal/xbee/api_frame.go +++ b/internal/xbee/api_frame.go @@ -8,8 +8,10 @@ package xbee import ( "bytes" "encoding/binary" + "errors" "fmt" "io" + "sync" ) // the frames have an outer shell - we will make a function that takes @@ -25,6 +27,10 @@ type Frameable interface { Bytes() ([]byte, error) } +// we also need a way of tracking frame IDs - this is a uint8 +// it uses a sync.Map + + // now we can describe our function that takes a framable and contains it + calculates checksums. func calculateChecksum(data []byte) byte { var sum byte @@ -84,104 +90,6 @@ const ( // AT commands are hard, so let's write out all the major ones here -type ATCmdFrame struct { - Id byte - Cmd string - Param []byte - Queued bool -} - -// implement the frame stuff for us. -func (atFrame *ATCmdFrame) Bytes() ([]byte, error) { - buf := new(bytes.Buffer) - - if atFrame.Queued { - // queued (batched) at comamnds have different Frame type - buf.WriteByte(byte(ATCmdQueue)) - - } else { - // normal frame type - buf.WriteByte(byte(ATCmd)) - - } - - buf.WriteByte(atFrame.Id) - - // write cmd, if it's the right length. - if cmdLen := len(atFrame.Cmd); cmdLen != 2 { - return nil, fmt.Errorf("AT command incorrect length: %d", cmdLen) - } - buf.Write([]byte(atFrame.Cmd)) - - // write param. - buf.Write(atFrame.Param) - return buf.Bytes(), nil -} - -// transmissions to this address are instead broadcast -const BroadcastAddr = 0xFFFF - -type TxFrame struct { - Id byte - Destination uint64 - BCastRadius uint8 - Options uint8 - Payload []byte -} - -func (txFrame *TxFrame) Bytes() ([]byte, error) { - buf := new(bytes.Buffer) - - buf.WriteByte(byte(TxReq)) - - buf.WriteByte(txFrame.Id) - - a := make([]byte, 8) - binary.LittleEndian.PutUint64(a, txFrame.Destination) - buf.Write(a) - - // write the reserved part. - buf.Write([]byte{0xFF, 0xFE}) - - // write the radius - buf.WriteByte(txFrame.BCastRadius) - - buf.WriteByte(txFrame.Options) - - buf.Write(txFrame.Payload) - - return buf.Bytes(), nil -} - -type RemoteATCmdReq struct { - ATCmdFrame - Destination uint64 - Options uint8 -} - -func (remoteAT *RemoteATCmdReq) Bytes() ([]byte, error) { - buf := new(bytes.Buffer) - buf.WriteByte(byte(RemoteCmdReq)) - - buf.WriteByte(remoteAT.Id) - - a := make([]byte, 8) - binary.LittleEndian.PutUint64(a, remoteAT.Destination) - buf.Write(a) - - // write the reserved part. - buf.Write([]byte{0xFF, 0xFE}) - // write options - buf.WriteByte(remoteAT.Options) - - // now, write the AT command and the data. - buf.Write([]byte(remoteAT.Cmd)) - - buf.Write(remoteAT.Param) - - return buf.Bytes(), nil - -} // 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. diff --git a/internal/xbee/at.go b/internal/xbee/at.go index 6de1d7b..b838d65 100644 --- a/internal/xbee/at.go +++ b/internal/xbee/at.go @@ -1,3 +1,73 @@ package xbee +import ( + "bytes" + "fmt" + "encoding/binary" +) + // this file contains some AT command constants and +type ATCmdFrame struct { + Id byte + Cmd string + Param []byte + Queued bool +} + +// implement the frame stuff for us. +func (atFrame *ATCmdFrame) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + + if atFrame.Queued { + // queued (batched) at comamnds have different Frame type + buf.WriteByte(byte(ATCmdQueue)) + + } else { + // normal frame type + buf.WriteByte(byte(ATCmd)) + + } + + buf.WriteByte(atFrame.Id) + + // write cmd, if it's the right length. + if cmdLen := len(atFrame.Cmd); cmdLen != 2 { + return nil, fmt.Errorf("AT command incorrect length: %d", cmdLen) + } + buf.Write([]byte(atFrame.Cmd)) + + // write param. + buf.Write(atFrame.Param) + return buf.Bytes(), nil +} + + +type RemoteATCmdReq struct { + ATCmdFrame + Destination uint64 + Options uint8 +} + +func (remoteAT *RemoteATCmdReq) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + buf.WriteByte(byte(RemoteCmdReq)) + + buf.WriteByte(remoteAT.Id) + + a := make([]byte, 8) + binary.LittleEndian.PutUint64(a, remoteAT.Destination) + buf.Write(a) + + // write the reserved part. + buf.Write([]byte{0xFF, 0xFE}) + // write options + buf.WriteByte(remoteAT.Options) + + // now, write the AT command and the data. + buf.Write([]byte(remoteAT.Cmd)) + + buf.Write(remoteAT.Param) + + return buf.Bytes(), nil + +} diff --git a/internal/xbee/conntrack.go b/internal/xbee/conntrack.go new file mode 100644 index 0000000..1c87b26 --- /dev/null +++ b/internal/xbee/conntrack.go @@ -0,0 +1,54 @@ +package xbee +import ( + "sync" + "errors" +) + +// A connTrack is a simple frame mark utility for xbee packets. The xbee api frame +// takes a mark that is used when sending the response - this allows to coordinate +// the sent packet and the response, since there may be other packets emitted +// between them. +type connTrack struct { + mu sync.RWMutex // use RW mutex to allow for multiple readers + internal map[uint8]bool // map frame tag to if it's been used. + // the map is set when writing a frame, and deleted when recieving a matching frame. +} + +// GetMark finds the next available marker and takes it, returning the value of +// the mark. If no mark can be acquired, it returns an error. +func (ct *connTrack) GetMark() (uint8, error) { + // get a read lock. + ct.mu.RLock() + + // NOTE: we start at one. This is because 0 will not return a frame ever - it's + // the "silent mode" mark + for i := 1; i < 256; i++ { + if !ct.internal[uint8(i)] { + // it's free. + // discard our read lock and lock for write. + ct.mu.RUnlock() + + ct.mu.Lock() + // update the value to true. + ct.internal[uint8(i)] = true + ct.mu.Unlock() + return uint8(i), nil + } + } + ct.mu.RUnlock() + return 0, errors.New("no available marks") +} +// ClearMark removes a given mark from the set if it exists, or returns an error. +func (ct *connTrack) ClearMark(mark uint8) error { + ct.mu.RLock() + // FIXME: should this be the other way around (swap if and normal execution + if ct.internal[mark] { + ct.mu.RUnlock() + ct.mu.Lock() + delete(ct.internal, mark) + ct.mu.Unlock() + return nil + } + ct.mu.RUnlock() + return errors.New("mark was not set") +} diff --git a/internal/xbee/rxframe.go b/internal/xbee/rxframe.go new file mode 100644 index 0000000..e69de29 diff --git a/internal/xbee/txframe.go b/internal/xbee/txframe.go new file mode 100644 index 0000000..3fd0f37 --- /dev/null +++ b/internal/xbee/txframe.go @@ -0,0 +1,40 @@ +package xbee + +import ( + "bytes" + "encoding/binary" +) +// transmissions to this address are instead broadcast +const BroadcastAddr = 0xFFFF + +type TxFrame struct { + Id byte + Destination uint64 + BCastRadius uint8 + Options uint8 + Payload []byte +} + +func (txFrame *TxFrame) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + + buf.WriteByte(byte(TxReq)) + + buf.WriteByte(txFrame.Id) + + a := make([]byte, 8) + binary.LittleEndian.PutUint64(a, txFrame.Destination) + buf.Write(a) + + // write the reserved part. + buf.Write([]byte{0xFF, 0xFE}) + + // write the radius + buf.WriteByte(txFrame.BCastRadius) + + buf.WriteByte(txFrame.Options) + + buf.Write(txFrame.Payload) + + return buf.Bytes(), nil +}