263 lines
6.2 KiB
Go
263 lines
6.2 KiB
Go
// SPDX-License-Identifier: 0BSD
|
|
|
|
package interfaces
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
|
)
|
|
|
|
const (
|
|
RNODE_CMD_DATA = 0x00
|
|
RNODE_CMD_FREQUENCY = 0x01
|
|
RNODE_CMD_BANDWIDTH = 0x02
|
|
RNODE_CMD_TXPOWER = 0x03
|
|
RNODE_CMD_SF = 0x04
|
|
RNODE_CMD_CR = 0x05
|
|
RNODE_CMD_RADIO_STATE = 0x06
|
|
RNODE_CMD_RADIO_LOCK = 0x07
|
|
RNODE_CMD_DETECT = 0x08
|
|
RNODE_CMD_LEAVE = 0x0A
|
|
RNODE_CMD_ST_ALOCK = 0x0B
|
|
RNODE_CMD_LT_ALOCK = 0x0C
|
|
RNODE_CMD_READY = 0x0F
|
|
RNODE_CMD_STAT_RX = 0x21
|
|
RNODE_CMD_STAT_TX = 0x22
|
|
RNODE_CMD_STAT_RSSI = 0x23
|
|
RNODE_CMD_STAT_SNR = 0x24
|
|
RNODE_CMD_FW_VERSION = 0x50
|
|
RNODE_CMD_PLATFORM = 0x48
|
|
RNODE_CMD_MCU = 0x49
|
|
|
|
RNODE_DETECT_REQ = 0x73
|
|
RNODE_DETECT_RESP = 0x46
|
|
|
|
RNODE_RSSI_OFFSET = 157
|
|
)
|
|
|
|
// RNodeInterface represents a Reticulum node interface.
|
|
type RNodeInterface struct {
|
|
Interface
|
|
frequency uint32
|
|
bandwidth uint32
|
|
sf uint8
|
|
cr uint8
|
|
txPower uint8
|
|
callback common.PacketCallback
|
|
|
|
rFrequency uint32
|
|
rBandwidth uint32
|
|
rTXPower uint8
|
|
rSF uint8
|
|
rCR uint8
|
|
rState uint8
|
|
rDetected bool
|
|
rMajVer uint8
|
|
rMinVer uint8
|
|
|
|
interfaceReady bool
|
|
packetQueue [][]byte
|
|
}
|
|
|
|
// NewRNodeInterface creates a new RNodeInterface.
|
|
func NewRNodeInterface(name string, underlying Interface, freq uint32, bw uint32, sf uint8, cr uint8, txPower uint8) (*RNodeInterface, error) {
|
|
ri := &RNodeInterface{
|
|
Interface: underlying,
|
|
frequency: freq,
|
|
bandwidth: bw,
|
|
sf: sf,
|
|
cr: cr,
|
|
txPower: txPower,
|
|
}
|
|
|
|
underlying.SetPacketCallback(ri.handleIncoming)
|
|
return ri, nil
|
|
}
|
|
|
|
// SetPacketCallback sets the packet callback for the RNodeInterface.
|
|
func (ri *RNodeInterface) SetPacketCallback(cb common.PacketCallback) {
|
|
ri.callback = cb
|
|
}
|
|
|
|
func (ri *RNodeInterface) handleIncoming(data []byte, ni common.NetworkInterface) {
|
|
if len(data) < 1 {
|
|
return
|
|
}
|
|
|
|
cmd := data[0]
|
|
payload := data[1:]
|
|
|
|
switch cmd {
|
|
case RNODE_CMD_DATA:
|
|
if ri.callback != nil {
|
|
ri.callback(payload, ri)
|
|
}
|
|
case RNODE_CMD_READY:
|
|
ri.processQueue()
|
|
case RNODE_CMD_DETECT:
|
|
if len(payload) >= 1 && payload[0] == RNODE_DETECT_RESP {
|
|
ri.rDetected = true
|
|
}
|
|
case RNODE_CMD_FW_VERSION:
|
|
if len(payload) >= 2 {
|
|
ri.rMajVer = payload[0]
|
|
ri.rMinVer = payload[1]
|
|
debug.Log(debug.DEBUG_INFO, "RNode firmware version", "name", ri.GetName(), "version", fmt.Sprintf("%d.%d", ri.rMajVer, ri.rMinVer))
|
|
}
|
|
case RNODE_CMD_FREQUENCY:
|
|
if len(payload) >= 4 {
|
|
ri.rFrequency = binary.BigEndian.Uint32(payload)
|
|
}
|
|
case RNODE_CMD_BANDWIDTH:
|
|
if len(payload) >= 4 {
|
|
ri.rBandwidth = binary.BigEndian.Uint32(payload)
|
|
}
|
|
case RNODE_CMD_TXPOWER:
|
|
if len(payload) >= 1 {
|
|
ri.rTXPower = payload[0]
|
|
}
|
|
case RNODE_CMD_SF:
|
|
if len(payload) >= 1 {
|
|
ri.rSF = payload[0]
|
|
}
|
|
case RNODE_CMD_CR:
|
|
if len(payload) >= 1 {
|
|
ri.rCR = payload[0]
|
|
}
|
|
case RNODE_CMD_RADIO_STATE:
|
|
if len(payload) >= 1 {
|
|
ri.rState = payload[0]
|
|
}
|
|
case RNODE_CMD_STAT_RSSI:
|
|
if len(payload) >= 1 {
|
|
rssi := int(payload[0]) - RNODE_RSSI_OFFSET
|
|
debug.Log(debug.DEBUG_VERBOSE, "RNode RSSI", "name", ri.GetName(), "rssi", rssi)
|
|
}
|
|
case RNODE_CMD_STAT_SNR:
|
|
if len(payload) >= 1 {
|
|
snr := float32(int8(payload[0])) * 0.25
|
|
debug.Log(debug.DEBUG_VERBOSE, "RNode SNR", "name", ri.GetName(), "snr", snr)
|
|
}
|
|
default:
|
|
debug.Log(debug.DEBUG_ALL, "RNode received command", "cmd", fmt.Sprintf("0x%02x", cmd), "len", len(payload))
|
|
}
|
|
}
|
|
|
|
func (ri *RNodeInterface) processQueue() {
|
|
ri.interfaceReady = true
|
|
if len(ri.packetQueue) > 0 {
|
|
packet := ri.packetQueue[0]
|
|
ri.packetQueue = ri.packetQueue[1:]
|
|
_ = ri.Send(packet, "")
|
|
}
|
|
}
|
|
|
|
// Start initializes the RNodeInterface and configures device parameters.
|
|
func (ri *RNodeInterface) Start() error {
|
|
err := ri.Interface.Start()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
if err := ri.detect(); err != nil {
|
|
return err
|
|
}
|
|
|
|
debug.Log(debug.DEBUG_INFO, "Initializing RNode...", "name", ri.GetName())
|
|
|
|
if ri.frequency != 0 {
|
|
freqBytes := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(freqBytes, ri.frequency)
|
|
if err := ri.sendRNodeCommand(RNODE_CMD_FREQUENCY, freqBytes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if ri.bandwidth != 0 {
|
|
bwBytes := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(bwBytes, ri.bandwidth)
|
|
if err := ri.sendRNodeCommand(RNODE_CMD_BANDWIDTH, bwBytes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if ri.sf != 0 {
|
|
if err := ri.sendRNodeCommand(RNODE_CMD_SF, []byte{ri.sf}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if ri.cr != 0 {
|
|
if err := ri.sendRNodeCommand(RNODE_CMD_CR, []byte{ri.cr}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if ri.txPower != 0 {
|
|
if err := ri.sendRNodeCommand(RNODE_CMD_TXPOWER, []byte{ri.txPower}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := ri.sendRNodeCommand(RNODE_CMD_RADIO_STATE, []byte{0x01}); err != nil {
|
|
return err
|
|
}
|
|
|
|
ri.interfaceReady = true
|
|
|
|
debug.Log(debug.DEBUG_INFO, "RNode initialized", "name", ri.GetName())
|
|
return nil
|
|
}
|
|
|
|
// detect attempts to detect the RNode device and obtain firmware version.
|
|
func (ri *RNodeInterface) detect() error {
|
|
detectCmd := []byte{RNODE_DETECT_REQ}
|
|
if err := ri.sendRNodeCommand(RNODE_CMD_DETECT, detectCmd); err != nil {
|
|
return err
|
|
}
|
|
|
|
start := time.Now()
|
|
for !ri.rDetected {
|
|
if time.Since(start) > 2*time.Second {
|
|
debug.Log(debug.DEBUG_ERROR, "RNode detection timed out", "name", ri.GetName())
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
if err := ri.sendRNodeCommand(RNODE_CMD_FW_VERSION, []byte{0x00}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// sendRNodeCommand sends a command to the RNode device.
|
|
func (ri *RNodeInterface) sendRNodeCommand(cmd byte, data []byte) error {
|
|
if kissInterface, ok := ri.Interface.(interface {
|
|
SendKISS(byte, []byte) error
|
|
}); ok {
|
|
return kissInterface.SendKISS(cmd, data)
|
|
}
|
|
|
|
frame := make([]byte, 0, len(data)+1)
|
|
frame = append(frame, cmd)
|
|
frame = append(frame, data...)
|
|
return ri.Interface.Send(frame, "")
|
|
}
|
|
|
|
// Send transmits data using the underlying interface.
|
|
func (ri *RNodeInterface) Send(data []byte, addr string) error {
|
|
if !ri.interfaceReady {
|
|
ri.packetQueue = append(ri.packetQueue, data)
|
|
return nil
|
|
}
|
|
return ri.Interface.Send(data, addr)
|
|
}
|