Files
ham/protocol/meshcore/node.go
2026-02-17 23:30:49 +01:00

342 lines
8.0 KiB
Go

package meshcore
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"log"
"strings"
"sync"
"git.maze.io/go/ham/protocol"
"git.maze.io/go/ham/protocol/meshcore/crypto"
)
const (
maxCompanionFrameSize = 172
)
type Node struct {
OnPacket (*Packet)
driver nodeDriver
}
type NodeInfo struct {
Manufacturer string `json:"manufacturer"`
FirmwareVersion string `json:"firmware_version"`
Type NodeType `json:"node_type"`
Name string `json:"name"`
Power uint8 `json:"power"`
MaxPower uint8 `json:"max_power"`
PublicKey *crypto.PublicKey `json:"public_key"`
Position *Position `json:"position"`
Frequency float64 `json:"frequency"` // in MHz
Bandwidth float64 `json:"bandwidth"` // in kHz
SpreadingFactor uint8 `json:"sf"`
CodingRate uint8 `json:"cr"`
}
// NewCompanion connects to a companion device type (over serial, TCP or BLE).
func NewCompanion(conn io.ReadWriteCloser) (*Node, error) {
driver := newCompanionDriver(conn)
if err := driver.Setup(); err != nil {
return nil, err
}
return &Node{
driver: driver,
}, nil
}
func (dev *Node) Close() error {
return dev.driver.Close()
}
func (dev *Node) Packets() <-chan *Packet {
return dev.driver.Packets()
}
func (dev *Node) RawPackets() <-chan *protocol.Packet {
return dev.driver.RawPackets()
}
func (dev *Node) Info() *NodeInfo {
return dev.driver.Info()
}
type nodeDriver interface {
Setup() error
Close() error
Packets() <-chan *Packet
RawPackets() <-chan *protocol.Packet
Info() *NodeInfo
}
type CompanionError struct {
Code byte
}
func (err CompanionError) Error() string {
switch err.Code {
case companionErrCodeUnsupported:
return "meshcore: companion: unsupported"
case companionErrCodeNotFound:
return "meshcore: companion: not found"
case companionErrCodeTableFull:
return "meshcore: companion: table full"
case companionErrCodeBadState:
return "meshcore: companion: bad state"
case companionErrCodeFileIOError:
return "meshcore: companion: file input/output error"
case companionErrCodeIllegalArgument:
return "meshcore: companion: illegal argument"
default:
return fmt.Sprintf("meshcore: companion: unknown error code %#02x", err.Code)
}
}
type companionDriver struct {
conn io.ReadWriteCloser
mu sync.Mutex
packets chan *Packet
rawPackets chan *protocol.Packet
info NodeInfo
}
func newCompanionDriver(conn io.ReadWriteCloser) *companionDriver {
return &companionDriver{
conn: conn,
}
}
func (drv *companionDriver) Close() error {
return drv.conn.Close()
}
func (drv *companionDriver) Setup() (err error) {
if err = drv.sendAppStart(); err != nil {
return
}
if err = drv.sendDeviceInfo(); err != nil {
return
}
go drv.poll()
return
}
func (drv *companionDriver) Packets() <-chan *Packet {
if drv.packets == nil {
drv.packets = make(chan *Packet, 16)
}
return drv.packets
}
func (drv *companionDriver) RawPackets() <-chan *protocol.Packet {
if drv.rawPackets == nil {
drv.rawPackets = make(chan *protocol.Packet, 16)
}
return drv.rawPackets
}
func (drv *companionDriver) Info() *NodeInfo {
return &drv.info
}
func (drv *companionDriver) readFrame() ([]byte, error) {
var frame [3 + maxCompanionFrameSize]byte
for {
n, err := drv.conn.Read(frame[:])
if err != nil {
return nil, err
} else if n < 3 {
continue
}
if frame[0] != '>' {
// not a companion frame
continue
}
size := int(binary.LittleEndian.Uint16(frame[1:]))
if size > maxCompanionFrameSize {
return nil, fmt.Errorf("meshcore: companion sent frame size of %d, which exceeds maximum of %d", size, maxCompanionFrameSize)
}
// Make sure we have read all bytes
o := n
for (o - 3) < size {
if n, err = drv.conn.Read(frame[o:]); err != nil {
return nil, err
}
o += n
}
//log.Printf("read %d:\n%s", size, hex.Dump(frame[:3+size]))
return frame[3 : 3+size], nil
}
}
func (drv *companionDriver) writeFrame(b []byte) (err error) {
if len(b) > maxCompanionFrameSize {
return fmt.Errorf("meshcore: companion: frame size %d exceed maximum of %d", len(b), maxCompanionFrameSize)
}
var frame [3 + maxCompanionFrameSize]byte
frame[0] = '<'
binary.LittleEndian.PutUint16(frame[1:], uint16(len(b)))
n := copy(frame[3:], b)
//log.Printf("send %d:\n%s", n, hex.Dump(frame[:3+n]))
_, err = drv.conn.Write(frame[:3+n])
return
}
func (drv *companionDriver) writeCommand(cmd byte, args []byte, wait ...byte) ([]byte, error) {
drv.mu.Lock()
defer drv.mu.Unlock()
if err := drv.writeFrame(append([]byte{cmd}, args...)); err != nil {
return nil, err
}
return drv.wait(wait...)
}
func (drv *companionDriver) wait(wait ...byte) ([]byte, error) {
for {
b, err := drv.readFrame()
if err != nil {
return nil, err
}
if len(b) < 1 {
continue
}
switch {
case b[0] == companionResponseError:
return nil, CompanionError{Code: b[1]}
case b[0] >= 0x80:
drv.handlePushFrame(b)
continue
case bytes.Contains(wait, b[:1]):
return b, nil
case wait == nil:
return b, nil
}
}
}
func (drv *companionDriver) handlePushFrame(b []byte) {
switch b[0] {
case companionPushAdvert:
case companionPushMessageWaiting:
case companionPushLogRXData:
drv.handleRXData(b[1:])
}
}
func (drv *companionDriver) handleRXData(b []byte) {
if len(b) < 2+minPacketSize {
return
}
if drv.packets == nil {
return // not listening for packets, discard
}
packet := new(Packet)
if err := packet.UnmarshalBytes(b[2:]); err == nil {
packet.SNR = float64(b[0]) / 4
packet.RSSI = int8(b[1])
select {
case drv.packets <- packet:
default:
log.Printf("meshcore: packet channel full, dropping packet")
}
if drv.rawPackets != nil {
select {
case drv.rawPackets <- &protocol.Packet{
Protocol: "meshcore",
SNR: packet.SNR,
RSSI: packet.RSSI,
Raw: packet.Raw,
}:
default:
log.Printf("meshcore: raw packet channel full, dropping packet")
}
}
}
}
func (drv *companionDriver) sendAppStart() (err error) {
var b []byte
if b, err = drv.writeCommand(companionAppStart, append(make([]byte, 8), []byte("git.maze.io/go/ham")...), companionResponseSelfInfo); err != nil {
return fmt.Errorf("meshcore: can't send application start: %v", err)
}
log.Printf("companion app start response:\n%s", hex.Dump(b))
const expect = 1 + 1 + 1 + 1 + 32 + 4 + 4 + 1 + 1 + 1 + 1 + 4 + 4 + 1 + 1
if len(b) < expect {
return fmt.Errorf("companion: expected %d bytes of self info, got %d", expect, len(b))
}
if b[0] != companionResponseSelfInfo {
return fmt.Errorf("companion: expected self info response, got %#02x", b[0])
}
b = b[1:]
drv.info.Type = NodeType(b[0])
drv.info.Power = b[1]
drv.info.MaxPower = b[2]
drv.info.PublicKey, _ = crypto.NewPublicKey(b[3 : 3+crypto.PublicKeySize])
drv.info.Position = new(Position)
drv.info.Position.Unmarshal(b[35:])
//drv.info.HasMultiACKs = b[43] != 0
//drv.info.AdvertLocationPolicy = b[44]
//drv.info.TelemetryFlags = b[45]
//drv.info.ManualAddContacts = b[46]
drv.info.Frequency = decodeFrequency(b[47:])
drv.info.Bandwidth = decodeFrequency(b[51:])
drv.info.SpreadingFactor = b[55]
drv.info.CodingRate = b[56]
drv.info.Name = strings.TrimRight(string(b[57:]), "\x00")
return
}
func (drv *companionDriver) sendDeviceInfo() (err error) {
var b []byte
if b, err = drv.writeCommand(companionDeviceQuery, []byte{0x03}, companionResponseDeviceInfo); err != nil {
return
}
const expect = 4 + 4 + 12 + 40 + 20
if len(b) < expect {
return fmt.Errorf("companion: expected %d bytes of self info, got %d", expect, len(b))
}
if b[0] != companionResponseDeviceInfo {
return fmt.Errorf("companion: expected device info response, got %#02x", b[0])
}
b = b[1:]
drv.info.Manufacturer = decodeCString(b[19:59])
drv.info.FirmwareVersion = decodeCString(b[59:79])
return
}
func (drv *companionDriver) poll() {
for {
if _, err := drv.wait(); err != nil {
log.Printf("meshcore: companion %s fatal error: %v", drv.info.Name, err)
return
}
}
}