Compare commits

...

7 Commits

Author SHA1 Message Date
Joel Wetzell 76cf8af4a5 update with latest drivers changes 2026-05-09 09:00:41 -05:00
Joel Wetzell f0f9262ba5 update example 2026-05-09 08:58:02 -05:00
Joel Wetzell c980d14d9f remote esp32s3 lockup example 2026-05-09 08:46:40 -05:00
Joel Wetzell 755ecf3829 mess around with a sort node concept for communicating over packet RF things 2026-05-01 11:01:20 -05:00
Joel Wetzell c24e49c0e1 go mod tidy 2026-04-27 19:07:49 -05:00
Joel Wetzell 5c0c9d0a93 example to take LoRa packet and output over UDP 2026-04-27 19:06:01 -05:00
Joel Wetzell cf5fcf7432 update for latest sx128x driver changes 2026-04-27 19:05:40 -05:00
9 changed files with 456 additions and 145 deletions
-3
View File
@@ -1,3 +0,0 @@
module esp32s3lockup
go 1.26.2
-35
View File
@@ -1,35 +0,0 @@
package main
import (
"machine"
"time"
)
func main() {
time.Sleep(3 * time.Second)
led := machine.GPIO37
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
txDone := true
count := 0
processingCount := 0
for {
if txDone {
txDone = false
led.Set(false)
time.Sleep(249 * time.Millisecond)
led.Set(true)
println("hello world", count)
count += 1
} else {
if processingCount > 100000000 {
txDone = true
println("stall done")
processingCount = 0
}
processingCount += 1
}
}
}
+17
View File
@@ -0,0 +1,17 @@
module lilygot3s3
go 1.26.2
require (
tinygo.org/x/drivers v0.35.0
tinygo.org/x/espradio v0.1.0
)
require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/soypat/lneto v0.1.1-0.20260425023453-aa77403a2b32 // indirect
)
replace tinygo.org/x/drivers => /Users/jwetzell/Projects/drivers
replace tinygo.org/x/espradio => /Users/jwetzell/Projects/espradio
+4
View File
@@ -0,0 +1,4 @@
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/soypat/lneto v0.1.1-0.20260425023453-aa77403a2b32 h1:4fNa4mcNPVTMJ3Kzn+cS/LqmpXhKM9SiZ9Vgu/Ucq3c=
github.com/soypat/lneto v0.1.1-0.20260425023453-aa77403a2b32/go.mod h1:Be5PjwoYukvHFiUXxpYi8+ppH2F/gw/vjGBvFdv+Ti8=
+254
View File
@@ -0,0 +1,254 @@
package main
import (
"machine"
"net"
"runtime"
"sync/atomic"
"time"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/netdev"
nl "tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/sx128x"
link "tinygo.org/x/espradio/netlink"
)
var (
ssid string
password string
)
type LoRaConfig struct {
Frequency uint32
Power int8
RadioRamp sx128x.RadioRampTime
RegulatorMode sx128x.RegulatorMode
SpreadingFactor sx128x.LoRaSpreadingFactor
Bandwidth sx128x.LoRaBandwidth
CodingRate sx128x.LoRaCodingRate
PreambleLength uint32
HeaderType sx128x.LoRaHeaderType
CrcType sx128x.LoRaCrcType
IqType sx128x.LoRaIqType
SyncWord uint16
}
func main() {
time.Sleep(3 * time.Second)
link := link.Esplink{}
netdev.UseNetdev(&link)
println("Connecting to WiFi...")
err := link.NetConnect(&nl.ConnectParams{
Ssid: ssid,
Passphrase: password,
})
if err != nil {
panic("connect failed: " + err.Error())
}
conn, err := net.Dial("udp", "10.0.0.50:53000")
if err != nil {
panic("dial failed: " + err.Error())
}
defer conn.Close()
println("Connected to WiFi.")
led := machine.GPIO37
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
spi := machine.SPI0
spi.Configure(machine.SPIConfig{
Mode: 0,
Frequency: 8 * 1e6,
SDO: machine.GPIO6,
SDI: machine.GPIO3,
SCK: machine.GPIO5,
})
nssPin := machine.GPIO7
nssPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
nssPin.Set(true)
resetPin := machine.GPIO8
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Set(true)
busyPin := machine.GPIO36
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
println("initializing radio...")
radio := sx128x.New(
spi,
nssPin,
resetPin,
busyPin,
)
dio1Pin := machine.GPIO9
dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInput})
txDone := atomic.Bool{}
txDone.Store(true)
rxDone := atomic.Bool{}
rxDone.Store(true)
timeout := atomic.Bool{}
timeout.Store(true)
dio1Interrupt := atomic.Bool{}
dio1Interrupt.Store(false)
dio1Pin.SetInterrupt(machine.PinRising, func(machine.Pin) {
dio1Interrupt.Store(true)
})
loraConfig := LoRaConfig{
Frequency: 2400000000, // 2.4Ghz
Power: 13, // dBm
RadioRamp: sx128x.RADIO_RAMP_02_US, // 2 microsecond ramp time
RegulatorMode: sx128x.REGULATOR_DC_DC,
SpreadingFactor: sx128x.LORA_SF_9,
Bandwidth: sx128x.LORA_BW_1600,
CodingRate: sx128x.LORA_CR_4_7,
PreambleLength: 12,
HeaderType: sx128x.LORA_HEADER_EXPLICIT,
CrcType: sx128x.LORA_CRC_DISABLE,
IqType: sx128x.LORA_IQ_STD,
SyncWord: 0x1424, // the default private sync word
}
radio.WaitWhileBusy(time.Second)
SetupLora(radio, loraConfig)
println("radio initialized, waiting for messages...")
for {
runtime.Gosched()
if dio1Interrupt.Load() {
// println("DIO1 interrupt triggered")
irqStatus, _ := radio.GetIrqStatus()
println("irq status:", irqStatus)
if irqStatus&sx128x.IRQ_TX_DONE_MASK != 0 {
txDone.Store(true)
timeout.Store(false)
led.Set(false)
}
if irqStatus&sx128x.IRQ_RX_DONE_MASK != 0 {
rxDone.Store(true)
timeout.Store(false)
led.Set(true)
println("rx done")
}
if irqStatus&sx128x.IRQ_RX_TX_TIMEOUT_MASK != 0 {
timeout.Store(true)
txDone.Store(true)
rxDone.Store(true)
println("rx/tx timeout")
}
dio1Interrupt.Store(false)
}
if rxDone.Load() {
rxDone.Store(false)
if !timeout.Load() {
rxLength, rxPointer, _ := radio.GetRxBufferStatus()
rxData, _ := radio.ReadBuffer(rxPointer, rxLength)
// num, err := conn.Write(rxData)
// if err != nil {
// println("failed to send OSC message:", err)
// } else {
// println("sent", num, "bytes to broker")
// }
println("received message:", string(rxData))
led.Set(false)
}
Rx(radio, loraConfig)
println("radio set to receive")
} else {
if dio1Pin.Get() {
irqStatus, _ := radio.GetIrqStatus()
if irqStatus&sx128x.IRQ_TX_DONE_MASK != 0 {
txDone.Store(true)
timeout.Store(false)
led.Set(false)
}
if irqStatus&sx128x.IRQ_RX_DONE_MASK != 0 {
rxDone.Store(true)
timeout.Store(false)
led.Set(true)
}
if irqStatus&sx128x.IRQ_RX_TX_TIMEOUT_MASK != 0 {
timeout.Store(true)
txDone.Store(true)
rxDone.Store(true)
}
}
}
}
}
func SetupLora(radio *sx128x.Device, config LoRaConfig) {
// Switch to standby prior to configuration changes
circuitMode, _, _ := radio.GetStatus()
if circuitMode != sx128x.CIRCUIT_MODE_STDBY_RC {
radio.SetStandby(sx128x.STANDBY_RC)
}
// Clear errors, disable radio interrupts for the moment
radio.SetPacketType(sx128x.PACKET_TYPE_LORA)
radio.SetCadParams(uint8(sx128x.LORA_CAD_08_SYMBOLS))
radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC)
radio.SetRfFrequency(config.Frequency)
radio.SetModulationParamsLoRa(config.SpreadingFactor, config.Bandwidth, config.CodingRate)
data := [1]uint8{}
if config.SpreadingFactor == lora.SpreadingFactor5 || config.SpreadingFactor == lora.SpreadingFactor6 {
data[0] = 0x1E
} else if config.SpreadingFactor == lora.SpreadingFactor7 || config.SpreadingFactor == lora.SpreadingFactor8 {
data[0] = 0x37
} else {
data[0] = 0x32
}
radio.WriteRegister(0x925, data[:])
existing, _ := radio.ReadRegister(0x93C)
data[0] = existing | 0x01
radio.WriteRegister(0x93C, data[:])
radio.SetTxParams(config.Power, sx128x.RADIO_RAMP_02_US)
radio.SetPacketParamsLoRa(config.PreambleLength, config.HeaderType, 0xFF, config.CrcType, config.IqType)
var syncWord [2]uint8
syncWord[0] = uint8(config.SyncWord >> 8)
syncWord[1] = uint8(config.SyncWord & 0x00FF)
radio.WriteRegister(sx128x.REG_LORA_SYNC_WORD_MSB, syncWord[:])
}
func checkStatus(radio *sx128x.Device, operation string) {
circuitMode, commandStatus, _ := radio.GetStatus()
if commandStatus != sx128x.COMMAND_STATUS_SUCCESS {
println(operation, "-> failed with status:", commandStatus)
}
if circuitMode != sx128x.CIRCUIT_MODE_STDBY_RC {
println(operation, "-> entered circuit mode:", circuitMode)
}
}
func Rx(radio *sx128x.Device, loraConfig LoRaConfig) {
radio.SetStandby(sx128x.STANDBY_XOSC)
radio.SetBufferBaseAddress(0, 0)
radio.SetDioIrqParams(sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00)
radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK)
err := radio.SetRx(sx128x.PERIOD_BASE_4_MS, 250)
if err != nil {
println("failed to set RX mode:", err)
}
}
+65 -100
View File
@@ -7,10 +7,24 @@ import (
"time" "time"
"github.com/jwetzell/osc-go" "github.com/jwetzell/osc-go"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/sx128x" "tinygo.org/x/drivers/sx128x"
) )
type LoRaConfig struct {
Frequency uint32
Power int8
RadioRamp sx128x.RadioRampTime
RegulatorMode sx128x.RegulatorMode
SpreadingFactor sx128x.LoRaSpreadingFactor
Bandwidth sx128x.LoRaBandwidth
CodingRate sx128x.LoRaCodingRate
PreambleLength uint32
HeaderType sx128x.LoRaHeaderType
CrcType sx128x.LoRaCrcType
IqType sx128x.LoRaIqType
SyncWord uint16
}
func main() { func main() {
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
@@ -65,13 +79,46 @@ func main() {
timeout := atomic.Bool{} timeout := atomic.Bool{}
timeout.Store(true) timeout.Store(true)
dio1Interrupt := atomic.Bool{}
dio1Interrupt.Store(false)
dio1Pin.SetInterrupt(machine.PinRising, func(machine.Pin) { dio1Pin.SetInterrupt(machine.PinRising, func(machine.Pin) {
irqStatus := radio.GetIrqStatus() dio1Interrupt.Store(true)
})
loraConfig := LoRaConfig{
Frequency: 2400000000, // 2.4Ghz
Power: 13, // dBm
RadioRamp: sx128x.RADIO_RAMP_02_US, // 2 microsecond ramp time
RegulatorMode: sx128x.REGULATOR_DC_DC,
SpreadingFactor: sx128x.LORA_SF_9,
Bandwidth: sx128x.LORA_BW_1600,
CodingRate: sx128x.LORA_CR_4_7,
PreambleLength: 12,
HeaderType: sx128x.LORA_HEADER_EXPLICIT,
CrcType: sx128x.LORA_CRC_DISABLE,
IqType: sx128x.LORA_IQ_STD,
SyncWord: 0x1424, // the default private sync word
}
radio.WaitWhileBusy(time.Second)
SetupLora(radio, loraConfig)
oscMessage := osc.OSCMessage{
Address: "/cue/1/go",
Args: []osc.OSCArg{},
}
for {
if dio1Interrupt.Load() {
irqStatus, _ := radio.GetIrqStatus()
if irqStatus&sx128x.IRQ_TX_DONE_MASK != 0 { if irqStatus&sx128x.IRQ_TX_DONE_MASK != 0 {
txDone.Store(true) txDone.Store(true)
timeout.Store(false) timeout.Store(false)
led.Set(false) led.Set(false)
println("tx done")
} }
if irqStatus&sx128x.IRQ_RX_DONE_MASK != 0 { if irqStatus&sx128x.IRQ_RX_DONE_MASK != 0 {
rxDone.Store(true) rxDone.Store(true)
@@ -83,36 +130,8 @@ func main() {
txDone.Store(true) txDone.Store(true)
rxDone.Store(true) rxDone.Store(true)
} }
}) dio1Interrupt.Store(false)
loraConfig := lora.Config{
Freq: 2400000000,
Bw: lora.Bandwidth_1625_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: sx128x.LORA_EXPLICIT_HEADER,
Preamble: 12,
Ldr: lora.LowDataRateOptimizeOff,
Iq: sx128x.LORA_IQ_STD,
Crc: sx128x.LORA_CRC_DISABLE,
SyncWord: 0x1424,
LoraTxPowerDBm: 13,
} }
radio.WaitWhileBusy()
SetupLora(radio, loraConfig)
oscMessage := osc.OSCMessage{
Address: "/lora/button",
Args: []osc.OSCArg{
{
Type: "i",
Value: 0,
},
},
}
for {
if txDone.Load() && buttonPressed.Load() { if txDone.Load() && buttonPressed.Load() {
txDone.Store(false) txDone.Store(false)
buttonPressed.Store(false) buttonPressed.Store(false)
@@ -120,7 +139,6 @@ func main() {
radio.SetStandby(sx128x.STANDBY_RC) radio.SetStandby(sx128x.STANDBY_RC)
led.Set(true) led.Set(true)
oscMessage.Args[0].Value = int32(time.Now().UnixMilli() / 1000)
bytes, err := oscMessage.ToBytes() bytes, err := oscMessage.ToBytes()
if err != nil { if err != nil {
println("failed to serialize OSC message:", err) println("failed to serialize OSC message:", err)
@@ -160,36 +178,36 @@ func main() {
} }
} }
func SetupLora(radio *sx128x.Device, config lora.Config) { func SetupLora(radio *sx128x.Device, config LoRaConfig) {
// Switch to standby prior to configuration changes // Switch to standby prior to configuration changes
circuitMode, _ := radio.GetStatus() circuitMode, _, _ := radio.GetStatus()
if circuitMode != sx128x.CIRCUIT_MODE_STDBY_RC { if circuitMode != sx128x.CIRCUIT_MODE_STDBY_RC {
radio.SetStandby(sx128x.STANDBY_RC) radio.SetStandby(sx128x.STANDBY_RC)
} }
// Clear errors, disable radio interrupts for the moment // Clear errors, disable radio interrupts for the moment
radio.SetPacketType(sx128x.PACKET_TYPE_LORA) radio.SetPacketType(sx128x.PACKET_TYPE_LORA)
radio.SetCadParams(sx128x.LORA_CAD_08_SYMBOLS) radio.SetCadParams(uint8(sx128x.LORA_CAD_08_SYMBOLS))
radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC) radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC)
radio.SetRfFrequency(config.Freq) radio.SetRfFrequency(config.Frequency)
radio.SetModulationParams(spreadingFactor(config.Sf), bandwidth(config.Bw), codingRate(config.Cr)) radio.SetModulationParamsLoRa(config.SpreadingFactor, config.Bandwidth, config.CodingRate)
data := [1]uint8{} data := [1]uint8{}
if config.Sf == lora.SpreadingFactor5 || config.Sf == lora.SpreadingFactor6 { if config.SpreadingFactor == sx128x.LORA_SF_5 || config.SpreadingFactor == sx128x.LORA_SF_6 {
data[0] = 0x1E data[0] = 0x1E
} else if config.Sf == lora.SpreadingFactor7 || config.Sf == lora.SpreadingFactor8 { } else if config.SpreadingFactor == sx128x.LORA_SF_7 || config.SpreadingFactor == sx128x.LORA_SF_8 {
data[0] = 0x37 data[0] = 0x37
} else { } else {
data[0] = 0x32 data[0] = 0x32
} }
radio.WriteRegister(0x925, data[:]) radio.WriteRegister(0x925, data[:])
existing := radio.ReadRegister(0x93C) existing, _ := radio.ReadRegister(0x93C)
data[0] = existing | 0x01 data[0] = existing | 0x01
radio.WriteRegister(0x93C, data[:]) radio.WriteRegister(0x93C, data[:])
radio.SetTxParams(config.LoraTxPowerDBm, sx128x.RADIO_RAMP_02_US) radio.SetTxParams(config.Power, sx128x.RADIO_RAMP_02_US)
radio.SetPacketParamsLoRa(uint32(config.Preamble), config.HeaderType, 0xFF, config.Crc, config.Iq) radio.SetPacketParamsLoRa(config.PreambleLength, config.HeaderType, 0xFF, config.CrcType, config.IqType)
var syncWord [2]uint8 var syncWord [2]uint8
syncWord[0] = uint8(config.SyncWord >> 8) syncWord[0] = uint8(config.SyncWord >> 8)
syncWord[1] = uint8(config.SyncWord & 0x00FF) syncWord[1] = uint8(config.SyncWord & 0x00FF)
@@ -199,7 +217,7 @@ func SetupLora(radio *sx128x.Device, config lora.Config) {
} }
func checkStatus(radio *sx128x.Device, operation string) { func checkStatus(radio *sx128x.Device, operation string) {
circuitMode, commandStatus := radio.GetStatus() circuitMode, commandStatus, _ := radio.GetStatus()
if commandStatus != sx128x.COMMAND_STATUS_SUCCESS { if commandStatus != sx128x.COMMAND_STATUS_SUCCESS {
println(operation, "-> failed with status:", commandStatus) println(operation, "-> failed with status:", commandStatus)
} }
@@ -208,12 +226,12 @@ func checkStatus(radio *sx128x.Device, operation string) {
} }
} }
func Tx(radio *sx128x.Device, loraConfig lora.Config, data []byte) error { func Tx(radio *sx128x.Device, loraConfig LoRaConfig, data []byte) error {
if len(data) > 255 { if len(data) > 255 {
return errors.New("data length exceeds maximum of 255 bytes") return errors.New("data length exceeds maximum of 255 bytes")
} }
radio.SetStandby(sx128x.STANDBY_RC) radio.SetStandby(sx128x.STANDBY_RC)
radio.SetPacketParamsLoRa(uint32(loraConfig.Preamble), loraConfig.HeaderType, uint8(len(data)&0xFF), loraConfig.Crc, loraConfig.Iq) radio.SetPacketParamsLoRa(loraConfig.PreambleLength, loraConfig.HeaderType, uint8(len(data)&0xFF), loraConfig.CrcType, loraConfig.IqType)
radio.SetBufferBaseAddress(0, 0) radio.SetBufferBaseAddress(0, 0)
radio.WriteBuffer(0, data) radio.WriteBuffer(0, data)
radio.SetDioIrqParams(sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00) radio.SetDioIrqParams(sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00)
@@ -222,64 +240,11 @@ func Tx(radio *sx128x.Device, loraConfig lora.Config, data []byte) error {
return nil return nil
} }
func Rx(radio *sx128x.Device, loraConfig lora.Config) { func Rx(radio *sx128x.Device, loraConfig LoRaConfig) {
radio.SetStandby(sx128x.STANDBY_RC) radio.SetStandby(sx128x.STANDBY_RC)
radio.SetDioIrqParams(sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00) radio.SetDioIrqParams(sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00)
radio.SetBufferBaseAddress(0, 0) radio.SetBufferBaseAddress(0, 0)
radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK) radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK)
radio.SetRfFrequency(loraConfig.Freq) radio.SetRfFrequency(loraConfig.Frequency)
radio.SetRx(sx128x.PERIOD_BASE_4_MS, 250) radio.SetRx(sx128x.PERIOD_BASE_4_MS, 250)
} }
func codingRate(cr uint8) uint8 {
switch cr {
case lora.CodingRate4_5:
return sx128x.LORA_CR_4_5
case lora.CodingRate4_6:
return sx128x.LORA_CR_4_6
case lora.CodingRate4_7:
return sx128x.LORA_CR_4_7
case lora.CodingRate4_8:
return sx128x.LORA_CR_4_8
default:
return 0
}
}
func spreadingFactor(sf uint8) uint8 {
switch sf {
case lora.SpreadingFactor5:
return sx128x.LORA_SF_5
case lora.SpreadingFactor6:
return sx128x.LORA_SF_6
case lora.SpreadingFactor7:
return sx128x.LORA_SF_7
case lora.SpreadingFactor8:
return sx128x.LORA_SF_8
case lora.SpreadingFactor9:
return sx128x.LORA_SF_9
case lora.SpreadingFactor10:
return sx128x.LORA_SF_10
case lora.SpreadingFactor11:
return sx128x.LORA_SF_11
case lora.SpreadingFactor12:
return sx128x.LORA_SF_12
default:
return 0
}
}
func bandwidth(bw uint8) uint8 {
switch bw {
case lora.Bandwidth_1625_0:
return sx128x.LORA_BW_1600
case lora.Bandwidth_812_5:
return sx128x.LORA_BW_800
case lora.Bandwidth_406_25:
return sx128x.LORA_BW_400
case lora.Bandwidth_203_125:
return sx128x.LORA_BW_200
default:
return 0
}
}
+3
View File
@@ -0,0 +1,3 @@
module node
go 1.26.2
+100
View File
@@ -0,0 +1,100 @@
package node
import (
"errors"
)
type Node struct {
id NodeId
transport Transport
sequenceNumber uint32
}
func New(id NodeId, transport Transport) *Node {
return &Node{
id: id,
transport: transport,
}
}
type NodeId uint8
func (id NodeId) IsBroadcast() bool {
return id == 0xFF
}
func (b *Node) Receive(timeout uint32) (NodeId, uint32, []byte, error) {
data, err := b.transport.Rx(timeout)
if err != nil {
return 0, 0, nil, err
}
if len(data) < 6 {
return 0, 0, nil, errors.New("invalid message length")
}
sourceId := NodeId(data[0])
targetId := NodeId(data[1])
if targetId != b.id && !targetId.IsBroadcast() {
return 0, 0, nil, nil
}
seq := uint32(data[2])<<24 | uint32(data[3])<<16 | uint32(data[4])<<8 | uint32(data[5])
payload := data[6:]
return sourceId, seq, payload, nil
}
func (b *Node) ReceiveWithAck(timeout uint32) (NodeId, uint32, []byte, error) {
data, err := b.transport.Rx(timeout)
if err != nil {
return 0, 0, nil, err
}
if len(data) < 6 {
return 0, 0, nil, errors.New("invalid message length")
}
sourceId := NodeId(data[0])
targetId := NodeId(data[1])
if targetId != b.id && !targetId.IsBroadcast() {
return 0, 0, nil, errors.New("message not intended for this node")
}
seq := uint32(data[2])<<24 | uint32(data[3])<<16 | uint32(data[4])<<8 | uint32(data[5])
payload := data[6:]
ackMsg := make([]byte, 6)
ackMsg[0] = byte(b.id)
ackMsg[1] = byte(sourceId)
ackMsg[2] = byte(seq >> 24)
ackMsg[3] = byte(seq >> 16)
ackMsg[4] = byte(seq >> 8)
ackMsg[5] = byte(seq)
err = b.transport.Tx(ackMsg, timeout)
if err != nil {
return 0, 0, nil, err
}
return sourceId, seq, payload, nil
}
func (b *Node) SendWithAck(target NodeId, payload []byte, timeout uint32) error {
if target.IsBroadcast() {
return errors.New("cannot send with ack to broadcast address")
}
msg := make([]byte, 6)
msg[0] = byte(b.id)
msg[1] = byte(target)
sentSeq := b.sequenceNumber
msg[2] = byte(sentSeq >> 24)
msg[3] = byte(sentSeq >> 16)
msg[4] = byte(sentSeq >> 8)
msg[5] = byte(sentSeq)
b.sequenceNumber++
msg = append(msg, payload...)
b.transport.Tx(msg, timeout)
sender, receivedSeq, _, err := b.Receive(timeout)
if err != nil {
return err
}
if sender != target {
return errors.New("received ACK from unexpected sender")
}
if receivedSeq != sentSeq {
return errors.New("received ACK with unexpected sequence number")
}
return nil
}
+6
View File
@@ -0,0 +1,6 @@
package node
type Transport interface {
Tx(payload []byte, timeout uint32) error
Rx(timeout uint32) ([]byte, error)
}