mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-04 23:17:47 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91909cf84a | |||
| 8ff5e7095e | |||
| b0286910c4 | |||
| 2bb10bf6b9 | |||
| 632cc964d5 | |||
| e296fa822b | |||
| d09ee42742 | |||
| 10458f377f | |||
| 2174b039a5 | |||
| 23b0068e53 | |||
| 7372929a35 | |||
| 68b1b14347 | |||
| 1ba616c6a6 | |||
| c149f3d458 | |||
| b096e7f2db | |||
| ae2c06592d | |||
| da791bf9f4 | |||
| c1637d0b79 |
@@ -289,6 +289,8 @@ func mode(setting string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func join(setting string) error {
|
func join(setting string) error {
|
||||||
|
// TODO: check that DevEUI, AppEUI, and AppKey have values
|
||||||
|
|
||||||
cmd := "JOIN"
|
cmd := "JOIN"
|
||||||
writeCommandOutput(cmd, "Starting")
|
writeCommandOutput(cmd, "Starting")
|
||||||
if err := lorawan.Join(otaa, session); err != nil {
|
if err := lorawan.Join(otaa, session); err != nil {
|
||||||
@@ -298,7 +300,12 @@ func join(setting string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Network joined")
|
writeCommandOutput(cmd, "Network joined")
|
||||||
// TODO: display NetID and DevAddr
|
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
|
||||||
|
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
|
||||||
|
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
|
||||||
|
writeCommandOutput(cmd, "NetID, "+otaa.GetNetID())
|
||||||
|
writeCommandOutput(cmd, "NwkSKey, "+session.GetNwkSKey())
|
||||||
|
writeCommandOutput(cmd, "AppSKey, "+session.GetAppSKey())
|
||||||
writeCommandOutput(cmd, "Done")
|
writeCommandOutput(cmd, "Done")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !featherwing && !gnse && !lorae5 && !nucleowl55jc
|
//go:build !featherwing && !stm32wlx && !sx126x
|
||||||
|
|
||||||
package common
|
package common
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
//go:build stm32wlx
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/lora"
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FREQ = 868100000
|
||||||
|
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
||||||
|
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loraRadio *sx126x.Device
|
||||||
|
)
|
||||||
|
|
||||||
|
var spi = machine.SPI3
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl()
|
||||||
|
}
|
||||||
|
|
||||||
|
// do sx126x setup here
|
||||||
|
func SetupLora() (lora.Radio, error) {
|
||||||
|
loraRadio = sx126x.New(spi)
|
||||||
|
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||||
|
|
||||||
|
// Create radio controller for target
|
||||||
|
loraRadio.SetRadioController(newRadioControl())
|
||||||
|
|
||||||
|
if state := loraRadio.DetectDevice(); !state {
|
||||||
|
return nil, errRadioNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
loraConf := lora.Config{
|
||||||
|
Freq: FREQ,
|
||||||
|
Bw: lora.Bandwidth_500_0,
|
||||||
|
Sf: lora.SpreadingFactor9,
|
||||||
|
Cr: lora.CodingRate4_7,
|
||||||
|
HeaderType: lora.HeaderExplicit,
|
||||||
|
Preamble: 12,
|
||||||
|
Ldr: lora.LowDataRateOptimizeOff,
|
||||||
|
Iq: lora.IQStandard,
|
||||||
|
Crc: lora.CRCOn,
|
||||||
|
SyncWord: lora.SyncPrivate,
|
||||||
|
LoraTxPowerDBm: 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
loraRadio.LoraConfig(loraConf)
|
||||||
|
|
||||||
|
return loraRadio, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FirmwareVersion() string {
|
||||||
|
return "sx126x"
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lorarx() ([]byte, error) {
|
||||||
|
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||||
|
}
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
//go:build gnse || lorae5 || nucleowl55jc
|
//go:build !stm32wlx && sx126x
|
||||||
|
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"device/stm32"
|
|
||||||
"machine"
|
"machine"
|
||||||
"runtime/interrupt"
|
|
||||||
|
|
||||||
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/lora"
|
"tinygo.org/x/drivers/lora"
|
||||||
"tinygo.org/x/drivers/sx126x"
|
"tinygo.org/x/drivers/sx126x"
|
||||||
@@ -23,23 +19,28 @@ var (
|
|||||||
loraRadio *sx126x.Device
|
loraRadio *sx126x.Device
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
spi = machine.SPI0
|
||||||
|
nssPin, busyPin, dio1Pin = machine.GP17, machine.GP10, machine.GP11
|
||||||
|
rxPin, txLowPin, txHighPin = machine.GP13, machine.GP12, machine.GP12
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl(nssPin, busyPin, dio1Pin, rxPin, txLowPin, txHighPin)
|
||||||
|
}
|
||||||
|
|
||||||
// do sx126x setup here
|
// do sx126x setup here
|
||||||
func SetupLora() (lora.Radio, error) {
|
func SetupLora() (lora.Radio, error) {
|
||||||
loraRadio = sx126x.New(machine.SPI3)
|
loraRadio = sx126x.New(spi)
|
||||||
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||||
|
|
||||||
// Create RF Switch
|
// Create radio controller for target
|
||||||
var radioSwitch rfswitch.CustomSwitch
|
loraRadio.SetRadioController(newRadioControl())
|
||||||
loraRadio.SetRfSwitch(radioSwitch)
|
|
||||||
|
|
||||||
if state := loraRadio.DetectDevice(); !state {
|
if state := loraRadio.DetectDevice(); !state {
|
||||||
return nil, errRadioNotFound
|
return nil, errRadioNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add interrupt handler for Radio IRQs
|
|
||||||
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
|
|
||||||
intr.Enable()
|
|
||||||
|
|
||||||
loraConf := lora.Config{
|
loraConf := lora.Config{
|
||||||
Freq: FREQ,
|
Freq: FREQ,
|
||||||
Bw: lora.Bandwidth_500_0,
|
Bw: lora.Bandwidth_500_0,
|
||||||
@@ -59,11 +60,6 @@ func SetupLora() (lora.Radio, error) {
|
|||||||
return loraRadio, nil
|
return loraRadio, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// radioIntHandler will take care of radio interrupts
|
|
||||||
func radioIntHandler(intr interrupt.Interrupt) {
|
|
||||||
loraRadio.HandleInterrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
func FirmwareVersion() string {
|
func FirmwareVersion() string {
|
||||||
return "sx126x"
|
return "sx126x"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import (
|
|||||||
"machine"
|
"machine"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/lora"
|
"tinygo.org/x/drivers/lora"
|
||||||
"tinygo.org/x/drivers/sx126x"
|
"tinygo.org/x/drivers/sx126x"
|
||||||
)
|
)
|
||||||
@@ -24,12 +22,11 @@ func main() {
|
|||||||
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
// Create the driver
|
// Create the driver
|
||||||
loraRadio = sx126x.New(machine.SPI3)
|
loraRadio = sx126x.New(spi)
|
||||||
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||||
|
|
||||||
// Create RF Switch
|
// Create radio controller for target
|
||||||
var radioSwitch rfswitch.CustomSwitch
|
loraRadio.SetRadioController(newRadioControl())
|
||||||
loraRadio.SetRfSwitch(radioSwitch)
|
|
||||||
|
|
||||||
state := loraRadio.DetectDevice()
|
state := loraRadio.DetectDevice()
|
||||||
if !state {
|
if !state {
|
||||||
@@ -76,6 +73,5 @@ func main() {
|
|||||||
|
|
||||||
loraRadio.SetStandby()
|
loraRadio.SetStandby()
|
||||||
time.Sleep(60 * time.Second)
|
time.Sleep(60 * time.Second)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build !stm32wlx
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
spi = machine.SPI0
|
||||||
|
nssPin, busyPin, dio1Pin = machine.GP17, machine.GP10, machine.GP11
|
||||||
|
rxPin, txLowPin, txHighPin = machine.GP13, machine.GP12, machine.GP12
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl(nssPin, busyPin, dio1Pin, rxPin, txLowPin, txHighPin)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//go:build stm32wlx
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
var spi = machine.SPI3
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl()
|
||||||
|
}
|
||||||
@@ -4,13 +4,9 @@ package main
|
|||||||
// module will be in RX mode between two transmissions
|
// module will be in RX mode between two transmissions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"device/stm32"
|
|
||||||
"machine"
|
"machine"
|
||||||
"runtime/interrupt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/lora"
|
"tinygo.org/x/drivers/lora"
|
||||||
"tinygo.org/x/drivers/sx126x"
|
"tinygo.org/x/drivers/sx126x"
|
||||||
)
|
)
|
||||||
@@ -27,23 +23,19 @@ var (
|
|||||||
txmsg = []byte("Hello TinyGO")
|
txmsg = []byte("Hello TinyGO")
|
||||||
)
|
)
|
||||||
|
|
||||||
// radioIntHandler will take care of radio interrupts
|
|
||||||
func radioIntHandler(intr interrupt.Interrupt) {
|
|
||||||
loraRadio.HandleInterrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
println("\n# TinyGo Lora RX/TX test")
|
println("\n# TinyGo Lora RX/TX test")
|
||||||
println("# ----------------------")
|
println("# ----------------------")
|
||||||
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
// Create the driver
|
// Create the driver
|
||||||
loraRadio = sx126x.New(machine.SPI3)
|
loraRadio = sx126x.New(spi)
|
||||||
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||||
|
|
||||||
// Create RF Switch
|
// Create radio controller for target
|
||||||
var radioSwitch rfswitch.CustomSwitch
|
loraRadio.SetRadioController(newRadioControl())
|
||||||
loraRadio.SetRfSwitch(radioSwitch)
|
|
||||||
|
|
||||||
// Detect the device
|
// Detect the device
|
||||||
state := loraRadio.DetectDevice()
|
state := loraRadio.DetectDevice()
|
||||||
@@ -51,10 +43,6 @@ func main() {
|
|||||||
panic("sx126x not detected.")
|
panic("sx126x not detected.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add interrupt handler for Radio IRQs
|
|
||||||
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
|
|
||||||
intr.Enable()
|
|
||||||
|
|
||||||
loraConf := lora.Config{
|
loraConf := lora.Config{
|
||||||
Freq: FREQ,
|
Freq: FREQ,
|
||||||
Bw: lora.Bandwidth_500_0,
|
Bw: lora.Bandwidth_500_0,
|
||||||
@@ -73,10 +61,10 @@ func main() {
|
|||||||
|
|
||||||
var count uint
|
var count uint
|
||||||
for {
|
for {
|
||||||
tStart := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
println("main: Receiving Lora for 10 seconds")
|
println("main: Receiving Lora for 10 seconds")
|
||||||
for int(time.Now().Sub(tStart).Seconds()) < 10 {
|
for time.Since(start) < 10*time.Second {
|
||||||
buf, err := loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
buf, err := loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("RX Error: ", err)
|
println("RX Error: ", err)
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//go:build !stm32wlx
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
spi = machine.SPI1
|
||||||
|
nssPin, busyPin, dio1Pin = machine.GP13, machine.GP6, machine.GP7
|
||||||
|
rxPin, txLowPin, txHighPin = machine.GP9, machine.GP8, machine.GP8
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl(nssPin, busyPin, dio1Pin, rxPin, txLowPin, txHighPin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
spi.Configure(machine.SPIConfig{
|
||||||
|
Mode: 0,
|
||||||
|
Frequency: 8 * 1e6,
|
||||||
|
SDO: machine.SPI1_SDO_PIN,
|
||||||
|
SDI: machine.SPI1_SDI_PIN,
|
||||||
|
SCK: machine.SPI1_SCK_PIN,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//go:build stm32wlx
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
var spi = machine.SPI3
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl()
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
//go:build gnse
|
|
||||||
|
|
||||||
/*
|
|
||||||
Generic Node Sensor Edition
|
|
||||||
RFSwitch
|
|
||||||
|
|
||||||
Disable Switch : PB8=OFF PA0=OFF PA1=OFF
|
|
||||||
Enable RX : PB8=ON PA0=ON PA1=OFF
|
|
||||||
Enable TX RFO LP : PB8=ON PA0=ON PA1=ON
|
|
||||||
Enable TX RFO HP : PB8=ON PA0=OFF PA1=ON
|
|
||||||
*/
|
|
||||||
package rfswitch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CustomSwitch struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
rfstate int
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s CustomSwitch) InitRFSwitch() {
|
|
||||||
machine.RF_FE_CTRL1.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
machine.RF_FE_CTRL2.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
machine.RF_FE_CTRL3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
rfstate = -1 //Unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s CustomSwitch) SetRfSwitchMode(mode int) error {
|
|
||||||
if mode == rfstate {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch mode {
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_TX_HP:
|
|
||||||
machine.RF_FE_CTRL1.Set(false)
|
|
||||||
machine.RF_FE_CTRL2.Set(true)
|
|
||||||
machine.RF_FE_CTRL3.Set(true)
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_TX_LP:
|
|
||||||
machine.RF_FE_CTRL1.Set(true)
|
|
||||||
machine.RF_FE_CTRL2.Set(true)
|
|
||||||
machine.RF_FE_CTRL3.Set(true)
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_RX:
|
|
||||||
machine.RF_FE_CTRL1.Set(true)
|
|
||||||
machine.RF_FE_CTRL2.Set(false)
|
|
||||||
machine.RF_FE_CTRL3.Set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
rfstate = mode
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
//go:build lorae5
|
|
||||||
|
|
||||||
package radio
|
|
||||||
|
|
||||||
/*
|
|
||||||
/!\ LoRa-E5 module ONLY transmits through RFO_HP:
|
|
||||||
|
|
||||||
Receive: PA4=1, PA5=0
|
|
||||||
Transmit(high output power, SMPS mode): PA4=0, PA5=1
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CustomSwitch struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s CustomSwitch) InitRFSwitch() {
|
|
||||||
machine.PA4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
machine.PB5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s CustomSwitch) SetRfSwitchMode(mode int) error {
|
|
||||||
switch mode {
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_RX:
|
|
||||||
machine.PA4.Set(true)
|
|
||||||
machine.PB5.Set(false)
|
|
||||||
case sx126x.RFSWITCH_TX_LP:
|
|
||||||
errors.New("RFSWITCH_TX_LP not supported ")
|
|
||||||
case sx126x.RFSWITCH_TX_HP:
|
|
||||||
machine.PA4.Set(false)
|
|
||||||
machine.PB5.Set(true)
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
//go:build nucleowl55jc
|
|
||||||
|
|
||||||
/*
|
|
||||||
Nucleo WL55JC1
|
|
||||||
RFSwitch
|
|
||||||
|
|
||||||
+-----------+---------+------------+------------+
|
|
||||||
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
|
|
||||||
| | (PC4) | (PC5) | (PC3) |
|
|
||||||
+-----------+----------+-----------+------------+
|
|
||||||
| TX_HP | LOW | HIGH | HIGH |
|
|
||||||
| TX_LP | HIGH | HIGH | HIGH |
|
|
||||||
| RX | HIGH | LOW | HIGH |
|
|
||||||
+-----------+----------+-----------+------------+
|
|
||||||
*/
|
|
||||||
package rfswitch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CustomSwitch struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s CustomSwitch) InitRFSwitch() {
|
|
||||||
machine.PC4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
machine.PC5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
machine.PC3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s CustomSwitch) SetRfSwitchMode(mode int) error {
|
|
||||||
switch mode {
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_TX_HP:
|
|
||||||
machine.PC4.Set(false)
|
|
||||||
machine.PC5.Set(true)
|
|
||||||
machine.PC3.Set(true)
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_TX_LP:
|
|
||||||
machine.PC4.Set(true)
|
|
||||||
machine.PC5.Set(true)
|
|
||||||
machine.PC3.Set(true)
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_RX:
|
|
||||||
machine.PC4.Set(true)
|
|
||||||
machine.PC5.Set(false)
|
|
||||||
machine.PC3.Set(true)
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
+33
-4
@@ -13,8 +13,35 @@ import (
|
|||||||
var (
|
var (
|
||||||
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
|
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
|
||||||
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
|
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
|
||||||
|
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
|
||||||
|
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
|
||||||
|
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
|
||||||
|
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
|
||||||
|
errInvalidGLLSentence = errors.New("invalid GLL NMEA sentence")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type GPSError struct {
|
||||||
|
Err error
|
||||||
|
Info string
|
||||||
|
Sentence string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGPSError(err error, sentence string, info string) GPSError {
|
||||||
|
return GPSError{
|
||||||
|
Info: info,
|
||||||
|
Err: err,
|
||||||
|
Sentence: sentence,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ge GPSError) Error() string {
|
||||||
|
return ge.Err.Error() + " " + ge.Info + " " + ge.Sentence
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ge GPSError) Unwrap() error {
|
||||||
|
return ge.Err
|
||||||
|
}
|
||||||
|
|
||||||
// Device wraps a connection to a GPS device.
|
// Device wraps a connection to a GPS device.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
buffer []byte
|
buffer []byte
|
||||||
@@ -127,16 +154,18 @@ func (gps *Device) WriteBytes(bytes []byte) {
|
|||||||
|
|
||||||
// validSentence checks if a sentence has been received uncorrupted
|
// validSentence checks if a sentence has been received uncorrupted
|
||||||
func validSentence(sentence string) error {
|
func validSentence(sentence string) error {
|
||||||
if len(sentence) < 4 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
|
if len(sentence) < 7 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
|
||||||
return errInvalidNMEASentenceLength
|
return errInvalidNMEASentenceLength
|
||||||
}
|
}
|
||||||
var cs byte = 0
|
var cs byte = 0
|
||||||
for i := 1; i < len(sentence)-3; i++ {
|
for i := 1; i < len(sentence)-3; i++ {
|
||||||
cs ^= sentence[i]
|
cs ^= sentence[i]
|
||||||
}
|
}
|
||||||
checksum := hex.EncodeToString([]byte{cs})
|
checksum := strings.ToUpper(hex.EncodeToString([]byte{cs}))
|
||||||
if (checksum[0] != sentence[len(sentence)-2]) || (checksum[1] != sentence[len(sentence)-1]) {
|
if checksum != sentence[len(sentence)-2:len(sentence)] {
|
||||||
return errInvalidNMEAChecksum
|
return newGPSError(errInvalidNMEAChecksum, sentence,
|
||||||
|
"expected "+sentence[len(sentence)-2:len(sentence)]+
|
||||||
|
" got "+checksum)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+35
-20
@@ -1,19 +1,11 @@
|
|||||||
package gps
|
package gps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
|
|
||||||
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
|
|
||||||
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
|
|
||||||
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parser for GPS NMEA sentences.
|
// Parser for GPS NMEA sentences.
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
}
|
}
|
||||||
@@ -51,18 +43,21 @@ func NewParser() Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses a NMEA sentence looking for fix info.
|
// Parse parses a NMEA sentence looking for fix info.
|
||||||
func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
|
func (parser *Parser) Parse(sentence string) (Fix, error) {
|
||||||
|
var fix Fix
|
||||||
if sentence == "" {
|
if sentence == "" {
|
||||||
err = errEmptyNMEASentence
|
return fix, errEmptyNMEASentence
|
||||||
return
|
}
|
||||||
|
if len(sentence) < 6 {
|
||||||
|
return fix, errInvalidNMEASentenceLength
|
||||||
}
|
}
|
||||||
typ := sentence[3:6]
|
typ := sentence[3:6]
|
||||||
switch typ {
|
switch typ {
|
||||||
case "GGA":
|
case "GGA":
|
||||||
|
// https://docs.novatel.com/OEM7/Content/Logs/GPGGA.htm
|
||||||
fields := strings.Split(sentence, ",")
|
fields := strings.Split(sentence, ",")
|
||||||
if len(fields) != 15 {
|
if len(fields) != 15 {
|
||||||
err = errInvalidGGASentence
|
return fix, errInvalidGGASentence
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fix.Altitude = findAltitude(fields[9])
|
fix.Altitude = findAltitude(fields[9])
|
||||||
@@ -71,11 +66,27 @@ func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
|
|||||||
fix.Latitude = findLatitude(fields[2], fields[3])
|
fix.Latitude = findLatitude(fields[2], fields[3])
|
||||||
fix.Time = findTime(fields[1])
|
fix.Time = findTime(fields[1])
|
||||||
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
||||||
|
|
||||||
|
return fix, nil
|
||||||
|
case "GLL":
|
||||||
|
// https://docs.novatel.com/OEM7/Content/Logs/GPGLL.htm
|
||||||
|
fields := strings.Split(sentence, ",")
|
||||||
|
if len(fields) != 8 {
|
||||||
|
return fix, errInvalidGLLSentence
|
||||||
|
}
|
||||||
|
|
||||||
|
fix.Latitude = findLatitude(fields[2], fields[3])
|
||||||
|
fix.Longitude = findLongitude(fields[4], fields[5])
|
||||||
|
fix.Time = findTime(fields[6])
|
||||||
|
|
||||||
|
fix.Valid = (fields[7] == "A")
|
||||||
|
|
||||||
|
return fix, nil
|
||||||
case "RMC":
|
case "RMC":
|
||||||
|
// https://docs.novatel.com/OEM7/Content/Logs/GPRMC.htm
|
||||||
fields := strings.Split(sentence, ",")
|
fields := strings.Split(sentence, ",")
|
||||||
if len(fields) != 13 {
|
if len(fields) != 13 {
|
||||||
err = errInvalidRMCSentence
|
return fix, errInvalidRMCSentence
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fix.Longitude = findLongitude(fields[5], fields[6])
|
fix.Longitude = findLongitude(fields[5], fields[6])
|
||||||
@@ -83,11 +94,12 @@ func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
|
|||||||
fix.Time = findTime(fields[1])
|
fix.Time = findTime(fields[1])
|
||||||
fix.Speed = findSpeed(fields[7])
|
fix.Speed = findSpeed(fields[7])
|
||||||
fix.Heading = findHeading(fields[8])
|
fix.Heading = findHeading(fields[8])
|
||||||
fix.Valid = (len(fields[2]) > 0 && fields[2][0:1] == "A")
|
fix.Valid = (len(fields[2]) > 0 && fields[2] == "A")
|
||||||
default:
|
|
||||||
err = errUnknownNMEASentence
|
return fix, nil
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
return fix, newGPSError(errUnknownNMEASentence, sentence, typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
// findTime returns the time from an NMEA sentence:
|
// findTime returns the time from an NMEA sentence:
|
||||||
@@ -100,7 +112,10 @@ func findTime(val string) time.Time {
|
|||||||
h, _ := strconv.ParseInt(val[0:2], 10, 8)
|
h, _ := strconv.ParseInt(val[0:2], 10, 8)
|
||||||
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
||||||
s, _ := strconv.ParseInt(val[4:6], 10, 8)
|
s, _ := strconv.ParseInt(val[4:6], 10, 8)
|
||||||
ms, _ := strconv.ParseInt(val[7:10], 10, 16)
|
ms := int64(0)
|
||||||
|
if len(val) == 10 {
|
||||||
|
ms, _ = strconv.ParseInt(val[7:10], 10, 16)
|
||||||
|
}
|
||||||
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
|
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|||||||
+25
-14
@@ -6,10 +6,28 @@ import (
|
|||||||
"tinygo.org/x/drivers/lora"
|
"tinygo.org/x/drivers/lora"
|
||||||
)
|
)
|
||||||
|
|
||||||
const LORA_RXTX_TIMEOUT = 1000
|
var (
|
||||||
|
ErrNoJoinAcceptReceived = errors.New("no JoinAccept packet received")
|
||||||
|
ErrNoRadioAttached = errors.New("no LoRa radio attached")
|
||||||
|
ErrInvalidEuiLength = errors.New("invalid EUI length")
|
||||||
|
ErrInvalidAppKeyLength = errors.New("invalid AppKey length")
|
||||||
|
ErrInvalidPacketLength = errors.New("invalid packet length")
|
||||||
|
ErrInvalidDevAddrLength = errors.New("invalid DevAddr length")
|
||||||
|
ErrInvalidMic = errors.New("invalid Mic")
|
||||||
|
ErrFrmPayloadTooLarge = errors.New("FRM payload too large")
|
||||||
|
ErrInvalidNetIDLength = errors.New("invalid NetID length")
|
||||||
|
ErrInvalidNwkSKeyLength = errors.New("invalid NwkSKey length")
|
||||||
|
ErrInvalidAppSKeyLength = errors.New("invalid AppSKey length")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LORA_TX_TIMEOUT = 2000
|
||||||
|
LORA_RX_TIMEOUT = 5000
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ActiveRadio lora.Radio
|
ActiveRadio lora.Radio
|
||||||
|
Retries = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
func UseRadio(r lora.Radio) {
|
func UseRadio(r lora.Radio) {
|
||||||
@@ -23,7 +41,7 @@ func Join(otaa *Otaa, session *Session) error {
|
|||||||
var resp []uint8
|
var resp []uint8
|
||||||
|
|
||||||
if ActiveRadio == nil {
|
if ActiveRadio == nil {
|
||||||
return errors.New("no LoRa radio attached")
|
return ErrNoRadioAttached
|
||||||
}
|
}
|
||||||
|
|
||||||
otaa.Init()
|
otaa.Init()
|
||||||
@@ -36,16 +54,15 @@ func Join(otaa *Otaa, session *Session) error {
|
|||||||
|
|
||||||
ActiveRadio.SetCrc(true)
|
ActiveRadio.SetCrc(true)
|
||||||
ActiveRadio.SetIqMode(0) // IQ Standard
|
ActiveRadio.SetIqMode(0) // IQ Standard
|
||||||
ActiveRadio.Tx(payload, LORA_RXTX_TIMEOUT)
|
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for JoinAccept
|
// Wait for JoinAccept
|
||||||
//println("lorawan: Wait for JOINACCEPT for 10s")
|
|
||||||
ActiveRadio.SetIqMode(1) // IQ Inverted
|
ActiveRadio.SetIqMode(1) // IQ Inverted
|
||||||
for i := 0; i < 15; i++ {
|
for i := 0; i < Retries; i++ {
|
||||||
resp, err = ActiveRadio.Rx(LORA_RXTX_TIMEOUT)
|
resp, err = ActiveRadio.Rx(LORA_RX_TIMEOUT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -54,19 +71,13 @@ func Join(otaa *Otaa, session *Session) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
return errors.New("no JoinAccept packet received")
|
return ErrNoJoinAcceptReceived
|
||||||
}
|
}
|
||||||
//println("lorawan: Received packet: len=", len(resp), "payload=", bytesToHexString(resp))
|
|
||||||
|
|
||||||
err = otaa.DecodeJoinAccept(resp, session)
|
err = otaa.DecodeJoinAccept(resp, session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// println("lorawan: Valid JOINACCEPT, now connected")
|
|
||||||
// println("lorawan: | DevAddr: ", bytesToHexString(r.Session.DevAddr[:]), " (LSB)")
|
|
||||||
// println("lorawan: | NetID : ", bytesToHexString(r.Otaa.NetID[:]))
|
|
||||||
// println("lorawan: | NwkSKey: ", bytesToHexString(r.Session.NwkSKey[:]))
|
|
||||||
// println("lorawan: | AppSKey: ", bytesToHexString(r.Session.AppSKey[:]))
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -78,7 +89,7 @@ func SendUplink(data []uint8, session *Session) error {
|
|||||||
}
|
}
|
||||||
ActiveRadio.SetCrc(true)
|
ActiveRadio.SetCrc(true)
|
||||||
ActiveRadio.SetIqMode(0) // IQ Standard
|
ActiveRadio.SetIqMode(0) // IQ Standard
|
||||||
ActiveRadio.Tx(payload, LORA_RXTX_TIMEOUT)
|
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,8 @@ func reverseBytes(s []byte) []byte {
|
|||||||
|
|
||||||
// GetRand16 returns 2 random bytes
|
// GetRand16 returns 2 random bytes
|
||||||
func GetRand16() ([2]uint8, error) {
|
func GetRand16() ([2]uint8, error) {
|
||||||
randomBytes := make([]byte, 2)
|
var randomBytes [2]byte
|
||||||
_, err := rand.Read(randomBytes)
|
_, err := rand.Read(randomBytes[:])
|
||||||
if err != nil {
|
|
||||||
return [2]uint8{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return [2]uint8{randomBytes[0], randomBytes[1]}, nil
|
return randomBytes, err
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-16
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Otaa is used to store Over The Air Activation data of a LoRaWAN session
|
// Otaa is used to store Over The Air Activation data of a LoRaWAN session
|
||||||
@@ -12,8 +11,8 @@ type Otaa struct {
|
|||||||
DevEUI [8]uint8
|
DevEUI [8]uint8
|
||||||
AppEUI [8]uint8
|
AppEUI [8]uint8
|
||||||
AppKey [16]uint8
|
AppKey [16]uint8
|
||||||
DevNonce [2]uint8
|
devNonce [2]uint8
|
||||||
AppNonce [3]uint8
|
appNonce [3]uint8
|
||||||
NetID [3]uint8
|
NetID [3]uint8
|
||||||
buf []uint8
|
buf []uint8
|
||||||
}
|
}
|
||||||
@@ -22,10 +21,20 @@ type Otaa struct {
|
|||||||
func (o *Otaa) Init() {
|
func (o *Otaa) Init() {
|
||||||
o.buf = make([]uint8, 0)
|
o.buf = make([]uint8, 0)
|
||||||
|
|
||||||
|
o.generateDevNonce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) generateDevNonce() {
|
||||||
// TODO: handle error
|
// TODO: handle error
|
||||||
rnd, _ := GetRand16()
|
rnd, _ := GetRand16()
|
||||||
o.DevNonce[0] = rnd[0]
|
o.devNonce[0] = rnd[0]
|
||||||
o.DevNonce[1] = rnd[1]
|
o.devNonce[1] = rnd[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) incrementDevNonce() {
|
||||||
|
nonce := uint16(o.devNonce[1])<<8 | uint16(o.devNonce[0]) + 1
|
||||||
|
o.devNonce[0] = uint8(nonce)
|
||||||
|
o.devNonce[1] = uint8((nonce >> 8))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set configures the Otaa AppEUI, DevEUI, AppKey for the device
|
// Set configures the Otaa AppEUI, DevEUI, AppKey for the device
|
||||||
@@ -38,7 +47,7 @@ func (o *Otaa) Set(appEUI []uint8, devEUI []uint8, appKey []uint8) {
|
|||||||
// SetAppEUI configures the Otaa AppEUI
|
// SetAppEUI configures the Otaa AppEUI
|
||||||
func (o *Otaa) SetAppEUI(appEUI []uint8) error {
|
func (o *Otaa) SetAppEUI(appEUI []uint8) error {
|
||||||
if len(appEUI) != 8 {
|
if len(appEUI) != 8 {
|
||||||
return errors.New("invalid length")
|
return ErrInvalidEuiLength
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(o.AppEUI[:], appEUI)
|
copy(o.AppEUI[:], appEUI)
|
||||||
@@ -53,7 +62,7 @@ func (o *Otaa) GetAppEUI() string {
|
|||||||
// SetDevEUI configures the Otaa DevEUI
|
// SetDevEUI configures the Otaa DevEUI
|
||||||
func (o *Otaa) SetDevEUI(devEUI []uint8) error {
|
func (o *Otaa) SetDevEUI(devEUI []uint8) error {
|
||||||
if len(devEUI) != 8 {
|
if len(devEUI) != 8 {
|
||||||
return errors.New("invalid length")
|
return ErrInvalidEuiLength
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(o.DevEUI[:], devEUI)
|
copy(o.DevEUI[:], devEUI)
|
||||||
@@ -68,7 +77,7 @@ func (o *Otaa) GetDevEUI() string {
|
|||||||
// SetAppKey configures the Otaa AppKey
|
// SetAppKey configures the Otaa AppKey
|
||||||
func (o *Otaa) SetAppKey(appKey []uint8) error {
|
func (o *Otaa) SetAppKey(appKey []uint8) error {
|
||||||
if len(appKey) != 16 {
|
if len(appKey) != 16 {
|
||||||
return errors.New("invalid length")
|
return ErrInvalidAppKeyLength
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(o.AppKey[:], appKey)
|
copy(o.AppKey[:], appKey)
|
||||||
@@ -80,14 +89,30 @@ func (o *Otaa) GetAppKey() string {
|
|||||||
return hex.EncodeToString(o.AppKey[:])
|
return hex.EncodeToString(o.AppKey[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) GetNetID() string {
|
||||||
|
return hex.EncodeToString(o.NetID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) SetNetID(netID []uint8) error {
|
||||||
|
if len(netID) != 3 {
|
||||||
|
return ErrInvalidNetIDLength
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(o.NetID[:], netID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GenerateJoinRequest Generates a LoraWAN Join request
|
// GenerateJoinRequest Generates a LoraWAN Join request
|
||||||
func (o *Otaa) GenerateJoinRequest() ([]uint8, error) {
|
func (o *Otaa) GenerateJoinRequest() ([]uint8, error) {
|
||||||
|
o.incrementDevNonce()
|
||||||
|
|
||||||
// TODO: Add checks
|
// TODO: Add checks
|
||||||
o.buf = o.buf[:0]
|
o.buf = o.buf[:0]
|
||||||
o.buf = append(o.buf, 0x00)
|
o.buf = append(o.buf, 0x00)
|
||||||
o.buf = append(o.buf, reverseBytes(o.AppEUI[:])...)
|
o.buf = append(o.buf, reverseBytes(o.AppEUI[:])...)
|
||||||
o.buf = append(o.buf, reverseBytes(o.DevEUI[:])...)
|
o.buf = append(o.buf, reverseBytes(o.DevEUI[:])...)
|
||||||
o.buf = append(o.buf, o.DevNonce[:]...)
|
o.buf = append(o.buf, o.devNonce[:]...)
|
||||||
mic := genPayloadMIC(o.buf, o.AppKey)
|
mic := genPayloadMIC(o.buf, o.AppKey)
|
||||||
o.buf = append(o.buf, mic[:]...)
|
o.buf = append(o.buf, mic[:]...)
|
||||||
|
|
||||||
@@ -97,21 +122,21 @@ func (o *Otaa) GenerateJoinRequest() ([]uint8, error) {
|
|||||||
// DecodeJoinAccept Decodes a Lora Join Accept packet
|
// DecodeJoinAccept Decodes a Lora Join Accept packet
|
||||||
func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error {
|
func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error {
|
||||||
if len(phyPload) < 12 {
|
if len(phyPload) < 12 {
|
||||||
return errors.New("Bad packet")
|
return ErrInvalidPacketLength
|
||||||
}
|
}
|
||||||
data := phyPload[1:] // Remove trailing 0x20
|
data := phyPload[1:] // Remove trailing 0x20
|
||||||
|
|
||||||
// Prepare AES Cipher
|
// Prepare AES Cipher
|
||||||
block, err := aes.NewCipher(o.AppKey[:])
|
block, err := aes.NewCipher(o.AppKey[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Lora Cipher error 1")
|
return err
|
||||||
}
|
}
|
||||||
buf := make([]byte, len(data))
|
buf := make([]byte, len(data))
|
||||||
for k := 0; k < len(data)/aes.BlockSize; k++ {
|
for k := 0; k < len(data)/aes.BlockSize; k++ {
|
||||||
block.Encrypt(buf[k*aes.BlockSize:], data[k*aes.BlockSize:])
|
block.Encrypt(buf[k*aes.BlockSize:], data[k*aes.BlockSize:])
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(o.AppNonce[:], buf[0:3])
|
copy(o.appNonce[:], buf[0:3])
|
||||||
copy(o.NetID[:], buf[3:6])
|
copy(o.NetID[:], buf[3:6])
|
||||||
copy(s.DevAddr[:], buf[6:10])
|
copy(s.DevAddr[:], buf[6:10])
|
||||||
s.DLSettings = buf[10]
|
s.DLSettings = buf[10]
|
||||||
@@ -124,7 +149,7 @@ func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error {
|
|||||||
|
|
||||||
dataMic := []byte{}
|
dataMic := []byte{}
|
||||||
dataMic = append(dataMic, phyPload[0])
|
dataMic = append(dataMic, phyPload[0])
|
||||||
dataMic = append(dataMic, o.AppNonce[:]...)
|
dataMic = append(dataMic, o.appNonce[:]...)
|
||||||
dataMic = append(dataMic, o.NetID[:]...)
|
dataMic = append(dataMic, o.NetID[:]...)
|
||||||
dataMic = append(dataMic, s.DevAddr[:]...)
|
dataMic = append(dataMic, s.DevAddr[:]...)
|
||||||
dataMic = append(dataMic, s.DLSettings)
|
dataMic = append(dataMic, s.DLSettings)
|
||||||
@@ -132,15 +157,15 @@ func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error {
|
|||||||
dataMic = append(dataMic, s.CFList[:]...)
|
dataMic = append(dataMic, s.CFList[:]...)
|
||||||
computedMic := genPayloadMIC(dataMic[:], o.AppKey)
|
computedMic := genPayloadMIC(dataMic[:], o.AppKey)
|
||||||
if !bytes.Equal(computedMic[:], rxMic[:]) {
|
if !bytes.Equal(computedMic[:], rxMic[:]) {
|
||||||
return errors.New("invalid Mic")
|
return ErrInvalidMic
|
||||||
}
|
}
|
||||||
// Generate NwkSKey
|
// Generate NwkSKey
|
||||||
// NwkSKey = aes128_encrypt(AppKey, 0x01|AppNonce|NetID|DevNonce|pad16)
|
// NwkSKey = aes128_encrypt(AppKey, 0x01|AppNonce|NetID|DevNonce|pad16)
|
||||||
sKey := []byte{}
|
sKey := []byte{}
|
||||||
sKey = append(sKey, 0x01)
|
sKey = append(sKey, 0x01)
|
||||||
sKey = append(sKey, o.AppNonce[:]...)
|
sKey = append(sKey, o.appNonce[:]...)
|
||||||
sKey = append(sKey, o.NetID[:]...)
|
sKey = append(sKey, o.NetID[:]...)
|
||||||
sKey = append(sKey, o.DevNonce[:]...)
|
sKey = append(sKey, o.devNonce[:]...)
|
||||||
for i := 0; i < 7; i++ {
|
for i := 0; i < 7; i++ {
|
||||||
sKey = append(sKey, 0x00) // PAD to 16
|
sKey = append(sKey, 0x00) // PAD to 16
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-4
@@ -4,7 +4,6 @@ import (
|
|||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ type Session struct {
|
|||||||
// SetDevAddr configures the Session DevAddr
|
// SetDevAddr configures the Session DevAddr
|
||||||
func (s *Session) SetDevAddr(devAddr []uint8) error {
|
func (s *Session) SetDevAddr(devAddr []uint8) error {
|
||||||
if len(devAddr) != 4 {
|
if len(devAddr) != 4 {
|
||||||
return errors.New("invalid length")
|
return ErrInvalidDevAddrLength
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(s.DevAddr[:], devAddr)
|
copy(s.DevAddr[:], devAddr)
|
||||||
@@ -31,11 +30,44 @@ func (s *Session) SetDevAddr(devAddr []uint8) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDevAddr returns the Session DevAddr
|
||||||
func (s *Session) GetDevAddr() string {
|
func (s *Session) GetDevAddr() string {
|
||||||
return hex.EncodeToString(s.DevAddr[:])
|
return hex.EncodeToString(s.DevAddr[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenMessage Forge an uplink message
|
// SetNwkSKey configures the Session NwkSKey
|
||||||
|
func (s *Session) SetNwkSKey(nwkSKey []uint8) error {
|
||||||
|
if len(nwkSKey) != 16 {
|
||||||
|
return ErrInvalidNwkSKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(s.NwkSKey[:], nwkSKey)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNwkSKey returns the Session NwkSKey
|
||||||
|
func (s *Session) GetNwkSKey() string {
|
||||||
|
return hex.EncodeToString(s.NwkSKey[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAppSKey configures the Session AppSKey
|
||||||
|
func (s *Session) SetAppSKey(appSKey []uint8) error {
|
||||||
|
if len(appSKey) != 16 {
|
||||||
|
return ErrInvalidAppSKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(s.AppSKey[:], appSKey)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppSKey returns the Session AppSKey
|
||||||
|
func (s *Session) GetAppSKey() string {
|
||||||
|
return hex.EncodeToString(s.AppSKey[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenMessage generates an uplink message.
|
||||||
func (s *Session) GenMessage(dir uint8, payload []uint8) ([]uint8, error) {
|
func (s *Session) GenMessage(dir uint8, payload []uint8) ([]uint8, error) {
|
||||||
var buf []uint8
|
var buf []uint8
|
||||||
buf = append(buf, 0b01000000) // FHDR Unconfirmed up
|
buf = append(buf, 0b01000000) // FHDR Unconfirmed up
|
||||||
@@ -75,7 +107,7 @@ func (s *Session) genFRMPayload(dir uint8, fCnt uint32, payload []byte, isFOpts
|
|||||||
k++
|
k++
|
||||||
}
|
}
|
||||||
if k > math.MaxUint8 {
|
if k > math.MaxUint8 {
|
||||||
return nil, errors.New("Payload too big !")
|
return nil, ErrFrmPayloadTooLarge
|
||||||
}
|
}
|
||||||
encrypted := make([]byte, 0, k*16)
|
encrypted := make([]byte, 0, k*16)
|
||||||
cipher, err := aes.NewCipher(s.AppSKey[:])
|
cipher, err := aes.NewCipher(s.AppSKey[:])
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package sx126x
|
||||||
|
|
||||||
|
// SX126X radio transceiver has several pins that control
|
||||||
|
// RF_IN, RF_OUT, NSS, and BUSY.
|
||||||
|
// This interface allows the creation of struct
|
||||||
|
// that can drive the RF Switch (Used in Lora RX and Lora Tx)
|
||||||
|
type RadioController interface {
|
||||||
|
Init() error
|
||||||
|
SetRfSwitchMode(mode int) error
|
||||||
|
SetNss(state bool) error
|
||||||
|
WaitWhileBusy() error
|
||||||
|
SetupInterrupts(handler func()) error
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
//go:build gnse
|
||||||
|
|
||||||
|
/*
|
||||||
|
Generic Node Sensor Edition
|
||||||
|
RFSwitch
|
||||||
|
|
||||||
|
Disable Switch : PB8=OFF PA0=OFF PA1=OFF
|
||||||
|
Enable RX : PB8=ON PA0=ON PA1=OFF
|
||||||
|
Enable TX RFO LP : PB8=ON PA0=ON PA1=ON
|
||||||
|
Enable TX RFO HP : PB8=ON PA0=OFF PA1=ON
|
||||||
|
*/
|
||||||
|
package sx126x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RadioControl for GNSE board.
|
||||||
|
type RadioControl struct {
|
||||||
|
STM32RadioControl
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRadioControl() *RadioControl {
|
||||||
|
return &RadioControl{STM32RadioControl{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init pins needed for controlling rx/tx
|
||||||
|
func (rc *RadioControl) Init() error {
|
||||||
|
machine.PA0.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
machine.PA1.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
machine.PB8.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *RadioControl) SetRfSwitchMode(mode int) error {
|
||||||
|
switch mode {
|
||||||
|
|
||||||
|
case RFSWITCH_TX_HP:
|
||||||
|
machine.PA0.Set(false)
|
||||||
|
machine.PA1.Set(true)
|
||||||
|
machine.PB8.Set(true)
|
||||||
|
|
||||||
|
case RFSWITCH_TX_LP:
|
||||||
|
machine.PA0.Set(true)
|
||||||
|
machine.PA1.Set(true)
|
||||||
|
machine.PB8.Set(true)
|
||||||
|
|
||||||
|
case RFSWITCH_RX:
|
||||||
|
machine.PA0.Set(true)
|
||||||
|
machine.PA1.Set(false)
|
||||||
|
machine.PB8.Set(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
//go:build lorae5
|
||||||
|
|
||||||
|
/*
|
||||||
|
LoRa-E5 module ONLY transmits through RFO_HP:
|
||||||
|
|
||||||
|
Receive: PA4=1, PA5=0
|
||||||
|
Transmit(high output power, SMPS mode): PA4=0, PA5=1
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sx126x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RadioControl for LoRa-E5 board.
|
||||||
|
type RadioControl struct {
|
||||||
|
STM32RadioControl
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRadioControl() *RadioControl {
|
||||||
|
return &RadioControl{STM32RadioControl: STM32RadioControl{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init pins needed for controlling rx/tx
|
||||||
|
func (rc *RadioControl) Init() error {
|
||||||
|
machine.PA4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
machine.PB5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *RadioControl) SetRfSwitchMode(mode int) error {
|
||||||
|
switch mode {
|
||||||
|
|
||||||
|
case RFSWITCH_RX:
|
||||||
|
machine.PA4.Set(true)
|
||||||
|
machine.PB5.Set(false)
|
||||||
|
case RFSWITCH_TX_LP:
|
||||||
|
return errLowPowerTxNotSupported
|
||||||
|
case RFSWITCH_TX_HP:
|
||||||
|
machine.PA4.Set(false)
|
||||||
|
machine.PB5.Set(true)
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
//go:build !stm32wlx
|
||||||
|
|
||||||
|
package sx126x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RadioControl for boards that are connected using normal pins.
|
||||||
|
type RadioControl struct {
|
||||||
|
nssPin, busyPin, dio1Pin machine.Pin
|
||||||
|
rxPin, txLowPin, txHighPin machine.Pin
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRadioControl(nssPin, busyPin, dio1Pin,
|
||||||
|
rxPin, txLowPin, txHighPin machine.Pin) *RadioControl {
|
||||||
|
return &RadioControl{
|
||||||
|
nssPin: nssPin,
|
||||||
|
busyPin: busyPin,
|
||||||
|
dio1Pin: dio1Pin,
|
||||||
|
rxPin: rxPin,
|
||||||
|
txLowPin: txLowPin,
|
||||||
|
txHighPin: txHighPin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNss sets the NSS line aka chip select for SPI.
|
||||||
|
func (rc *RadioControl) SetNss(state bool) error {
|
||||||
|
rc.nssPin.Set(state)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitWhileBusy wait until the radio is no longer busy
|
||||||
|
func (rc *RadioControl) WaitWhileBusy() error {
|
||||||
|
count := 100
|
||||||
|
for count > 0 {
|
||||||
|
if !rc.busyPin.Get() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
count--
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
return errWaitWhileBusyTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init() configures whatever needed for sx126x radio control
|
||||||
|
func (rc *RadioControl) Init() error {
|
||||||
|
rc.nssPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
rc.busyPin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// add interrupt handler for Radio IRQs for pins
|
||||||
|
func (rc *RadioControl) SetupInterrupts(handler func()) error {
|
||||||
|
irqHandler = handler
|
||||||
|
|
||||||
|
rc.dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
|
||||||
|
if err := rc.dio1Pin.SetInterrupt(machine.PinRising, handleInterrupt); err != nil {
|
||||||
|
return errRadioNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var irqHandler func()
|
||||||
|
|
||||||
|
func handleInterrupt(machine.Pin) {
|
||||||
|
irqHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *RadioControl) SetRfSwitchMode(mode int) error {
|
||||||
|
switch mode {
|
||||||
|
|
||||||
|
case RFSWITCH_RX:
|
||||||
|
rc.rxPin.Set(true)
|
||||||
|
rc.txLowPin.Set(false)
|
||||||
|
rc.txHighPin.Set(false)
|
||||||
|
case RFSWITCH_TX_LP:
|
||||||
|
rc.rxPin.Set(false)
|
||||||
|
rc.txLowPin.Set(true)
|
||||||
|
rc.txHighPin.Set(false)
|
||||||
|
case RFSWITCH_TX_HP:
|
||||||
|
rc.rxPin.Set(false)
|
||||||
|
rc.txLowPin.Set(false)
|
||||||
|
rc.txHighPin.Set(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -4,40 +4,28 @@ package sx126x
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"device/stm32"
|
"device/stm32"
|
||||||
"errors"
|
|
||||||
"machine"
|
"runtime/interrupt"
|
||||||
"tinygo.org/x/drivers"
|
|
||||||
"tinygo.org/x/drivers/lora"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates a new SX126x connection.
|
// STM32RadioControl helps implement the RadioController interface
|
||||||
func New(spi drivers.SPI) *Device {
|
type STM32RadioControl struct {
|
||||||
c := make(chan lora.RadioEvent, 10)
|
irqHandler func()
|
||||||
d := Device{
|
|
||||||
spi: spi,
|
|
||||||
radioEventChan: c,
|
|
||||||
}
|
|
||||||
if d.spi == machine.SPI3 {
|
|
||||||
d.SubGhzInit()
|
|
||||||
d.SetDeviceType(DEVICE_TYPE_SX1262)
|
|
||||||
} else {
|
|
||||||
panic("Driver only support SUBGHZSPI (SPI3) on stm32wlx targets")
|
|
||||||
}
|
|
||||||
return &d
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpiSetNss Sets the NSS line
|
// SetNss sets the NSS line aka chip select for SPI.
|
||||||
func (d *Device) SpiSetNss(state bool) {
|
func (rc *STM32RadioControl) SetNss(state bool) error {
|
||||||
if state {
|
if state {
|
||||||
stm32.PWR.SUBGHZSPICR.SetBits(stm32.PWR_SUBGHZSPICR_NSS)
|
stm32.PWR.SUBGHZSPICR.SetBits(stm32.PWR_SUBGHZSPICR_NSS)
|
||||||
} else {
|
} else {
|
||||||
stm32.PWR.SUBGHZSPICR.ClearBits(stm32.PWR_SUBGHZSPICR_NSS)
|
stm32.PWR.SUBGHZSPICR.ClearBits(stm32.PWR_SUBGHZSPICR_NSS)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitBusy sleep until all busy flags clears
|
// WaitWhileBusy wait until the radio is no longer busy
|
||||||
func (d *Device) WaitBusy() error {
|
func (rc *STM32RadioControl) WaitWhileBusy() error {
|
||||||
count := 100
|
count := 100
|
||||||
var rfbusyms, rfbusys bool
|
var rfbusyms, rfbusys bool
|
||||||
for count > 0 {
|
for count > 0 {
|
||||||
@@ -49,12 +37,11 @@ func (d *Device) WaitBusy() error {
|
|||||||
}
|
}
|
||||||
count--
|
count--
|
||||||
}
|
}
|
||||||
return errors.New("WaitBusy Timeout")
|
return errWaitWhileBusyTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubGhzInit() configures internal SX1262's SPI bus.
|
// init() configures whatever needed for sx126x radio control
|
||||||
func (d *Device) SubGhzInit() {
|
func init() {
|
||||||
|
|
||||||
// Enable APB3 Periph clock and delay
|
// Enable APB3 Periph clock and delay
|
||||||
stm32.RCC.APB3ENR.SetBits(stm32.RCC_APB3ENR_SUBGHZSPIEN)
|
stm32.RCC.APB3ENR.SetBits(stm32.RCC_APB3ENR_SUBGHZSPIEN)
|
||||||
_ = stm32.RCC.APB3ENR.Get()
|
_ = stm32.RCC.APB3ENR.Get()
|
||||||
@@ -81,5 +68,18 @@ func (d *Device) SubGhzInit() {
|
|||||||
stm32.SPI3.CR1.Set(stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI | (0b010 << 3) | stm32.SPI_CR1_SSM)
|
stm32.SPI3.CR1.Set(stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI | (0b010 << 3) | stm32.SPI_CR1_SSM)
|
||||||
stm32.SPI3.CR2.Set(stm32.SPI_CR2_FRXTH | (0b111 << 8))
|
stm32.SPI3.CR2.Set(stm32.SPI_CR2_FRXTH | (0b111 << 8))
|
||||||
stm32.SPI3.CR1.SetBits(stm32.SPI_CR1_SPE)
|
stm32.SPI3.CR1.SetBits(stm32.SPI_CR1_SPE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *STM32RadioControl) SetupInterrupts(handler func()) error {
|
||||||
|
irqHandler = handler
|
||||||
|
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, handleInterrupt)
|
||||||
|
intr.Enable()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var irqHandler func()
|
||||||
|
|
||||||
|
func handleInterrupt(interrupt.Interrupt) {
|
||||||
|
irqHandler()
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
//go:build nucleowl55jc
|
//go:build nucleowl55jc
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Nucleo WL55JC1
|
Nucleo WL55JC1
|
||||||
RFSwitch
|
RFSwitch
|
||||||
|
|
||||||
+-----------+---------+------------+------------+
|
+-----------+---------+------------+------------+
|
||||||
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
|
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
|
||||||
@@ -13,42 +13,48 @@
|
|||||||
| RX | HIGH | LOW | HIGH |
|
| RX | HIGH | LOW | HIGH |
|
||||||
+-----------+----------+-----------+------------+
|
+-----------+----------+-----------+------------+
|
||||||
*/
|
*/
|
||||||
package common
|
package sx126x
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"machine"
|
"machine"
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CustomSwitch struct {
|
// RadioControl for Nucleo WL55JC1 board.
|
||||||
|
type RadioControl struct {
|
||||||
|
STM32RadioControl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CustomSwitch) InitRFSwitch() {
|
func NewRadioControl() *RadioControl {
|
||||||
|
return &RadioControl{STM32RadioControl{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init pins needed for controlling rx/tx
|
||||||
|
func (rc *RadioControl) Init() error {
|
||||||
machine.PC4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.PC4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
machine.PC5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.PC5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
machine.PC3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.PC3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s CustomSwitch) SetRfSwitchMode(mode int) error {
|
func (rc *RadioControl) SetRfSwitchMode(mode int) error {
|
||||||
switch mode {
|
switch mode {
|
||||||
|
|
||||||
case sx126x.RFSWITCH_TX_HP:
|
case RFSWITCH_TX_HP:
|
||||||
machine.PC4.Set(false)
|
machine.PC4.Set(false)
|
||||||
machine.PC5.Set(true)
|
machine.PC5.Set(true)
|
||||||
machine.PC3.Set(true)
|
machine.PC3.Set(true)
|
||||||
|
|
||||||
case sx126x.RFSWITCH_TX_LP:
|
case RFSWITCH_TX_LP:
|
||||||
machine.PC4.Set(true)
|
machine.PC4.Set(true)
|
||||||
machine.PC5.Set(true)
|
machine.PC5.Set(true)
|
||||||
machine.PC3.Set(true)
|
machine.PC3.Set(true)
|
||||||
|
|
||||||
case sx126x.RFSWITCH_RX:
|
case RFSWITCH_RX:
|
||||||
machine.PC4.Set(true)
|
machine.PC4.Set(true)
|
||||||
machine.PC5.Set(false)
|
machine.PC5.Set(false)
|
||||||
machine.PC3.Set(true)
|
machine.PC3.Set(true)
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
+53
-40
@@ -13,13 +13,13 @@ import (
|
|||||||
"tinygo.org/x/drivers/lora"
|
"tinygo.org/x/drivers/lora"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SX126X radio transceiver RF_IN and RF_OUT may be connected
|
var (
|
||||||
// to RF Switch. This interface allows the creation of struct
|
errWaitWhileBusyTimeout = errors.New("WaitWhileBusy Timeout")
|
||||||
// that can drive the RF Switch (Used in Lora RX and Lora Tx)
|
errLowPowerTxNotSupported = errors.New("RFSWITCH_TX_LP not supported")
|
||||||
type RFSwitch interface {
|
errRadioNotFound = errors.New("LoRa radio not found")
|
||||||
InitRFSwitch()
|
errUnexpectedRxRadioEvent = errors.New("Unexpected Radio Event during RX")
|
||||||
SetRfSwitchMode(mode int) error
|
errUnexpectedTxRadioEvent = errors.New("Unexpected Radio Event during TX")
|
||||||
}
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DEVICE_TYPE_SX1261 = iota
|
DEVICE_TYPE_SX1261 = iota
|
||||||
@@ -38,18 +38,26 @@ const (
|
|||||||
SPI_BUFFER_SIZE = 256
|
SPI_BUFFER_SIZE = 256
|
||||||
)
|
)
|
||||||
|
|
||||||
// Device wraps an SPI connection to a SX127x device.
|
// Device wraps an SPI connection to a SX126x device.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
spi drivers.SPI // SPI bus for module communication
|
spi drivers.SPI // SPI bus for module communication
|
||||||
rstPin, csPin machine.Pin // GPIOs for reset and chip select
|
rstPin machine.Pin // GPIO for reset pin
|
||||||
radioEventChan chan lora.RadioEvent // Channel for Receiving events
|
radioEventChan chan lora.RadioEvent // Channel for Receiving events
|
||||||
loraConf lora.Config // Current Lora configuration
|
loraConf lora.Config // Current Lora configuration
|
||||||
rfswitch RFSwitch // RF Switch, if any
|
controller RadioController // to manage interactions with the radio
|
||||||
deepSleep bool // Internal Sleep state
|
deepSleep bool // Internal Sleep state
|
||||||
deviceType int // sx1261,sx1262,sx1268 (defaults sx1261)
|
deviceType int // sx1261,sx1262,sx1268 (defaults sx1261)
|
||||||
spiBuffer [SPI_BUFFER_SIZE]uint8
|
spiBuffer [SPI_BUFFER_SIZE]uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New creates a new SX126x connection.
|
||||||
|
func New(spi drivers.SPI) *Device {
|
||||||
|
return &Device{
|
||||||
|
spi: spi,
|
||||||
|
radioEventChan: make(chan lora.RadioEvent, 10),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SX126X_RTC_FREQ_IN_HZ uint32 = 64000
|
SX126X_RTC_FREQ_IN_HZ uint32 = 64000
|
||||||
)
|
)
|
||||||
@@ -79,10 +87,15 @@ func (d *Device) SetDeviceType(devType int) {
|
|||||||
d.deviceType = devType
|
d.deviceType = devType
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRfSwitch let you define a custom RF Switch driver if needed
|
// SetRadioControl let you define the RadioController
|
||||||
func (d *Device) SetRfSwitch(rfswitch RFSwitch) {
|
func (d *Device) SetRadioController(rc RadioController) error {
|
||||||
d.rfswitch = rfswitch
|
d.controller = rc
|
||||||
d.rfswitch.InitRFSwitch()
|
if err := d.controller.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.controller.SetupInterrupts(d.HandleInterrupt)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
@@ -126,8 +139,8 @@ func (d *Device) SetFs() {
|
|||||||
|
|
||||||
// SetTxContinuousWave set device in test mode to generate a continuous wave (RF tone)
|
// SetTxContinuousWave set device in test mode to generate a continuous wave (RF tone)
|
||||||
func (d *Device) SetTxContinuousWave() {
|
func (d *Device) SetTxContinuousWave() {
|
||||||
if d.rfswitch != nil {
|
if d.controller != nil {
|
||||||
d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP)
|
d.controller.SetRfSwitchMode(RFSWITCH_TX_HP)
|
||||||
}
|
}
|
||||||
d.ExecSetCommand(SX126X_CMD_SET_TX_CONTINUOUS_WAVE, []uint8{})
|
d.ExecSetCommand(SX126X_CMD_SET_TX_CONTINUOUS_WAVE, []uint8{})
|
||||||
}
|
}
|
||||||
@@ -136,8 +149,8 @@ func (d *Device) SetTxContinuousWave() {
|
|||||||
// Take care to initialize all Lora settings like it's done in Tx before calling this function
|
// Take care to initialize all Lora settings like it's done in Tx before calling this function
|
||||||
// If you don't init properly all the settings, it'll fail
|
// If you don't init properly all the settings, it'll fail
|
||||||
func (d *Device) SetTxContinuousPreamble() {
|
func (d *Device) SetTxContinuousPreamble() {
|
||||||
if d.rfswitch != nil {
|
if d.controller != nil {
|
||||||
d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP)
|
d.controller.SetRfSwitchMode(RFSWITCH_TX_HP)
|
||||||
}
|
}
|
||||||
d.ExecSetCommand(SX126X_CMD_SET_TX_INFINITE_PREAMBLE, []uint8{})
|
d.ExecSetCommand(SX126X_CMD_SET_TX_INFINITE_PREAMBLE, []uint8{})
|
||||||
}
|
}
|
||||||
@@ -235,25 +248,25 @@ func (d *Device) SetRxTxFallbackMode(fallbackMode uint8) {
|
|||||||
// ReadRegister reads register value
|
// ReadRegister reads register value
|
||||||
func (d *Device) ReadRegister(addr, size uint16) ([]uint8, error) {
|
func (d *Device) ReadRegister(addr, size uint16) ([]uint8, error) {
|
||||||
d.CheckDeviceReady()
|
d.CheckDeviceReady()
|
||||||
d.SpiSetNss(false)
|
d.controller.SetNss(false)
|
||||||
// Send command
|
// Send command
|
||||||
cmd := []uint8{SX126X_CMD_READ_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF), 0x00}
|
cmd := []uint8{SX126X_CMD_READ_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF), 0x00}
|
||||||
d.spi.Tx(cmd, nil)
|
d.spi.Tx(cmd, nil)
|
||||||
ret := d.spiBuffer[0:size]
|
ret := d.spiBuffer[0:size]
|
||||||
d.spi.Tx(nil, ret)
|
d.spi.Tx(nil, ret)
|
||||||
d.SpiSetNss(true)
|
d.controller.SetNss(true)
|
||||||
d.WaitBusy()
|
d.controller.WaitWhileBusy()
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteRegister writes value to register
|
// WriteRegister writes value to register
|
||||||
func (d *Device) WriteRegister(addr uint16, data []uint8) {
|
func (d *Device) WriteRegister(addr uint16, data []uint8) {
|
||||||
d.CheckDeviceReady()
|
d.CheckDeviceReady()
|
||||||
d.SpiSetNss(false)
|
d.controller.SetNss(false)
|
||||||
cmd := []uint8{SX126X_CMD_WRITE_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF)}
|
cmd := []uint8{SX126X_CMD_WRITE_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF)}
|
||||||
d.spi.Tx(append(cmd, data...), nil)
|
d.spi.Tx(append(cmd, data...), nil)
|
||||||
d.SpiSetNss(true)
|
d.controller.SetNss(true)
|
||||||
d.WaitBusy()
|
d.controller.WaitWhileBusy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBuffer write data from current buffer position
|
// WriteBuffer write data from current buffer position
|
||||||
@@ -466,12 +479,12 @@ func (d *Device) SetModulationParams(spreadingFactor, bandwidth, codingRate, low
|
|||||||
// CheckDeviceReady sleep until all busy flags clears
|
// CheckDeviceReady sleep until all busy flags clears
|
||||||
func (d *Device) CheckDeviceReady() error {
|
func (d *Device) CheckDeviceReady() error {
|
||||||
if d.deepSleep == true {
|
if d.deepSleep == true {
|
||||||
d.SpiSetNss(false)
|
d.controller.SetNss(false)
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(time.Millisecond)
|
||||||
d.SpiSetNss(true)
|
d.controller.SetNss(true)
|
||||||
d.deepSleep = false
|
d.deepSleep = false
|
||||||
}
|
}
|
||||||
return d.WaitBusy()
|
return d.controller.WaitWhileBusy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecSetCommand send a command to configure the peripheral
|
// ExecSetCommand send a command to configure the peripheral
|
||||||
@@ -482,24 +495,24 @@ func (d *Device) ExecSetCommand(cmd uint8, buf []uint8) {
|
|||||||
} else {
|
} else {
|
||||||
d.deepSleep = false
|
d.deepSleep = false
|
||||||
}
|
}
|
||||||
d.SpiSetNss(false)
|
d.controller.SetNss(false)
|
||||||
// Send command and params
|
// Send command and params
|
||||||
d.spi.Tx(append([]uint8{cmd}, buf...), nil)
|
d.spi.Tx(append([]uint8{cmd}, buf...), nil)
|
||||||
d.SpiSetNss(true)
|
d.controller.SetNss(true)
|
||||||
if cmd != SX126X_CMD_SET_SLEEP {
|
if cmd != SX126X_CMD_SET_SLEEP {
|
||||||
d.WaitBusy()
|
d.controller.WaitWhileBusy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecGetCommand queries the peripheral the peripheral
|
// ExecGetCommand queries the peripheral the peripheral
|
||||||
func (d *Device) ExecGetCommand(cmd uint8, size uint8) []uint8 {
|
func (d *Device) ExecGetCommand(cmd uint8, size uint8) []uint8 {
|
||||||
d.CheckDeviceReady()
|
d.CheckDeviceReady()
|
||||||
d.SpiSetNss(false)
|
d.controller.SetNss(false)
|
||||||
// Send the command and flush first status byte (as not used)
|
// Send the command and flush first status byte (as not used)
|
||||||
d.spi.Tx([]uint8{cmd, 0x00}, nil)
|
d.spi.Tx([]uint8{cmd, 0x00}, nil)
|
||||||
d.spi.Tx(nil, d.spiBuffer[:size])
|
d.spi.Tx(nil, d.spiBuffer[:size])
|
||||||
d.SpiSetNss(true)
|
d.controller.SetNss(true)
|
||||||
d.WaitBusy()
|
d.controller.WaitWhileBusy()
|
||||||
return d.spiBuffer[:size]
|
return d.spiBuffer[:size]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +523,7 @@ func (d *Device) ExecGetCommand(cmd uint8, size uint8) []uint8 {
|
|||||||
// SetFrequency() Sets current Lora Frequency
|
// SetFrequency() Sets current Lora Frequency
|
||||||
// NB: Change will be applied at next RX / TX
|
// NB: Change will be applied at next RX / TX
|
||||||
func (d *Device) SetFrequency(freq uint32) {
|
func (d *Device) SetFrequency(freq uint32) {
|
||||||
d.loraConf.Freq = d.loraConf.Freq
|
d.loraConf.Freq = freq
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetIqMode() defines the current IQ Mode (Standard/Inverted)
|
// SetIqMode() defines the current IQ Mode (Standard/Inverted)
|
||||||
@@ -582,8 +595,8 @@ func (d *Device) Tx(pkt []uint8, timeoutMs uint32) error {
|
|||||||
return lora.ErrUndefinedLoraConf
|
return lora.ErrUndefinedLoraConf
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.rfswitch != nil {
|
if d.controller != nil {
|
||||||
err := d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP)
|
err := d.controller.SetRfSwitchMode(RFSWITCH_TX_HP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -605,7 +618,7 @@ func (d *Device) Tx(pkt []uint8, timeoutMs uint32) error {
|
|||||||
|
|
||||||
msg := <-d.GetRadioEventChan()
|
msg := <-d.GetRadioEventChan()
|
||||||
if msg.EventType != lora.RadioEventTxDone {
|
if msg.EventType != lora.RadioEventTxDone {
|
||||||
return errors.New("Unexpected Radio Event while TX")
|
return errUnexpectedTxRadioEvent
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -616,8 +629,8 @@ func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
|
|||||||
return nil, lora.ErrUndefinedLoraConf
|
return nil, lora.ErrUndefinedLoraConf
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.rfswitch != nil {
|
if d.controller != nil {
|
||||||
err := d.rfswitch.SetRfSwitchMode(RFSWITCH_RX)
|
err := d.controller.SetRfSwitchMode(RFSWITCH_RX)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -637,7 +650,7 @@ func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
|
|||||||
if msg.EventType == lora.RadioEventTimeout {
|
if msg.EventType == lora.RadioEventTimeout {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
} else if msg.EventType != lora.RadioEventRxDone {
|
} else if msg.EventType != lora.RadioEventRxDone {
|
||||||
return nil, errors.New("Unexpected Radio Event while RX")
|
return nil, errUnexpectedRxRadioEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
pLen, pStart := d.GetRxBufferStatus()
|
pLen, pStart := d.GetRxBufferStatus()
|
||||||
|
|||||||
Reference in New Issue
Block a user