Compare commits

..

60 Commits

Author SHA1 Message Date
deadprogram 820298bfe3 all: prepare for release 0.24.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-12 22:30:48 +01:00
deadprogram 46cbacbe03 docs: add sx127x to list of supported devices
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-12 21:33:36 +01:00
deadprogram 9d4847fc6d sx12xx: add README files for some basic explanation
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-12 21:33:36 +01:00
deadprogram 4de0afc224 build: work around for CVE-2022-24765
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-12 08:39:28 +01:00
deadprogram 21fa184b76 build: update to actions/checkout@v3
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-02-12 08:39:28 +01:00
Olivier Fauchon fae4307190 sx127x: Fix bug in SetTxPower, SetPreambleLength 2023-02-01 09:08:43 +01:00
Olivier Fauchon 7f61bda24c lorawan: Update AU915 RegionSet modulations 2023-02-01 08:53:21 +01:00
deadprogram e613ab1eb3 sx127x: use select for timeout
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-31 23:54:43 +01:00
Olivier Fauchon d57d9b6c74 sx127x: fix typos, provide corrections 2023-01-31 23:54:43 +01:00
Olivier Fauchon 07ba9ec0fd fix bandwidth,tx power in lora/sx126x/lora_continuous example 2023-01-31 23:54:20 +01:00
Olivier Fauchon fa0cf6ec7f lorawan cleanup and optimizations:
- Remove all default loraconf default initialisation in lorawan examples (Region Settings takes care of this now)
- Remove retries while receiving JoinAccept and increase LoraRX to 10s (no need to wait more than 10s)
- Add constants for RegionSetting default frequencies
- Standardize all lora default configurations in lora examples
- Add new AT+LW=NET,(ON|OFF) command in atcmd example
2023-01-31 23:54:13 +01:00
deadprogram 4e687b29c3 mpu6050: add functions to configure clock, and scaling for accelerometer and gyroscope
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-30 10:07:38 +01:00
Olivier Fauchon d283abdb3a lorawan: Fix error in uplink crc 2023-01-29 23:56:19 +01:00
Olivier Fauchon 02d808a6fb lorawan/sx12xx : Code cleanup 2023-01-29 23:56:19 +01:00
Olivier Fauchon d473b9e1dc lorawan/basic-demo: add debug logs at compile time with ldflags 2023-01-29 23:56:19 +01:00
Olivier Fauchon 7cc32dffed lorawan/basic-demo : fix in lorawan debug informations 2023-01-29 23:56:19 +01:00
Olivier Fauchon 58cb44f3f6 sx126x driver: fix in SetBandwidth function 2023-01-29 23:56:19 +01:00
Olivier Fauchon 78cc1afe46 lorawan: Basic implementation of Lorawan Regional Settings and EU868/AU915 regions 2023-01-29 23:56:19 +01:00
deadprogram e6cde8f7ae bmp180: add ReadAltitude() function copied from BMP280 driver
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-27 21:42:08 +01:00
deadprogram 89f113c0ca examples: lora/lorawan add needed build tags for lgt92/featherwing
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-27 00:49:38 +01:00
deadprogram 3b8a808a52 examples: lora/lorawan pin mapping for lgt92 and Adafruit Featherwing
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-27 00:49:38 +01:00
deadprogram 7506a895a2 examples: update lora/lorawan common setup to use sx127x RadioControl
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-27 00:49:38 +01:00
deadprogram 4a950a4474 sx127x: add RadioController interface to match sx126x
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-27 00:49:38 +01:00
deadprogram f2637a87d2 gps: improve parsing and add tests to verify
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-17 07:48:22 +01:00
deadprogram ecb343b3b3 gps: improve error handling
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-17 07:48:22 +01:00
deadprogram 0b62d4fb4a gps: add support for GLL sentence type, add original sentence to gps errors
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-17 07:48:22 +01:00
deadprogram 5b461ec2d1 gps: improve error handling
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-17 07:48:22 +01:00
Vincent Janelle b1c9612158 (#501) make IP.String() method return something sensible 2023-01-16 13:43:59 +01:00
deadprogram 0b6e1d0e78 lorawan: increment devnonce don't just create a new one each join attempt
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram 49e7a6d6c8 sx126x: actually set the frequency when calling SetFrequency()
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram be644b36fe lorawan: simplify GetRand16() function
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram 409723e496 lorawan: set devNonce to new random value on each attempt to join
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram 8aa3530c8b lorawan: increase timeout values for tx/rx
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram 390854ee45 examples: set pin mappings for sx126x external board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram ce5dd65dad sx126x: pre-define all errors to avoid heap allocations
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram 73e9d5bfec sx126x: move RadioController into separate file for clarity
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram 462d2b3315 sx126x: correct RX/TX pin mapping for TheThingsIndustries GNSE board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram 9e70ecbe64 sx126x: there is no dio0 just dio1
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram d6d06396b4 sx126x: refactor to RadioController interface to more easily handle non-STM32WL boards and remove duplicated code
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram 80b2a4a569 lora/lorawan: set functions for keys
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram 556aa9e6d3 examples: lorawan atcmd outputs needed info after Join to reuse, aka 'OTAA provisioning'
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
deadprogram 4bc8ecdbdd lora/lorawan: completed functions needed for Join OTAA process
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 09:33:13 +01:00
sago35 999f42dc03 net/http: add PostForm() 2023-01-11 21:29:12 +01:00
Olivier Fauchon a1b47cd778 lorawan: Add test in Makefile 2023-01-11 07:48:30 +01:00
Olivier Fauchon 889536f7f0 Lorawan uplink support and new lorawan 'basic-demo' example 2023-01-11 07:48:30 +01:00
deadprogram 301e6bc35b examples: improvements to README for LoRaWAN atcmd example
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram 49b390f7bc examples: lorawan atcmd able to join
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram a62fc01d81 lora/lorawan: implement minimal Join() function, and supporting functions
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram 36dfdf568e examples: move atcmd into lorawan
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram 7b5a3970ca lorawan: add initial LoRaWAN stack support
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram 3433364586 examples: example with LoRa AT command set implementation
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram 338be928c9 sx126x, sx127x: remove extra Lora in functions to avoid stuttering
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
deadprogram 98943393a5 sx127x: correct header type implicit/explcit as they were reversed
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
deadprogram 6b817faed3 sx126x: add Reset() and needed pin
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
deadprogram 9b14ee3ec5 sx127x: lora rx/tx example working, now assumes pybadge+featherwing
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
deadprogram 6d51370512 lora: created shared RadioEvent
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
deadprogram 2005d7d483 lora: move shared config for sx126x/sx127x to single package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
Olivier Fauchon f15eec822f sx127x: Ajout du support d'interruptions RXTimeout (DIO1) 2023-01-09 07:36:07 +01:00
Olivier Fauchon dc4f0c974a sx127x: Major cleanup/rewrite, and tests pass with lorawan stack tests 2023-01-09 07:36:07 +01:00
Olivier Fauchon 28ddb2681d sx127x: Driver for Semtech sx127x radio modules
This first version of the driver has been tested with BB-FRM9x module
2023-01-09 07:36:07 +01:00
68 changed files with 2446 additions and 592 deletions
+4 -1
View File
@@ -13,8 +13,11 @@ jobs:
runs-on: ubuntu-latest
container: tinygo/tinygo-dev
steps:
- name: Work around CVE-2022-24765
# We're not on a multi-user machine, so this is safe.
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: TinyGo version check
run: tinygo version
- name: Enforce Go Formatted Code
+77
View File
@@ -1,3 +1,80 @@
0.24.0
---
- **new devices**
- **lora**
- created shared RadioEvent
- move shared config for sx126x/sx127x to single package
- **lorawan**
- add initial LoRaWAN stack support
- Basic implementation of Lorawan Regional Settings and EU868/AU915 regions
- **qmi8658c**
- Add support for the QMI8658C sensor (#467)
- **sh1106**
- add support for SH1106 display driver
- **sx127x**
- Driver for Semtech sx127x radio modules
- **enhancements**
- **bme280**
- improve config support
- add ReadAltitude() function copied from BMP280 driver
- **buzzer**
- make all note durations float64
- no tone during rest
- **dht22**
- update DHT22 receive to use runtime/interrupt
- **gps**
- add support for GLL sentence type, add original sentence to gps errors
- improve error handling
- improve parsing and add tests to verify
- **microbitmatrix**
- add link to schema for microbit V2
- add smoke test for microbitmatrix with microbit-v2
- add support for brightness of led pixels
- harmonize v1 and v2 implementation
- move Size() to version agnostic part
- **mpu6050**
- add functions to configure clock, and scaling for accelerometer and gyroscope
- **net/http**
- add PostForm()
- **sx126x**
- add Reset() and needed pin
- move RadioController into separate file for clarity
- pre-define all errors to avoid heap allocations
- refactor to RadioController interface to more easily handle non-STM32WL boards and remove duplicated code
- **vl53l1x**
- Add getter for the effective SPAD count
- **wifinina**
- add support for http server (#480)
- **bugfixes**
- **lsm303agr**
- fix I2C address auto increment for multi data read
- **net**
- (#501) make IP.String() method return something sensible
- **mpu6050**
- return I2C error when configuring fails
- **sx126x**
- fix in SetBandwidth function
- actually set the frequency when calling SetFrequency()
- correct RX/TX pin mapping for TheThingsIndustries GNSE board
- **examples**
- **LoRaWAN**
- example with LoRaWAN AT command set implementation
- basic example
- update all remaining examples for refactored API
- **sx126x**
- fix bandwidth,tx power in lora//lora_continuous example
- **sx127x**
- rx/tx example
- **build**
- remove older format build tags
- update to actions/checkout@v3
- work around for CVE-2022-24765
0.23.0
---
- **new devices**
+3
View File
@@ -249,6 +249,9 @@ endif
@md5sum ./build/test.uf2
tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go
@md5sum ./build/test.uf2
tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/lora/lorawan/atcmd/
@md5sum ./build/test.hex
# rwildcard is a recursive version of $(wildcard)
# https://blog.jgc.org/2011/07/gnu-make-recursive-wildcard-function.html
+1
View File
@@ -146,6 +146,7 @@ The following 90 devices are supported.
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
| [XPT2046 touch controller](http://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf) | GPIO |
| [Semtech SX126x Lora](https://www.semtech.com/products/wireless-rf/lora-connect/sx1261) | SPI |
| [Semtech SX127x Lora](https://www.semtech.com/products/wireless-rf/lora-connect/sx1276) | SPI |
| [SSD1289 TFT color display](http://aitendo3.sakura.ne.jp/aitendo_data/product_img/lcd/tft2/M032C1289TP/3.2-SSD1289.pdf) | GPIO |
## Contributing
+16
View File
@@ -6,6 +6,7 @@
package bmp180 // import "tinygo.org/x/drivers/bmp180"
import (
"math"
"time"
"tinygo.org/x/drivers"
@@ -123,6 +124,21 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
return 1000 * (p + ((x1 + x2 + 3791) >> 4)), nil
}
// ReadAltitude returns the current altitude in meters based on the
// current barometric pressure and estimated pressure at sea level.
// Calculation is based on code from Adafruit BME280 library
//
// https://github.com/adafruit/Adafruit_BME280_Library
func (d *Device) ReadAltitude() (int32, error) {
mPa, err := d.ReadPressure()
if err != nil {
return 0, err
}
atmP := float32(mPa) / 100000
return int32(44330.0 * (1.0 - math.Pow(float64(atmP/SEALEVEL_PRESSURE), 0.1903))), nil
}
// rawTemp returns the sensor's raw values of the temperature
func (d *Device) rawTemp() (int32, error) {
d.bus.WriteRegister(uint8(d.Address), REG_CTRL, []byte{CMD_TEMP})
+4
View File
@@ -28,3 +28,7 @@ const (
// ULTRAHIGHRESOLUTION is the highest oversampling mode of the pressure measurement.
ULTRAHIGHRESOLUTION
)
const (
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
)
+3
View File
@@ -27,6 +27,9 @@ func main() {
pressure, _ := sensor.ReadPressure()
println("Pressure", float32(pressure)/100000, "hPa")
altitude, _ := sensor.ReadAltitude()
println("Altitude", altitude, "meters")
time.Sleep(2 * time.Second)
}
}
-29
View File
@@ -1,29 +0,0 @@
# AT-CMD implementation of at-lora command set
```
$ tinygo monitor
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
+AT: OK
+VER: 0.0.1 (sx127x v18)
```
# Building
## Simulator
```
tinygo flash -target pico ./examples/lora/atcmd/
```
## PyBadge with LoRa Featherwing
```
tinygo flash -target pybadge -tags featherwing ./examples/lora/atcmd/
```
## LoRa-E5
```
tinygo flash -target lorae5 ./examples/lora/atcmd/
```
-73
View File
@@ -1,73 +0,0 @@
//go:build gnse || lorae5 || nucleowl55jc
package main
import (
"device/stm32"
"machine"
"runtime/interrupt"
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
"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
)
// do sx126x setup here
func setupLora() (LoraRadio, error) {
loraRadio = sx126x.New(machine.SPI3)
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
// Create RF Switch
var radioSwitch rfswitch.CustomSwitch
loraRadio.SetRfSwitch(radioSwitch)
if state := loraRadio.DetectDevice(); !state {
return nil, errRadioNotFound
}
// Add interrupt handler for Radio IRQs
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
intr.Enable()
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
}
// radioIntHandler will take care of radio interrupts
func radioIntHandler(intr interrupt.Interrupt) {
loraRadio.HandleInterrupt()
}
func firmwareVersion() string {
return "sx126x"
}
func lorarx() ([]byte, error) {
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
}
-85
View File
@@ -1,85 +0,0 @@
//go:build featherwing
package main
import (
"strconv"
"machine"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/sx127x"
)
const (
FREQ = 868100000
LORA_DEFAULT_RXTIMEOUT_MS = 1000
LORA_DEFAULT_TXTIMEOUT_MS = 5000
)
var (
// We assume LoRa Featherwing module is connected to PyBadge:
rstPin = machine.D11
csPin = machine.D10
dio0Pin = machine.D6
dio1Pin = machine.D9
spi = machine.SPI0
loraRadio *sx127x.Device
)
// do sx127x setup here
func setupLora() (LoraRadio, error) {
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dio0Pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
spi.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
loraRadio = sx127x.New(spi, csPin, rstPin)
loraRadio.Reset()
if state := loraRadio.DetectDevice(); !state {
return nil, errRadioNotFound
}
// Setup DIO0 interrupt Handling
if err := dio0Pin.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO0 pin interrupt:", err.Error())
}
// Setup DIO1 interrupt Handling
if err := dio1Pin.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO1 pin interrupt:", err.Error())
}
// Prepare for Lora Operation
loraConf := lora.Config{
Freq: FREQ,
Bw: lora.Bandwidth_500_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
Preamble: 12,
Iq: lora.IQStandard,
Crc: lora.CRCOn,
SyncWord: lora.SyncPrivate,
LoraTxPowerDBm: 20,
}
loraRadio.LoraConfig(loraConf)
return loraRadio, nil
}
func dioIrqHandler(machine.Pin) {
loraRadio.HandleInterrupt()
}
func firmwareVersion() string {
v := loraRadio.GetVersion()
return "sx127x v" + strconv.Itoa(int(v))
}
func lorarx() ([]byte, error) {
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
}
-7
View File
@@ -1,7 +0,0 @@
package main
const VERSION = "0.0.1"
func currentVersion() string {
return VERSION
}
+52
View File
@@ -0,0 +1,52 @@
# AT-CMD implementation of at-lora command set
This example implements the AT command set as used by Seeed in the LoRa-E5 series of boards, but in the form of a TinyGo program that provides a serial interface.
See https://files.seeedstudio.com/products/317990687/res/LoRa-E5%20AT%20Command%20Specification_V1.0%20.pdf for more information.
```
$ tinygo monitor
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
+AT: OK
+VER: 0.0.1 (sx127x v18)
```
# Building
Run the following commands from the main `drivers` directory.
## Simulator
Builds/flashes atcmd console application with simulator instead of actual LoRa radio.
```
tinygo flash -target pico ./examples/lora/lorawan/atcmd/
```
## PyBadge with LoRa Featherwing
Builds/flashes atcmd console application on PyBadge using LoRa Featherwing (RFM95/SX1276).
```
tinygo flash -target pybadge -tags featherwing ./examples/lora/lorawan/atcmd/
```
## LoRa-E5
Builds/flashes atcmd console application on Lora-E5 using onboard SX126x.
```
tinygo flash -target lorae5 ./examples/lora/lorawan/atcmd/
```
## Joining a Public Lorawan Network
```
AT+ID=DevEui,0101010101010101
AT+ID=AppEui,0123012301230213
AT+KEY=APPKEY,AEAEAEAEAEAEAEAAEAEAEAEAEAEAAEAE
AT+LW=NET,ON
AT+JOIN
```
AT+LW=NET,(ON|OFF) command changes Lora Sync Word to connect on public network(ON) or private networks(OFF)
@@ -3,6 +3,9 @@ package main
import (
"encoding/hex"
"strings"
"tinygo.org/x/drivers/examples/lora/lorawan/common"
"tinygo.org/x/drivers/lora/lorawan"
)
// Use to test if connection to module is OK.
@@ -12,7 +15,7 @@ func quicktest() {
// Check firmware version.
func version() {
writeCommandOutput("VER", currentVersion()+" ("+firmwareVersion()+")")
writeCommandOutput("VER", common.CurrentVersion()+" ("+common.FirmwareVersion()+")")
}
// Use to check the ID of the LoRaWAN module, or change the ID.
@@ -23,13 +26,42 @@ func id(args string) error {
param, val, hasComma := strings.Cut(args, ",")
if hasComma {
// set
data := strings.Trim(val, "\"'")
// convert data from hex formatted string
data = strings.ReplaceAll(data, " ", "")
hexdata, err := hex.DecodeString(data)
if err != nil {
writeCommandOutput(cmd, err.Error())
return err
}
switch param {
case "DevAddr":
writeCommandOutput(cmd, "DevAddr, "+val)
if err := session.SetDevAddr(hexdata); err != nil {
writeCommandOutput(cmd, err.Error())
return err
}
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
case "DevEui":
writeCommandOutput(cmd, "DevEui, "+val)
if err := otaa.SetDevEUI(hexdata); err != nil {
writeCommandOutput(cmd, err.Error())
return err
}
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
case "AppEui":
writeCommandOutput(cmd, "AppEui, "+val)
if err := otaa.SetAppEUI(hexdata); err != nil {
writeCommandOutput(cmd, err.Error())
return err
}
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
default:
return errInvalidCommand
}
@@ -40,15 +72,15 @@ func id(args string) error {
// get
switch param {
case "DevAddr":
writeCommandOutput(cmd, "DevAddr, xx:xx:xx:xx")
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
case "DevEui":
writeCommandOutput(cmd, "DevEui, xx:xx:xx:xx:xx:xx:xx:xx")
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
case "AppEui":
writeCommandOutput(cmd, "AppEui, xx:xx:xx:xx:xx:xx:xx:xx")
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
default:
writeCommandOutput(cmd, "DevAddr, xx:xx:xx:xx")
writeCommandOutput(cmd, "DevEui, xx:xx:xx:xx:xx:xx:xx:xx")
writeCommandOutput(cmd, "AppEui, xx:xx:xx:xx:xx:xx:xx:xx")
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
}
return nil
@@ -208,9 +240,38 @@ func rxwin1(setting string) error {
func key(setting string) error {
cmd := "KEY"
writeCommandOutput(cmd, setting)
return nil
// look for comma in args
param, val, hasComma := strings.Cut(setting, ",")
if hasComma {
// set
data := strings.Trim(val, "\"'")
// convert data from hex formatted string
data = strings.ReplaceAll(data, " ", "")
hexdata, err := hex.DecodeString(data)
if err != nil {
writeCommandOutput(cmd, err.Error())
return err
}
switch param {
case "APPKEY":
if err := otaa.SetAppKey(hexdata); err != nil {
writeCommandOutput(cmd, err.Error())
return err
}
writeCommandOutput(cmd, "APPKEY, "+otaa.GetAppKey())
default:
return errInvalidCommand
}
}
// cannot get keys
return errInvalidCommand
}
func fdefault(setting string) error {
@@ -228,9 +289,23 @@ func mode(setting string) error {
}
func join(setting string) error {
// TODO: check that DevEUI, AppEUI, and AppKey have values
cmd := "JOIN"
writeCommandOutput(cmd, "Starting")
if err := lorawan.Join(otaa, session); err != nil {
writeCommandOutput(cmd, err.Error())
return err
}
writeCommandOutput(cmd, "Network joined")
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")
return nil
}
@@ -262,6 +337,17 @@ func delay(setting string) error {
func lw(setting string) error {
cmd := "LW"
param, val, hasComma := strings.Cut(setting, ",")
if hasComma {
if param == "NET" {
if val == "ON" {
lorawan.SetPublicNetwork(true)
} else {
lorawan.SetPublicNetwork(false)
}
}
}
writeCommandOutput(cmd, setting)
return nil
@@ -376,7 +462,7 @@ func sendhex(data string) error {
func recv(setting string) error {
cmd := "RECV"
data, err := lorarx()
data, err := common.Lorarx()
if err != nil {
writeCommandOutput(cmd, "ERROR "+err.Error())
return err
@@ -389,7 +475,7 @@ func recv(setting string) error {
func recvhex(setting string) error {
cmd := "RECVHEX"
data, err := lorarx()
data, err := common.Lorarx()
if err != nil {
writeCommandOutput(cmd, "ERROR "+err.Error())
return err
@@ -11,6 +11,11 @@ package main
import (
"machine"
"time"
"tinygo.org/x/drivers/examples/lora/lorawan/common"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/lora/lorawan"
"tinygo.org/x/drivers/lora/lorawan/region"
)
// change these to test a different UART or pins if available
@@ -20,7 +25,10 @@ var (
rx = machine.UART_RX_PIN
input = make([]byte, 0, 64)
radio LoraRadio
radio lora.Radio
session *lorawan.Session
otaa *lorawan.Otaa
defaultTimeout uint32 = 1000
)
@@ -28,11 +36,17 @@ func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
var err error
radio, err = setupLora()
radio, err = common.SetupLora()
if err != nil {
fail(err.Error())
}
session = &lorawan.Session{}
otaa = &lorawan.Otaa{}
lorawan.UseRadio(radio)
lorawan.UseRegionSettings(region.EU868())
for {
if uart.Buffered() > 0 {
data, _ := uart.ReadByte()
@@ -0,0 +1,44 @@
# Simple Lorawan example
This demo code will connect Lorawan network and send sample uplink message
You may change your Lorawan keys (AppEUI, DevEUI, AppKEY) in key-default.go
```
$ tinygo monitor
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
Lorawan Simple Demo
Start Lorawan Join sequence
loraConnect: Connected !
```
# Building
## Simulator
```
tinygo flash -target pico ./examples/lora/lorawan/basic-demo
```
## PyBadge with LoRa Featherwing
```
tinygo flash -target pybadge -tags featherwing ./examples/lora/lorawan/basic-demo
```
## LoRa-E5
```
tinygo flash -target lorae5 ./examples/lora/lorawan/basic-demo
```
## Enable debugging
You can also enable some debug logs with ldflags :
```
$ tinygo build -ldflags="-X 'main.debug=true'" -target=lorae5
```
@@ -0,0 +1,17 @@
//go:build !customkeys
package main
import (
"tinygo.org/x/drivers/lora/lorawan"
)
// These are sample keys, so the example builds
// Either change here, or create a new go file and use customkeys build tag
func setLorawanKeys() {
otaa.SetAppEUI([]uint8{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
otaa.SetDevEUI([]uint8{0xB3, 0xD5, 0x41, 0x00, 0x0A, 0xF1, 0xA4, 0x45})
otaa.SetAppKey([]uint8{0x12, 0x22, 0xA3, 0xFF, 0x0C, 0x7B, 0x76, 0x7B, 0x8F, 0xD3, 0x12, 0x4F, 0xCE, 0x7A, 0x32, 0x16})
lorawan.SetPublicNetwork(true)
}
+109
View File
@@ -0,0 +1,109 @@
// Simple code for connecting to Lorawan network and uploading sample payload
package main
import (
"errors"
"strconv"
"time"
"tinygo.org/x/drivers/examples/lora/lorawan/common"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/lora/lorawan"
"tinygo.org/x/drivers/lora/lorawan/region"
)
var debug string
const (
LORAWAN_JOIN_TIMEOUT_SEC = 180
LORAWAN_RECONNECT_DELAY_SEC = 15
LORAWAN_UPLINK_DELAY_SEC = 60
)
var (
radio lora.Radio
session *lorawan.Session
otaa *lorawan.Otaa
)
func loraConnect() error {
start := time.Now()
var err error
for time.Since(start) < LORAWAN_JOIN_TIMEOUT_SEC*time.Second {
println("Trying to join network")
err = lorawan.Join(otaa, session)
if err == nil {
println("Connected to network !")
return nil
}
println("Join error:", err, "retrying in", LORAWAN_RECONNECT_DELAY_SEC, "sec")
time.Sleep(time.Second * LORAWAN_RECONNECT_DELAY_SEC)
}
err = errors.New("Unable to join Lorawan network")
println(err.Error())
return err
}
func failMessage(err error) {
println("FATAL:", err)
for {
}
}
func main() {
println("*** Lorawan basic join and uplink demo ***")
// Board specific Lorawan initialization
var err error
radio, err = common.SetupLora()
if err != nil {
failMessage(err)
}
// Required for LoraWan operations
session = &lorawan.Session{}
otaa = &lorawan.Otaa{}
// Connect the lorawan with the Lora Radio device.
lorawan.UseRadio(radio)
lorawan.UseRegionSettings(region.EU868())
// Configure AppEUI, DevEUI, APPKey, and public/private Lorawan Network
setLorawanKeys()
if debug != "" {
println("main: Network joined")
println("main: DevEui, " + otaa.GetDevEUI())
println("main: AppEui, " + otaa.GetAppEUI())
println("main: DevAddr, " + otaa.GetAppKey())
}
// Try to connect Lorawan network
if err := loraConnect(); err != nil {
failMessage(err)
}
if debug != "" {
println("main: NetID, " + otaa.GetNetID())
println("main: NwkSKey, " + session.GetNwkSKey())
println("main: AppSKey, " + session.GetAppSKey())
println("main: Done")
}
// Try to periodicaly send an uplink sample message
upCount := 1
for {
payload := "Hello TinyGo #" + strconv.Itoa(upCount)
if err := lorawan.SendUplink([]byte(payload), session); err != nil {
println("Uplink error:", err)
} else {
println("Uplink success, msg=", payload)
}
println("Sleeping for", LORAWAN_UPLINK_DELAY_SEC, "sec")
time.Sleep(time.Second * LORAWAN_UPLINK_DELAY_SEC)
upCount++
}
}
@@ -0,0 +1,14 @@
//go:build featherwing
package common
import "machine"
var (
// We assume LoRa Featherwing module with sx127x is connected to PyBadge
rstPin = machine.D11
csPin = machine.D10
dio0Pin = machine.D6
dio1Pin = machine.D9
spi = machine.SPI0
)
+13
View File
@@ -0,0 +1,13 @@
//go:build lgt92
package common
import "machine"
var (
rstPin = machine.PB0
csPin = machine.PA15
dio0Pin = machine.PC13
dio1Pin = machine.PB10
spi = machine.SPI0
)
+10
View File
@@ -0,0 +1,10 @@
package common
import (
"errors"
)
var (
errRadioNotFound = errors.New("radio not found")
errRxTimeout = errors.New("radio RX timeout")
)
@@ -1,9 +1,11 @@
//go:build !featherwing && !gnse && !lorae5 && !nucleowl55jc
//go:build !featherwing && !lgt92 && !stm32wlx && !sx126x
package main
package common
import "tinygo.org/x/drivers/lora"
// do simulator setup here
func setupLora() (LoraRadio, error) {
func SetupLora() (lora.Radio, error) {
return &SimLoraRadio{}, nil
}
@@ -27,11 +29,12 @@ func (sr *SimLoraRadio) SetCodingRate(cr uint8) {}
func (sr *SimLoraRadio) SetBandwidth(bw uint8) {}
func (sr *SimLoraRadio) SetCrc(enable bool) {}
func (sr *SimLoraRadio) SetSpreadingFactor(sf uint8) {}
func (sr *SimLoraRadio) LoraConfig(cnf lora.Config) {}
func firmwareVersion() string {
return "simulator " + currentVersion()
func FirmwareVersion() string {
return "simulator " + CurrentVersion()
}
func lorarx() ([]byte, error) {
func Lorarx() ([]byte, error) {
return nil, nil
}
+49
View File
@@ -0,0 +1,49 @@
//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
}
return loraRadio, nil
}
func FirmwareVersion() string {
return "sx126x"
}
func Lorarx() ([]byte, error) {
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
}
+53
View File
@@ -0,0 +1,53 @@
//go:build !stm32wlx && sx126x
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.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
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
}
return loraRadio, nil
}
func FirmwareVersion() string {
return "sx126x"
}
func Lorarx() ([]byte, error) {
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
}
+47
View File
@@ -0,0 +1,47 @@
//go:build featherwing || lgt92
package common
import (
"strconv"
"machine"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/sx127x"
)
const (
FREQ = 868100000
LORA_DEFAULT_RXTIMEOUT_MS = 1000
LORA_DEFAULT_TXTIMEOUT_MS = 5000
)
var (
loraRadio *sx127x.Device
)
// do sx127x setup here
func SetupLora() (lora.Radio, error) {
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
spi.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
loraRadio = sx127x.New(spi, rstPin)
loraRadio.SetRadioController(sx127x.NewRadioControl(csPin, dio0Pin, dio1Pin))
loraRadio.Reset()
if state := loraRadio.DetectDevice(); !state {
return nil, errRadioNotFound
}
return loraRadio, nil
}
func FirmwareVersion() string {
v := loraRadio.GetVersion()
return "sx127x v" + strconv.Itoa(int(v))
}
func Lorarx() ([]byte, error) {
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
}
+7
View File
@@ -0,0 +1,7 @@
package common
const VERSION = "0.0.1"
func CurrentVersion() string {
return VERSION
}
@@ -5,8 +5,6 @@ import (
"machine"
"time"
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/sx126x"
)
@@ -24,12 +22,11 @@ func main() {
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
// Create the driver
loraRadio = sx126x.New(machine.SPI3)
loraRadio = sx126x.New(spi)
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
// Create RF Switch
var radioSwitch rfswitch.CustomSwitch
loraRadio.SetRfSwitch(radioSwitch)
// Create radio controller for target
loraRadio.SetRadioController(newRadioControl())
state := loraRadio.DetectDevice()
if !state {
@@ -38,8 +35,8 @@ func main() {
// Prepare for Lora operation
loraConf := lora.Config{
Freq: FREQ,
Bw: lora.Bandwidth_500_0,
Freq: lora.MHz_868_1,
Bw: lora.Bandwidth_125_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
@@ -48,7 +45,7 @@ func main() {
Iq: lora.IQStandard,
Crc: lora.CRCOn,
SyncWord: lora.SyncPrivate,
LoraTxPowerDBm: 14,
LoraTxPowerDBm: 20,
}
loraRadio.LoraConfig(loraConf)
@@ -76,6 +73,5 @@ func main() {
loraRadio.SetStandby()
time.Sleep(60 * time.Second)
}
}
+19
View File
@@ -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()
}
+9 -23
View File
@@ -4,19 +4,13 @@ package main
// module will be in RX mode between two transmissions
import (
"device/stm32"
"machine"
"runtime/interrupt"
"time"
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/sx126x"
)
const FREQ = 868100000
const (
LORA_DEFAULT_RXTIMEOUT_MS = 1000
LORA_DEFAULT_TXTIMEOUT_MS = 5000
@@ -27,23 +21,19 @@ var (
txmsg = []byte("Hello TinyGO")
)
// radioIntHandler will take care of radio interrupts
func radioIntHandler(intr interrupt.Interrupt) {
loraRadio.HandleInterrupt()
}
func main() {
time.Sleep(3 * time.Second)
println("\n# TinyGo Lora RX/TX test")
println("# ----------------------")
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
// Create the driver
loraRadio = sx126x.New(machine.SPI3)
loraRadio = sx126x.New(spi)
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
// Create RF Switch
var radioSwitch rfswitch.CustomSwitch
loraRadio.SetRfSwitch(radioSwitch)
// Create radio controller for target
loraRadio.SetRadioController(newRadioControl())
// Detect the device
state := loraRadio.DetectDevice()
@@ -51,13 +41,9 @@ func main() {
panic("sx126x not detected.")
}
// Add interrupt handler for Radio IRQs
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
intr.Enable()
loraConf := lora.Config{
Freq: FREQ,
Bw: lora.Bandwidth_500_0,
Freq: lora.MHz_868_1,
Bw: lora.Bandwidth_125_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
@@ -73,10 +59,10 @@ func main() {
var count uint
for {
tStart := time.Now()
start := time.Now()
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)
if err != nil {
println("RX Error: ", err)
+29
View File
@@ -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()
}
-60
View File
@@ -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
}
-42
View File
@@ -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
}
-54
View File
@@ -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
}
+6 -18
View File
@@ -11,8 +11,6 @@ import (
"tinygo.org/x/drivers/sx127x"
)
const FREQ = 868100000
const (
LORA_DEFAULT_RXTIMEOUT_MS = 1000
LORA_DEFAULT_TXTIMEOUT_MS = 5000
@@ -40,13 +38,13 @@ func main() {
println("# ----------------------")
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
SX127X_PIN_RST.Configure(machine.PinConfig{Mode: machine.PinOutput})
SX127X_PIN_CS.Configure(machine.PinConfig{Mode: machine.PinOutput})
SX127X_PIN_DIO0.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
SX127X_PIN_DIO1.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
SX127X_SPI.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
println("main: create and start SX127x driver")
loraRadio = sx127x.New(SX127X_SPI, SX127X_PIN_CS, SX127X_PIN_RST)
loraRadio = sx127x.New(SX127X_SPI, SX127X_PIN_RST)
loraRadio.SetRadioController(sx127x.NewRadioControl(SX127X_PIN_CS, SX127X_PIN_DIO0, SX127X_PIN_DIO1))
loraRadio.Reset()
state := loraRadio.DetectDevice()
if !state {
@@ -55,20 +53,10 @@ func main() {
println("main: sx127x found")
}
// Setup DIO0 interrupt Handling
if err := SX127X_PIN_DIO0.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO0 pin interrupt:", err.Error())
}
// Setup DIO1 interrupt Handling
if err := SX127X_PIN_DIO1.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO1 pin interrupt:", err.Error())
}
// Prepare for Lora Operation
loraConf := lora.Config{
Freq: FREQ,
Bw: lora.Bandwidth_500_0,
Freq: lora.MHz_868_1,
Bw: lora.Bandwidth_125_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
+46 -6
View File
@@ -13,6 +13,39 @@ import (
var (
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
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
}
const (
minimumNMEALength = 7
startingDelimiter = '$'
checksumDelimiter = '*'
)
// Device wraps a connection to a GPS device.
@@ -60,11 +93,11 @@ func (gps *Device) readNextSentence() (sentence string) {
gps.sentence.Reset()
var b byte = ' '
for b != '$' {
for b != startingDelimiter {
b = gps.readNextByte()
}
for b != '*' {
for b != checksumDelimiter {
gps.sentence.WriteByte(b)
b = gps.readNextByte()
}
@@ -126,17 +159,24 @@ func (gps *Device) WriteBytes(bytes []byte) {
}
// validSentence checks if a sentence has been received uncorrupted
// For example, a valid NMEA sentence such as this:
// $GPGLL,3751.65,S,14507.36,E*77
// It has to start with a '$' character.
// It has to have a 5 character long sentence identifier.
// It has to end with a '*' character following by a checksum.
func validSentence(sentence string) error {
if len(sentence) < 4 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
if len(sentence) < minimumNMEALength || sentence[0] != startingDelimiter || sentence[len(sentence)-3] != checksumDelimiter {
return errInvalidNMEASentenceLength
}
var cs byte = 0
for i := 1; i < len(sentence)-3; i++ {
cs ^= sentence[i]
}
checksum := hex.EncodeToString([]byte{cs})
if (checksum[0] != sentence[len(sentence)-2]) || (checksum[1] != sentence[len(sentence)-1]) {
return errInvalidNMEAChecksum
checksum := strings.ToUpper(hex.EncodeToString([]byte{cs}))
if checksum != sentence[len(sentence)-2:len(sentence)] {
return newGPSError(errInvalidNMEAChecksum, sentence,
"expected "+sentence[len(sentence)-2:len(sentence)]+
" got "+checksum)
}
return nil
+47 -32
View File
@@ -1,19 +1,11 @@
package gps
import (
"errors"
"strconv"
"strings"
"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.
type Parser struct {
}
@@ -51,43 +43,63 @@ func NewParser() Parser {
}
// 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 == "" {
err = errEmptyNMEASentence
return
return fix, errEmptyNMEASentence
}
if len(sentence) < 6 {
return fix, errInvalidNMEASentenceLength
}
typ := sentence[3:6]
switch typ {
case "GGA":
// https://docs.novatel.com/OEM7/Content/Logs/GPGGA.htm
fields := strings.Split(sentence, ",")
if len(fields) != 15 {
err = errInvalidGGASentence
return
return fix, errInvalidGGASentence
}
fix.Altitude = findAltitude(fields[9])
fix.Satellites = findSatellites(fields[7])
fix.Longitude = findLongitude(fields[4], fields[5])
fix.Latitude = findLatitude(fields[2], fields[3])
fix.Time = findTime(fields[1])
fix.Latitude = findLatitude(fields[2], fields[3])
fix.Longitude = findLongitude(fields[4], fields[5])
fix.Satellites = findSatellites(fields[7])
fix.Altitude = findAltitude(fields[9])
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[1], fields[2])
fix.Longitude = findLongitude(fields[3], fields[4])
fix.Time = findTime(fields[5])
fix.Valid = (fields[6] == "A")
return fix, nil
case "RMC":
// https://docs.novatel.com/OEM7/Content/Logs/GPRMC.htm
fields := strings.Split(sentence, ",")
if len(fields) != 13 {
err = errInvalidRMCSentence
return
return fix, errInvalidRMCSentence
}
fix.Longitude = findLongitude(fields[5], fields[6])
fix.Latitude = findLatitude(fields[3], fields[4])
fix.Time = findTime(fields[1])
fix.Valid = (fields[2] == "A")
fix.Latitude = findLatitude(fields[3], fields[4])
fix.Longitude = findLongitude(fields[5], fields[6])
fix.Speed = findSpeed(fields[7])
fix.Heading = findHeading(fields[8])
fix.Valid = (len(fields[2]) > 0 && fields[2][0:1] == "A")
default:
err = errUnknownNMEASentence
return fix, nil
}
return
return fix, newGPSError(errUnknownNMEASentence, sentence, typ)
}
// 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)
m, _ := strconv.ParseInt(val[2:4], 10, 8)
s, _ := strconv.ParseInt(val[4:6], 10, 8)
ms, _ := strconv.ParseInt(val[7:10], 10, 16)
ms := int64(0)
if len(val) > 6 {
ms, _ = strconv.ParseInt(val[7:], 10, 16)
}
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
return t
@@ -120,11 +135,11 @@ func findAltitude(val string) int32 {
// $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh
func findLatitude(val, hemi string) float32 {
if len(val) > 8 {
var dd = val[0:2]
var mm = val[2:]
var d, _ = strconv.ParseFloat(dd, 32)
var m, _ = strconv.ParseFloat(mm, 32)
var v = float32(d + (m / 60))
dd := val[0:2]
mm := val[2:]
d, _ := strconv.ParseFloat(dd, 32)
m, _ := strconv.ParseFloat(mm, 32)
v := float32(d + (m / 60))
if hemi == "S" {
v *= -1
}
@@ -133,7 +148,7 @@ func findLatitude(val, hemi string) float32 {
return 0.0
}
// findLatitude returns the longitude from an NMEA sentence:
// findLongitude returns the longitude from an NMEA sentence:
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
func findLongitude(val, hemi string) float32 {
if len(val) > 8 {
+97
View File
@@ -0,0 +1,97 @@
package gps
import (
"testing"
"time"
qt "github.com/frankban/quicktest"
)
func TestParseUnknownSentence(t *testing.T) {
c := qt.New(t)
p := NewParser()
val := "$GPGSV,3,1,09,07,14,317,22,08,31,284,25,10,32,133,39,16,85,232,29*7F"
_, err := p.Parse(val)
c.Assert(err.Error(), qt.Contains, "unsupported NMEA sentence type")
}
func TestParseGGA(t *testing.T) {
c := qt.New(t)
p := NewParser()
val := "$GPGGA,115739.00,4158.8441367,N,09147.4416929,"
fix, err := p.Parse(val)
if err != errInvalidGGASentence {
t.Error("should have errInvalidGGASentence error")
}
val = "$GPGGA,115739.00,4158.8441367,N,09147.4416929,W,4,13,0.9,255.747,M,-32.00,M,01,0000*6E"
fix, err = p.Parse(val)
if err != nil {
t.Error("should have parsed")
}
c.Assert(fix.Latitude, qt.Equals, float32(41.980735778808594))
c.Assert(fix.Longitude, qt.Equals, float32(-91.79069519042969))
c.Assert(fix.Altitude, qt.Equals, int32(255))
}
func TestParseGLL(t *testing.T) {
c := qt.New(t)
p := NewParser()
val := "$GPGLL,3953.88008971,N,10506.7531891"
_, err := p.Parse(val)
if err != errInvalidGLLSentence {
t.Error("should have errInvalidGLLSentence error")
}
val = "$GPGLL,5109.0262317,N,11401.8407304,W,202725.00,A,D*79"
fix, err := p.Parse(val)
if err != nil {
t.Error("should have parsed")
}
c.Assert(fix.Latitude, qt.Equals, float32(51.15043640136719))
c.Assert(fix.Longitude, qt.Equals, float32(-114.03067779541016))
}
func TestParseRMC(t *testing.T) {
c := qt.New(t)
p := NewParser()
val := "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,"
_, err := p.Parse(val)
if err != errInvalidRMCSentence {
t.Error("should have errInvalidRMCSentence error")
}
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,130522,0.0,E,D*2B"
fix, err := p.Parse(val)
if err != nil {
t.Error("should have parsed")
}
c.Assert(fix.Latitude, qt.Equals, float32(51.15043640136719))
c.Assert(fix.Longitude, qt.Equals, float32(-114.03067779541016))
}
func TestTime(t *testing.T) {
c := qt.New(t)
val := ""
tm := findTime(val)
c.Assert(tm, qt.Equals, time.Time{})
val = "225446"
tm = findTime(val)
c.Assert(tm, qt.Equals, time.Date(0, 0, 0, 22, 54, 46, 0, time.UTC))
val = "124326.02752"
tm = findTime(val)
c.Assert(tm, qt.Equals, time.Date(0, 0, 0, 12, 43, 26, 2752, time.UTC))
}
+7
View File
@@ -76,3 +76,10 @@ const (
SyncPublic = iota
SyncPrivate
)
const (
MHz_868_1 = 868100000
MHz_868_5 = 868500000
MHz_916_8 = 916800000
MHz_923_3 = 923300000
)
+138
View File
@@ -0,0 +1,138 @@
package lorawan
import (
"errors"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/lora/lorawan/region"
)
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")
ErrUndefinedRegionSettings = errors.New("undefined Regionnal Settings ")
)
const (
LORA_TX_TIMEOUT = 2000
LORA_RX_TIMEOUT = 10000
)
var (
ActiveRadio lora.Radio
Retries = 15
regionSettings region.RegionSettings
)
// UseRegionSettings sets current Lorawan Regional parameters
func UseRegionSettings(rs region.RegionSettings) {
regionSettings = rs
}
// UseRadio attaches Lora radio driver to Lorawan
func UseRadio(r lora.Radio) {
if ActiveRadio != nil {
panic("lorawan.ActiveRadio is already set")
}
ActiveRadio = r
}
// SetPublicNetwork defines Lora Sync Word according to network type (public/private)
func SetPublicNetwork(enabled bool) {
ActiveRadio.SetPublicNetwork(enabled)
}
// ApplyChannelConfig sets current Lora modulation according to current regional settings
func applyChannelConfig(ch *region.Channel) {
ActiveRadio.SetFrequency(ch.Frequency)
ActiveRadio.SetBandwidth(ch.Bandwidth)
ActiveRadio.SetCodingRate(ch.CodingRate)
ActiveRadio.SetSpreadingFactor(ch.SpreadingFactor)
ActiveRadio.SetPreambleLength(ch.PreambleLength)
ActiveRadio.SetTxPower(ch.TxPowerDBm)
// Lorawan defaults to explicit headers
ActiveRadio.SetHeaderType(lora.HeaderExplicit)
ActiveRadio.SetCrc(true)
}
// Join tries to connect Lorawan Gateway
func Join(otaa *Otaa, session *Session) error {
var resp []uint8
if ActiveRadio == nil {
return ErrNoRadioAttached
}
if regionSettings == nil {
return ErrUndefinedRegionSettings
}
otaa.Init()
// Send join packet
payload, err := otaa.GenerateJoinRequest()
if err != nil {
return err
}
// Prepare radio for Join Tx
applyChannelConfig(regionSettings.JoinRequestChannel())
ActiveRadio.SetIqMode(lora.IQStandard)
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
if err != nil {
return err
}
// Wait for JoinAccept
applyChannelConfig(regionSettings.JoinAcceptChannel())
ActiveRadio.SetIqMode(lora.IQInverted)
resp, err = ActiveRadio.Rx(LORA_RX_TIMEOUT)
if err != nil {
return err
}
if resp == nil {
return ErrNoJoinAcceptReceived
}
err = otaa.DecodeJoinAccept(resp, session)
if err != nil {
return err
}
return nil
}
// SendUplink sends Lorawan Uplink message
func SendUplink(data []uint8, session *Session) error {
if regionSettings == nil {
return ErrUndefinedRegionSettings
}
payload, err := session.GenMessage(0, []byte(data))
if err != nil {
return err
}
applyChannelConfig(regionSettings.UplinkChannel())
ActiveRadio.SetIqMode(lora.IQStandard)
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
if err != nil {
return err
}
return nil
}
func ListenDownlink() error {
return nil
}
+20
View File
@@ -0,0 +1,20 @@
package lorawan
import "crypto/rand"
// reverseBytes reverses order of a given byte slice
func reverseBytes(s []byte) []byte {
result := make([]byte, len(s))
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = s[j], s[i]
}
return result
}
// GetRand16 returns 2 random bytes
func GetRand16() ([2]uint8, error) {
var randomBytes [2]byte
_, err := rand.Read(randomBytes[:])
return randomBytes, err
}
+253
View File
@@ -0,0 +1,253 @@
package lorawan
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"hash"
"unsafe"
)
type cmacHash struct {
ciph cipher.Block
k1 []byte
k2 []byte
data []byte
x []byte
}
const (
Size = aes.BlockSize
blockSize = Size
)
var (
subkeyZero []byte
subkeyRb []byte
)
func init() {
subkeyZero = bytes.Repeat([]byte{0x00}, blockSize)
subkeyRb = append(bytes.Repeat([]byte{0x00}, blockSize-1), 0x87)
}
// New returns an AES-CMAC hash using the supplied key. The key must be 16, 24,
// or 32 bytes long.
func NewCmac(key []byte) (hash.Hash, error) {
// Create a cipher.
ciph, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Set up the hash object.
h := &cmacHash{ciph: ciph}
h.k1, h.k2 = generateSubkeys(ciph)
h.Reset()
return h, nil
}
func Xor(dst []byte, a []byte, b []byte) error {
if len(dst) != len(a) || len(a) != len(b) {
panic("crypto/Xor: bad length")
}
for i, _ := range a {
dst[i] = a[i] ^ b[i]
}
return nil
}
func (h *cmacHash) Reset() {
h.data = h.data[:0]
h.x = make([]byte, blockSize)
}
func (h *cmacHash) BlockSize() int {
return h.ciph.BlockSize()
}
func (h *cmacHash) Size() int {
return h.ciph.BlockSize()
}
func (h *cmacHash) Sum(b []byte) []byte {
dataLen := len(h.data)
// We should have at most one block left.
if dataLen > blockSize {
panic("cmacHash err1")
}
// Calculate M_last.
mLast := make([]byte, blockSize)
if dataLen == blockSize {
Xor(mLast, h.data, h.k1)
} else {
// TODO(jacobsa): Accept a destination buffer in common.PadBlock and
// simplify this code.
Xor(mLast, PadBlock(h.data), h.k2)
}
y := make([]byte, blockSize)
Xor(y, mLast, h.x)
result := make([]byte, blockSize)
h.ciph.Encrypt(result, y)
b = append(b, result...)
return b
}
func (h *cmacHash) Write(p []byte) (n int, err error) {
n = len(p)
// First step: consume enough data to expand h.data to a full block, if
// possible.
{
toConsume := blockSize - len(h.data)
if toConsume > len(p) {
toConsume = len(p)
}
h.data = append(h.data, p[:toConsume]...)
p = p[toConsume:]
}
// If there's no data left in p, it means h.data might not be a full block.
// Even if it is, we're not sure it's the final block, which we must treat
// specially. So we must stop here.
if len(p) == 0 {
return
}
// h.data is a full block and is not the last; process it.
h.writeBlocks(h.data)
h.data = h.data[:0]
// Consume any further full blocks in p that we're sure aren't the last. Note
// that we're sure that len(p) is greater than zero here.
blocksToProcess := (len(p) - 1) / blockSize
bytesToProcess := blocksToProcess * blockSize
h.writeBlocks(p[:bytesToProcess])
p = p[bytesToProcess:]
// Store the rest for later.
h.data = append(h.data, p...)
return
}
func (h *cmacHash) writeBlocks(p []byte) {
y := make([]byte, blockSize)
for off := 0; off < len(p); off += blockSize {
block := p[off : off+blockSize]
xorBlock(
unsafe.Pointer(&y[0]),
unsafe.Pointer(&h.x[0]),
unsafe.Pointer(&block[0]))
h.ciph.Encrypt(h.x, y)
}
return
}
func xorBlock(
dstPtr unsafe.Pointer,
aPtr unsafe.Pointer,
bPtr unsafe.Pointer) {
// Check assumptions. (These are compile-time constants, so this should
// compile out.)
const wordSize = unsafe.Sizeof(uintptr(0))
if blockSize != 4*wordSize {
panic("xorBlock err1")
}
// Convert.
a := (*[4]uintptr)(aPtr)
b := (*[4]uintptr)(bPtr)
dst := (*[4]uintptr)(dstPtr)
// Compute.
dst[0] = a[0] ^ b[0]
dst[1] = a[1] ^ b[1]
dst[2] = a[2] ^ b[2]
dst[3] = a[3] ^ b[3]
}
func PadBlock(block []byte) []byte {
blockLen := len(block)
if blockLen >= aes.BlockSize {
panic("PadBlock input must be less than 16 bytes.")
}
result := make([]byte, aes.BlockSize)
copy(result, block)
result[blockLen] = 0x80
return result
}
// Given the supplied cipher, whose block size must be 16 bytes, return two
// subkeys that can be used in MAC generation. See section 5.3 of NIST SP
// 800-38B. Note that the other NIST-approved block size of 8 bytes is not
// supported by this function.
func generateSubkeys(ciph cipher.Block) (k1 []byte, k2 []byte) {
if ciph.BlockSize() != blockSize {
panic("generateSubkeys requires a cipher with a block size of 16 bytes.")
}
// Step 1
l := make([]byte, blockSize)
ciph.Encrypt(l, subkeyZero)
// Step 2: Derive the first subkey.
if Msb(l) == 0 {
// TODO(jacobsa): Accept a destination buffer in ShiftLeft and then hoist
// the allocation in the else branch below.
k1 = ShiftLeft(l)
} else {
k1 = make([]byte, blockSize)
Xor(k1, ShiftLeft(l), subkeyRb)
}
// Step 3: Derive the second subkey.
if Msb(k1) == 0 {
k2 = ShiftLeft(k1)
} else {
k2 = make([]byte, blockSize)
Xor(k2, ShiftLeft(k1), subkeyRb)
}
return
}
func ShiftLeft(b []byte) []byte {
l := len(b)
if l == 0 {
panic("shiftLeft requires a non-empty buffer.")
}
output := make([]byte, l)
overflow := byte(0)
for i := int(l - 1); i >= 0; i-- {
output[i] = b[i] << 1
output[i] |= overflow
overflow = (b[i] & 0x80) >> 7
}
return output
}
// Msb returns the most significant bit of the supplied data (which must be
// non-empty). This is the MSB(L) function of RFC 4493.
func Msb(buf []byte) uint8 {
if len(buf) == 0 {
panic("msb requires non-empty buffer.")
}
return buf[0] >> 7
}
+38
View File
@@ -0,0 +1,38 @@
package lorawan
import (
"encoding/binary"
)
// genPayloadMIC computes MIC given the payload and the key
func genPayloadMIC(payload []uint8, key [16]uint8) [4]uint8 {
var mic [4]uint8
hash, _ := NewCmac(key[:])
hash.Write(payload)
hb := hash.Sum([]byte{})
copy(mic[:], hb[0:4])
return mic
}
func calcMessageMIC(payload []uint8, key [16]uint8, dir uint8, addr []byte, fCnt uint32, lenMessage uint8) [4]uint8 {
var b0 []byte
b0 = append(b0, 0x49, 0x00, 0x00, 0x00, 0x00)
b0 = append(b0, dir)
b0 = append(b0, addr[:]...)
var b [4]byte
binary.LittleEndian.PutUint32(b[:], fCnt)
b0 = append(b0, b[:]...)
b0 = append(b0, 0x00)
b0 = append(b0, lenMessage)
var full []byte
full = append(full, b0...)
full = append(full, payload...)
var mic [4]uint8
hash, _ := NewCmac(key[:])
hash.Write(full)
hb := hash.Sum([]byte{})
copy(mic[:], hb[0:4])
return mic
}
+185
View File
@@ -0,0 +1,185 @@
package lorawan
import (
"bytes"
"crypto/aes"
"encoding/hex"
)
// Otaa is used to store Over The Air Activation data of a LoRaWAN session
type Otaa struct {
DevEUI [8]uint8
AppEUI [8]uint8
AppKey [16]uint8
devNonce [2]uint8
appNonce [3]uint8
NetID [3]uint8
buf []uint8
}
// Initialize DevNonce
func (o *Otaa) Init() {
o.buf = make([]uint8, 0)
o.generateDevNonce()
}
func (o *Otaa) generateDevNonce() {
// TODO: handle error
rnd, _ := GetRand16()
o.devNonce[0] = rnd[0]
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
func (o *Otaa) Set(appEUI []uint8, devEUI []uint8, appKey []uint8) {
o.SetAppEUI(appEUI)
o.SetDevEUI(devEUI)
o.SetAppKey(appKey)
}
// SetAppEUI configures the Otaa AppEUI
func (o *Otaa) SetAppEUI(appEUI []uint8) error {
if len(appEUI) != 8 {
return ErrInvalidEuiLength
}
copy(o.AppEUI[:], appEUI)
return nil
}
func (o *Otaa) GetAppEUI() string {
return hex.EncodeToString(o.AppEUI[:])
}
// SetDevEUI configures the Otaa DevEUI
func (o *Otaa) SetDevEUI(devEUI []uint8) error {
if len(devEUI) != 8 {
return ErrInvalidEuiLength
}
copy(o.DevEUI[:], devEUI)
return nil
}
func (o *Otaa) GetDevEUI() string {
return hex.EncodeToString(o.DevEUI[:])
}
// SetAppKey configures the Otaa AppKey
func (o *Otaa) SetAppKey(appKey []uint8) error {
if len(appKey) != 16 {
return ErrInvalidAppKeyLength
}
copy(o.AppKey[:], appKey)
return nil
}
func (o *Otaa) GetAppKey() string {
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
func (o *Otaa) GenerateJoinRequest() ([]uint8, error) {
o.incrementDevNonce()
// TODO: Add checks
o.buf = o.buf[:0]
o.buf = append(o.buf, 0x00)
o.buf = append(o.buf, reverseBytes(o.AppEUI[:])...)
o.buf = append(o.buf, reverseBytes(o.DevEUI[:])...)
o.buf = append(o.buf, o.devNonce[:]...)
mic := genPayloadMIC(o.buf, o.AppKey)
o.buf = append(o.buf, mic[:]...)
return o.buf, nil
}
// DecodeJoinAccept Decodes a Lora Join Accept packet
func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error {
if len(phyPload) < 12 {
return ErrInvalidPacketLength
}
data := phyPload[1:] // Remove trailing 0x20
// Prepare AES Cipher
block, err := aes.NewCipher(o.AppKey[:])
if err != nil {
return err
}
buf := make([]byte, len(data))
for k := 0; k < len(data)/aes.BlockSize; k++ {
block.Encrypt(buf[k*aes.BlockSize:], data[k*aes.BlockSize:])
}
copy(o.appNonce[:], buf[0:3])
copy(o.NetID[:], buf[3:6])
copy(s.DevAddr[:], buf[6:10])
s.DLSettings = buf[10]
s.RXDelay = buf[11]
if len(buf) > 16 {
copy(s.CFList[:], buf[12:28])
}
rxMic := buf[len(buf)-4:]
dataMic := []byte{}
dataMic = append(dataMic, phyPload[0])
dataMic = append(dataMic, o.appNonce[:]...)
dataMic = append(dataMic, o.NetID[:]...)
dataMic = append(dataMic, s.DevAddr[:]...)
dataMic = append(dataMic, s.DLSettings)
dataMic = append(dataMic, s.RXDelay)
dataMic = append(dataMic, s.CFList[:]...)
computedMic := genPayloadMIC(dataMic[:], o.AppKey)
if !bytes.Equal(computedMic[:], rxMic[:]) {
return ErrInvalidMic
}
// Generate NwkSKey
// NwkSKey = aes128_encrypt(AppKey, 0x01|AppNonce|NetID|DevNonce|pad16)
sKey := []byte{}
sKey = append(sKey, 0x01)
sKey = append(sKey, o.appNonce[:]...)
sKey = append(sKey, o.NetID[:]...)
sKey = append(sKey, o.devNonce[:]...)
for i := 0; i < 7; i++ {
sKey = append(sKey, 0x00) // PAD to 16
}
block.Encrypt(buf, sKey)
copy(s.NwkSKey[:], buf[0:16])
// Generate AppSKey
// AppSKey = aes128_encrypt(AppKey, 0x02|AppNonce|NetID|DevNonce|pad16)
sKey[0] = 0x02
block.Encrypt(buf, sKey)
copy(s.AppSKey[:], buf[0:16])
// Reset counters
s.FCntDown = 0
s.FCntUp = 0
return nil
}
+49
View File
@@ -0,0 +1,49 @@
package region
import "tinygo.org/x/drivers/lora"
const (
AU915_DEFAULT_PREAMBLE_LEN = 8
AU915_DEFAULT_TX_POWER_DBM = 20
)
type RegionSettingsAU915 struct {
joinRequestChannel *Channel
joinAcceptChannel *Channel
uplinkChannel *Channel
}
func AU915() *RegionSettingsAU915 {
return &RegionSettingsAU915{
joinRequestChannel: &Channel{lora.MHz_916_8,
lora.Bandwidth_125_0,
lora.SpreadingFactor9,
lora.CodingRate4_5,
AU915_DEFAULT_PREAMBLE_LEN,
AU915_DEFAULT_TX_POWER_DBM},
joinAcceptChannel: &Channel{lora.MHz_923_3,
lora.Bandwidth_500_0,
lora.SpreadingFactor9,
lora.CodingRate4_5,
AU915_DEFAULT_PREAMBLE_LEN,
AU915_DEFAULT_TX_POWER_DBM},
uplinkChannel: &Channel{lora.MHz_916_8,
lora.Bandwidth_125_0,
lora.SpreadingFactor9,
lora.CodingRate4_5,
AU915_DEFAULT_PREAMBLE_LEN,
AU915_DEFAULT_TX_POWER_DBM},
}
}
func (r *RegionSettingsAU915) JoinRequestChannel() *Channel {
return r.joinRequestChannel
}
func (r *RegionSettingsAU915) JoinAcceptChannel() *Channel {
return r.joinAcceptChannel
}
func (r *RegionSettingsAU915) UplinkChannel() *Channel {
return r.uplinkChannel
}
+49
View File
@@ -0,0 +1,49 @@
package region
import "tinygo.org/x/drivers/lora"
const (
EU868_DEFAULT_PREAMBLE_LEN = 8
EU868_DEFAULT_TX_POWER_DBM = 20
)
type RegionSettingsEU868 struct {
joinRequestChannel *Channel
joinAcceptChannel *Channel
uplinkChannel *Channel
}
func EU868() *RegionSettingsEU868 {
return &RegionSettingsEU868{
joinRequestChannel: &Channel{lora.MHz_868_1,
lora.Bandwidth_125_0,
lora.SpreadingFactor9,
lora.CodingRate4_7,
EU868_DEFAULT_PREAMBLE_LEN,
EU868_DEFAULT_TX_POWER_DBM},
joinAcceptChannel: &Channel{lora.MHz_868_1,
lora.Bandwidth_125_0,
lora.SpreadingFactor9,
lora.CodingRate4_7,
EU868_DEFAULT_PREAMBLE_LEN,
EU868_DEFAULT_TX_POWER_DBM},
uplinkChannel: &Channel{lora.MHz_868_1,
lora.Bandwidth_125_0,
lora.SpreadingFactor9,
lora.CodingRate4_7,
EU868_DEFAULT_PREAMBLE_LEN,
EU868_DEFAULT_TX_POWER_DBM},
}
}
func (r *RegionSettingsEU868) JoinRequestChannel() *Channel {
return r.joinRequestChannel
}
func (r *RegionSettingsEU868) JoinAcceptChannel() *Channel {
return r.joinAcceptChannel
}
func (r *RegionSettingsEU868) UplinkChannel() *Channel {
return r.uplinkChannel
}
+16
View File
@@ -0,0 +1,16 @@
package region
type Channel struct {
Frequency uint32
Bandwidth uint8
SpreadingFactor uint8
CodingRate uint8
PreambleLength uint16
TxPowerDBm int8
}
type RegionSettings interface {
JoinRequestChannel() *Channel
JoinAcceptChannel() *Channel
UplinkChannel() *Channel
}
+137
View File
@@ -0,0 +1,137 @@
package lorawan
import (
"crypto/aes"
"encoding/binary"
"encoding/hex"
"math"
)
// Session is used to store session data of a LoRaWAN session
type Session struct {
NwkSKey [16]uint8
AppSKey [16]uint8
DevAddr [4]uint8
FCntDown uint32
FCntUp uint32
CFList [16]uint8
RXDelay uint8
DLSettings uint8
}
// SetDevAddr configures the Session DevAddr
func (s *Session) SetDevAddr(devAddr []uint8) error {
if len(devAddr) != 4 {
return ErrInvalidDevAddrLength
}
copy(s.DevAddr[:], devAddr)
return nil
}
// GetDevAddr returns the Session DevAddr
func (s *Session) GetDevAddr() string {
return hex.EncodeToString(s.DevAddr[:])
}
// 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) {
var buf []uint8
buf = append(buf, 0b01000000) // FHDR Unconfirmed up
buf = append(buf, s.DevAddr[:]...)
// FCtl : No ADR, No RFU, No ACK, No FPending, No FOpt
buf = append(buf, 0x00)
// FCnt Up
buf = append(buf, uint8(s.FCntUp&0xFF), uint8((s.FCntUp>>8)&0xFF))
// FPort=1
buf = append(buf, 0x01)
fCnt := uint32(0)
if dir == 0 {
fCnt = s.FCntUp
s.FCntUp++
} else {
fCnt = s.FCntDown
}
data, err := s.genFRMPayload(dir, fCnt, payload, false)
if err != nil {
return nil, err
}
buf = append(buf, data[:]...)
mic := calcMessageMIC(buf, s.NwkSKey, dir, s.DevAddr[:], fCnt, uint8(len(buf)))
buf = append(buf, mic[:]...)
return buf, nil
}
func (s *Session) genFRMPayload(dir uint8, fCnt uint32, payload []byte, isFOpts bool) ([]byte, error) {
k := len(payload) / aes.BlockSize
if len(payload)%aes.BlockSize != 0 {
k++
}
if k > math.MaxUint8 {
return nil, ErrFrmPayloadTooLarge
}
encrypted := make([]byte, 0, k*16)
cipher, err := aes.NewCipher(s.AppSKey[:])
if err != nil {
panic(err)
}
var a [aes.BlockSize]byte
a[0] = 0x01
a[5] = dir
copy(a[6:10], s.DevAddr[:])
binary.LittleEndian.PutUint32(a[10:14], fCnt)
var ss [aes.BlockSize]byte
var b [aes.BlockSize]byte
for i := uint8(0); i < uint8(k); i++ {
copy(b[:], payload[i*aes.BlockSize:])
if !isFOpts {
a[15] = i + 1
}
cipher.Encrypt(ss[:], a[:])
for j := 0; j < aes.BlockSize; j++ {
b[j] = b[j] ^ ss[j]
}
encrypted = append(encrypted, b[:]...)
}
return encrypted[:len(payload)], nil
}
+8 -11
View File
@@ -1,15 +1,6 @@
package main
package lora
import (
"errors"
)
var (
errRadioNotFound = errors.New("radio not found")
errRxTimeout = errors.New("radio RX timeout")
)
type LoraRadio interface {
type Radio interface {
Reset()
Tx(pkt []uint8, timeoutMs uint32) error
Rx(timeoutMs uint32) ([]uint8, error)
@@ -19,4 +10,10 @@ type LoraRadio interface {
SetBandwidth(bw uint8)
SetCrc(enable bool)
SetSpreadingFactor(sf uint8)
SetPreambleLength(plen uint16)
SetTxPower(txpow int8)
SetSyncWord(syncWord uint16)
SetPublicNetwork(enable bool)
SetHeaderType(headerType uint8)
LoraConfig(cnf Config)
}
+16 -1
View File
@@ -32,7 +32,7 @@ func (d Device) Connected() bool {
// Configure sets up the device for communication.
func (d Device) Configure() error {
return d.bus.WriteRegister(uint8(d.Address), PWR_MGMT_1, []uint8{0})
return d.SetClockSource(CLOCK_INTERNAL)
}
// ReadAcceleration reads the current acceleration from the device and returns
@@ -78,3 +78,18 @@ func (d Device) ReadRotation() (x int32, y int32, z int32) {
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 2048 * 1000
return
}
// SetClockSource allows the user to configure the clock source.
func (d Device) SetClockSource(source uint8) error {
return d.bus.WriteRegister(uint8(d.Address), PWR_MGMT_1, []uint8{source})
}
// SetFullScaleGyroRange allows the user to configure the scale range for the gyroscope.
func (d Device) SetFullScaleGyroRange(rng uint8) error {
return d.bus.WriteRegister(uint8(d.Address), GYRO_CONFIG, []uint8{rng})
}
// SetFullScaleAccelRange allows the user to configure the scale range for the accelerometer.
func (d Device) SetFullScaleAccelRange(rng uint8) error {
return d.bus.WriteRegister(uint8(d.Address), ACCEL_CONFIG, []uint8{rng})
}
+23
View File
@@ -98,6 +98,29 @@ const (
I2C_PER3_DO = 0x66
I2C_MST_DELAY_CT = 0x67
// Clock settings
CLOCK_INTERNAL = 0x00
CLOCK_PLL_XGYRO = 0x01
CLOCK_PLL_YGYRO = 0x02
CLOCK_PLL_ZGYRO = 0x03
CLOCK_PLL_EXTERNAL_32_768_KZ = 0x04
CLOCK_PLL_EXTERNAL_19_2_MHZ = 0x05
CLOCK_RESERVED = 0x06
CLOCK_STOP = 0x07
// Accelerometer settings
AFS_RANGE_2G = 0x00
AFS_RANGE_4G = 0x01
AFS_RANGE_8G = 0x02
AFS_RANGE_16G = 0x03
// Gyroscope settings
FS_RANGE_250 = 0x00
FS_RANGE_500 = 0x01
FS_RANGE_1000 = 0x02
FS_RANGE_2000 = 0x03
// other registers
SIGNAL_PATH_RES = 0x68 // Signal path reset
USER_CTRL = 0x6A // User control
PWR_MGMT_1 = 0x6B // Power Management 1
+40
View File
@@ -2,6 +2,8 @@ package http
import (
"io"
"net/url"
"strings"
"time"
)
@@ -211,3 +213,41 @@ func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response,
req.Header.Set("Content-Type", contentType)
return c.Do(req)
}
// PostForm issues a POST to the specified URL, with data's keys and
// values URL-encoded as the request body.
//
// The Content-Type header is set to application/x-www-form-urlencoded.
// To set other headers, use NewRequest and DefaultClient.Do.
//
// When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it.
//
// PostForm is a wrapper around DefaultClient.PostForm.
//
// See the Client.Do method documentation for details on how redirects
// are handled.
//
// To make a request with a specified context.Context, use NewRequestWithContext
// and DefaultClient.Do.
func PostForm(url string, data url.Values) (resp *Response, err error) {
return DefaultClient.PostForm(url, data)
}
// PostForm issues a POST to the specified URL,
// with data's keys and values URL-encoded as the request body.
//
// The Content-Type header is set to application/x-www-form-urlencoded.
// To set other headers, use NewRequest and Client.Do.
//
// When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it.
//
// See the Client.Do method documentation for details on how redirects
// are handled.
//
// To make a request with a specified context.Context, use NewRequestWithContext
// and Client.Do.
func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) {
return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
+4 -1
View File
@@ -357,7 +357,10 @@ func ParseIP(s string) IP {
// String returns the string form of the IP address ip.
func (ip IP) String() string {
return string(ip)
if len(ip) < 4 {
return ""
}
return strconv.Itoa(int(ip[0])) + "." + strconv.Itoa(int(ip[1])) + "." + strconv.Itoa(int(ip[2])) + "." + strconv.Itoa(int(ip[3]))
}
// Conn is a generic stream-oriented network connection.
+14
View File
@@ -0,0 +1,14 @@
package net
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestIPAddressString(t *testing.T) {
c := qt.New(t)
ipaddr := ParseIP("127.0.0.1")
c.Assert(ipaddr.String(), qt.Equals, "127.0.0.1")
}
+27
View File
@@ -0,0 +1,27 @@
# SX126x LoRa Radio
The Semtech SX126x family of sub-Ghz radio transceivers come in a number of different formats.
SX126x radios have a wide continuous frequency coverage from 150 MHz to 960 MHz.
SX1261 can transmit up to +15 dBm and the SX1262 can transmit up to +22 dBm
Some development boards are specific to a particular frequency range, so for example you need a different variation of that board for EU868 vs. for AU915.
## LAMBDA62 RF module
Cost effective radio module featuring the Semtech SX1262.
## STM32 STM32WLE5JC
ST Micro STM32WLE5/E4xx is 32-bit ARM Cortex M4 processor with Semtech SX126x processor on a single die.
https://www.st.com/en/microcontrollers-microprocessors/stm32wle5jc.html
Several boards have been made using this processor.
### Wio-E5 mini
https://www.seeedstudio.com/LoRa-E5-mini-STM32WLE5JC-p-4869.html
Wio-E5 mini is a compacted-sized dev board suitable for the rapid testing and building of small-size LoRa device and application prototyping. Wio-E5 mini is embedded with and leads out full GPIOs of Wio-E5 STM32WLE5JC
+13
View File
@@ -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
}
+56
View File
@@ -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
}
+47
View File
@@ -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
}
+91
View File
@@ -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 (
"device/stm32"
"errors"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/lora"
"runtime/interrupt"
)
// New creates a new SX126x connection.
func New(spi drivers.SPI) *Device {
c := make(chan lora.RadioEvent, 10)
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
// STM32RadioControl helps implement the RadioController interface
type STM32RadioControl struct {
irqHandler func()
}
// SpiSetNss Sets the NSS line
func (d *Device) SpiSetNss(state bool) {
// SetNss sets the NSS line aka chip select for SPI.
func (rc *STM32RadioControl) SetNss(state bool) error {
if state {
stm32.PWR.SUBGHZSPICR.SetBits(stm32.PWR_SUBGHZSPICR_NSS)
} else {
stm32.PWR.SUBGHZSPICR.ClearBits(stm32.PWR_SUBGHZSPICR_NSS)
}
return nil
}
// WaitBusy sleep until all busy flags clears
func (d *Device) WaitBusy() error {
// WaitWhileBusy wait until the radio is no longer busy
func (rc *STM32RadioControl) WaitWhileBusy() error {
count := 100
var rfbusyms, rfbusys bool
for count > 0 {
@@ -49,12 +37,11 @@ func (d *Device) WaitBusy() error {
}
count--
}
return errors.New("WaitBusy Timeout")
return errWaitWhileBusyTimeout
}
// SubGhzInit() configures internal SX1262's SPI bus.
func (d *Device) SubGhzInit() {
// init() configures whatever needed for sx126x radio control
func init() {
// Enable APB3 Periph clock and delay
stm32.RCC.APB3ENR.SetBits(stm32.RCC_APB3ENR_SUBGHZSPIEN)
_ = 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.CR2.Set(stm32.SPI_CR2_FRXTH | (0b111 << 8))
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
/*
Nucleo WL55JC1
RFSwitch
Nucleo WL55JC1
RFSwitch
+-----------+---------+------------+------------+
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
@@ -13,42 +13,48 @@
| RX | HIGH | LOW | HIGH |
+-----------+----------+-----------+------------+
*/
package main
package sx126x
import (
"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.PC5.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 {
case sx126x.RFSWITCH_TX_HP:
case RFSWITCH_TX_HP:
machine.PC4.Set(false)
machine.PC5.Set(true)
machine.PC3.Set(true)
case sx126x.RFSWITCH_TX_LP:
case RFSWITCH_TX_LP:
machine.PC4.Set(true)
machine.PC5.Set(true)
machine.PC3.Set(true)
case sx126x.RFSWITCH_RX:
case RFSWITCH_RX:
machine.PC4.Set(true)
machine.PC5.Set(false)
machine.PC3.Set(true)
}
return nil
return nil
}
+74 -42
View File
@@ -13,13 +13,13 @@ import (
"tinygo.org/x/drivers/lora"
)
// SX126X radio transceiver RF_IN and RF_OUT may be connected
// to RF Switch. This interface allows the creation of struct
// that can drive the RF Switch (Used in Lora RX and Lora Tx)
type RFSwitch interface {
InitRFSwitch()
SetRfSwitchMode(mode int) error
}
var (
errWaitWhileBusyTimeout = errors.New("WaitWhileBusy Timeout")
errLowPowerTxNotSupported = errors.New("RFSWITCH_TX_LP not supported")
errRadioNotFound = errors.New("LoRa radio not found")
errUnexpectedRxRadioEvent = errors.New("Unexpected Radio Event during RX")
errUnexpectedTxRadioEvent = errors.New("Unexpected Radio Event during TX")
)
const (
DEVICE_TYPE_SX1261 = iota
@@ -38,18 +38,26 @@ const (
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 {
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
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
deviceType int // sx1261,sx1262,sx1268 (defaults sx1261)
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 (
SX126X_RTC_FREQ_IN_HZ uint32 = 64000
)
@@ -79,10 +87,15 @@ func (d *Device) SetDeviceType(devType int) {
d.deviceType = devType
}
// SetRfSwitch let you define a custom RF Switch driver if needed
func (d *Device) SetRfSwitch(rfswitch RFSwitch) {
d.rfswitch = rfswitch
d.rfswitch.InitRFSwitch()
// SetRadioControl let you define the RadioController
func (d *Device) SetRadioController(rc RadioController) error {
d.controller = rc
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)
func (d *Device) SetTxContinuousWave() {
if d.rfswitch != nil {
d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP)
if d.controller != nil {
d.controller.SetRfSwitchMode(RFSWITCH_TX_HP)
}
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
// If you don't init properly all the settings, it'll fail
func (d *Device) SetTxContinuousPreamble() {
if d.rfswitch != nil {
d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP)
if d.controller != nil {
d.controller.SetRfSwitchMode(RFSWITCH_TX_HP)
}
d.ExecSetCommand(SX126X_CMD_SET_TX_INFINITE_PREAMBLE, []uint8{})
}
@@ -235,25 +248,25 @@ func (d *Device) SetRxTxFallbackMode(fallbackMode uint8) {
// ReadRegister reads register value
func (d *Device) ReadRegister(addr, size uint16) ([]uint8, error) {
d.CheckDeviceReady()
d.SpiSetNss(false)
d.controller.SetNss(false)
// Send command
cmd := []uint8{SX126X_CMD_READ_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF), 0x00}
d.spi.Tx(cmd, nil)
ret := d.spiBuffer[0:size]
d.spi.Tx(nil, ret)
d.SpiSetNss(true)
d.WaitBusy()
d.controller.SetNss(true)
d.controller.WaitWhileBusy()
return ret, nil
}
// WriteRegister writes value to register
func (d *Device) WriteRegister(addr uint16, data []uint8) {
d.CheckDeviceReady()
d.SpiSetNss(false)
d.controller.SetNss(false)
cmd := []uint8{SX126X_CMD_WRITE_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF)}
d.spi.Tx(append(cmd, data...), nil)
d.SpiSetNss(true)
d.WaitBusy()
d.controller.SetNss(true)
d.controller.WaitWhileBusy()
}
// 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
func (d *Device) CheckDeviceReady() error {
if d.deepSleep == true {
d.SpiSetNss(false)
d.controller.SetNss(false)
time.Sleep(time.Millisecond)
d.SpiSetNss(true)
d.controller.SetNss(true)
d.deepSleep = false
}
return d.WaitBusy()
return d.controller.WaitWhileBusy()
}
// ExecSetCommand send a command to configure the peripheral
@@ -482,24 +495,24 @@ func (d *Device) ExecSetCommand(cmd uint8, buf []uint8) {
} else {
d.deepSleep = false
}
d.SpiSetNss(false)
d.controller.SetNss(false)
// Send command and params
d.spi.Tx(append([]uint8{cmd}, buf...), nil)
d.SpiSetNss(true)
d.controller.SetNss(true)
if cmd != SX126X_CMD_SET_SLEEP {
d.WaitBusy()
d.controller.WaitWhileBusy()
}
}
// ExecGetCommand queries the peripheral the peripheral
func (d *Device) ExecGetCommand(cmd uint8, size uint8) []uint8 {
d.CheckDeviceReady()
d.SpiSetNss(false)
d.controller.SetNss(false)
// Send the command and flush first status byte (as not used)
d.spi.Tx([]uint8{cmd, 0x00}, nil)
d.spi.Tx(nil, d.spiBuffer[:size])
d.SpiSetNss(true)
d.WaitBusy()
d.controller.SetNss(true)
d.controller.WaitWhileBusy()
return d.spiBuffer[:size]
}
@@ -510,7 +523,7 @@ func (d *Device) ExecGetCommand(cmd uint8, size uint8) []uint8 {
// SetFrequency() Sets current Lora Frequency
// NB: Change will be applied at next RX / TX
func (d *Device) SetFrequency(freq uint32) {
d.loraConf.Freq = d.loraConf.Freq
d.loraConf.Freq = freq
}
// SetIqMode() defines the current IQ Mode (Standard/Inverted)
@@ -532,7 +545,7 @@ func (d *Device) SetCodingRate(cr uint8) {
// SetBandwidth() sets current Lora Bandwidth
// NB: Change will be applied at next RX / TX
func (d *Device) SetBandwidth(bw uint8) {
d.loraConf.Cr = bw
d.loraConf.Bw = bw
}
// SetCrc() sets current CRC mode (ON/OFF)
@@ -545,12 +558,30 @@ func (d *Device) SetCrc(enable bool) {
}
}
// SetSpreadingFactor setc surrent Lora Spreading Factor
// SetSpreadingFactor sets current Lora Spreading Factor
// NB: Change will be applied at next RX / TX
func (d *Device) SetSpreadingFactor(sf uint8) {
d.loraConf.Sf = sf
}
// SetPreambleLength sets current Lora Preamble Length
// NB: Change will be applied at next RX / TX
func (d *Device) SetPreambleLength(pl uint16) {
d.loraConf.Preamble = pl
}
// SetTxPowerDbm sets current Lora TX Power in DBm
// NB: Change will be applied at next RX / TX
func (d *Device) SetTxPower(txpow int8) {
d.loraConf.LoraTxPowerDBm = txpow
}
// SetHeaderType sets implicit or explicit header mode
// NB: Change will be applied at next RX / TX
func (d *Device) SetHeaderType(headerType uint8) {
d.loraConf.HeaderType = headerType
}
//
// Lora functions
//
@@ -582,8 +613,8 @@ func (d *Device) Tx(pkt []uint8, timeoutMs uint32) error {
return lora.ErrUndefinedLoraConf
}
if d.rfswitch != nil {
err := d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP)
if d.controller != nil {
err := d.controller.SetRfSwitchMode(RFSWITCH_TX_HP)
if err != nil {
return err
}
@@ -605,7 +636,7 @@ func (d *Device) Tx(pkt []uint8, timeoutMs uint32) error {
msg := <-d.GetRadioEventChan()
if msg.EventType != lora.RadioEventTxDone {
return errors.New("Unexpected Radio Event while TX")
return errUnexpectedTxRadioEvent
}
return nil
}
@@ -616,8 +647,8 @@ func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
return nil, lora.ErrUndefinedLoraConf
}
if d.rfswitch != nil {
err := d.rfswitch.SetRfSwitchMode(RFSWITCH_RX)
if d.controller != nil {
err := d.controller.SetRfSwitchMode(RFSWITCH_RX)
if err != nil {
return nil, err
}
@@ -627,6 +658,7 @@ func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
irqVal := uint16(SX126X_IRQ_RX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR)
d.SetStandby()
d.SetBufferBaseAddress(0, 0)
d.SetRfFrequency(d.loraConf.Freq)
d.SetModulationParams(d.loraConf.Sf, bandwidth(d.loraConf.Bw), d.loraConf.Cr, d.loraConf.Ldr)
d.SetPacketParam(d.loraConf.Preamble, d.loraConf.HeaderType, d.loraConf.Crc, 0xFF, d.loraConf.Iq)
d.SetDioIrqParams(irqVal, irqVal, SX126X_IRQ_NONE, SX126X_IRQ_NONE)
@@ -637,7 +669,7 @@ func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
if msg.EventType == lora.RadioEventTimeout {
return nil, nil
} else if msg.EventType != lora.RadioEventRxDone {
return nil, errors.New("Unexpected Radio Event while RX")
return nil, errUnexpectedRxRadioEvent
}
pLen, pStart := d.GetRxBufferStatus()
+24
View File
@@ -0,0 +1,24 @@
# SX127x LoRa Radio
This processor comes in several different packages.
## Adafruit LoRa Radio Featherwing
https://www.adafruit.com/product/3231
This is the LoRa 9x @ 900 MHz radio version, which can be used for either 868MHz or 915MHz transmission/reception - the exact radio frequency is determined when you load the software since it can be tuned around dynamically. They can easily go 2 Km line of sight using simple wire antennas, or up to 20Km with directional antennas and settings tweaks.
### Connecting
To use the LoRa Featherwing with a Pybadge/Gobadge, you need to connect some pads on the board itself. Solder some jumper wires as follows:
RST -> A
CS -> B
DIO1 -> C
IRQ -> D
You also need to connect an antenna. The easiest is to cut a small piece of wire to the correct length based on the desired frequency:
EU868 Mhz - 34.54 cm
You can also use a fancier solution such as a uFL SMA connector with matching antenna.
+10
View File
@@ -0,0 +1,10 @@
package sx127x
// SX127X radio transceiver has several pins that control NSS,
// and that are signalled when RX or TX operations are completed.
// This interface allows the creation of types that can control this
type RadioController interface {
Init() error
SetNss(state bool) error
SetupInterrupts(handler func()) error
}
+55
View File
@@ -0,0 +1,55 @@
package sx127x
import (
"machine"
)
// RadioControl for boards that are connected using normal pins.
type RadioControl struct {
nssPin, dio0Pin, dio1Pin machine.Pin
}
func NewRadioControl(nssPin, dio0Pin, dio1Pin machine.Pin) *RadioControl {
return &RadioControl{
nssPin: nssPin,
dio0Pin: dio0Pin,
dio1Pin: dio1Pin,
}
}
// SetNss sets the NSS line aka chip select for SPI.
func (rc *RadioControl) SetNss(state bool) error {
rc.nssPin.Set(state)
return nil
}
// Init() configures whatever needed for sx127x radio control
func (rc *RadioControl) Init() error {
rc.nssPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
rc.dio0Pin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
rc.dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
return nil
}
// add interrupt handlers for Radio IRQs for pins
func (rc *RadioControl) SetupInterrupts(handler func()) error {
irqHandler = handler
// Setup DIO0 interrupt Handling
if err := rc.dio0Pin.SetInterrupt(machine.PinRising, handleInterrupt); err != nil {
return err
}
// Setup DIO1 interrupt Handling
if err := rc.dio1Pin.SetInterrupt(machine.PinRising, handleInterrupt); err != nil {
return err
}
return nil
}
var irqHandler func()
func handleInterrupt(machine.Pin) {
irqHandler()
}
+67 -33
View File
@@ -21,9 +21,10 @@ const (
// Device wraps an SPI connection to a SX127x device.
type Device struct {
spi drivers.SPI // SPI bus for module communication
rstPin, csPin machine.Pin // GPIOs for reset and chip select
rstPin machine.Pin // GPIO for reset
radioEventChan chan lora.RadioEvent // Channel for Receiving events
loraConf lora.Config // Current Lora configuration
controller RadioController // to manage interactions with the radio
deepSleep bool // Internal Sleep state
deviceType int // sx1261,sx1262,sx1268 (defaults sx1261)
spiBuffer [SPI_BUFFER_SIZE]uint8
@@ -41,16 +42,26 @@ func (d *Device) GetRadioEventChan() chan lora.RadioEvent {
}
// New creates a new SX127x connection. The SPI bus must already be configured.
func New(spi machine.SPI, csPin machine.Pin, rstPin machine.Pin) *Device {
func New(spi machine.SPI, rstPin machine.Pin) *Device {
k := Device{
spi: spi,
csPin: csPin,
rstPin: rstPin,
radioEventChan: make(chan lora.RadioEvent, 10),
}
return &k
}
// SetRadioControl let you define the RadioController
func (d *Device) SetRadioController(rc RadioController) error {
d.controller = rc
if err := d.controller.Init(); err != nil {
return err
}
d.controller.SetupInterrupts(d.HandleInterrupt)
return nil
}
// Reset re-initialize the sx127x device
func (d *Device) Reset() {
d.rstPin.Low()
@@ -67,21 +78,21 @@ func (d *Device) DetectDevice() bool {
// ReadRegister reads register value
func (d *Device) ReadRegister(reg uint8) uint8 {
d.csPin.Low()
d.controller.SetNss(false)
d.spi.Tx([]byte{reg & 0x7f}, nil)
var value [1]byte
d.spi.Tx(nil, value[:])
d.csPin.High()
d.controller.SetNss(true)
return value[0]
}
// WriteRegister writes value to register
func (d *Device) WriteRegister(reg uint8, value uint8) uint8 {
var response [1]byte
d.csPin.Low()
d.controller.SetNss(false)
d.spi.Tx([]byte{reg | 0x80}, nil)
d.spi.Tx([]byte{value}, response[:])
d.csPin.High()
d.controller.SetNss(true)
return response[0]
}
@@ -134,8 +145,8 @@ func (d *Device) GetBandwidth() int32 {
}
*/
// SetTxPower sets the transmitter output power
func (d *Device) SetTxPower(txPower int8, paBoost bool) {
// SetTxPowerWithPaBoost sets the transmitter output power and may activate paBoost
func (d *Device) SetTxPowerWithPaBoost(txPower int8, paBoost bool) {
if !paBoost {
// RFO
if txPower < 0 {
@@ -260,8 +271,8 @@ func (d *Device) SetCodingRate(cr uint8) {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, (d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)&0xf1)|(cr<<1))
}
// SetImplicitHeaderModeOn Enables implicit header mode ***
func (d *Device) SetHeaderMode(headerType uint8) {
// SetHeaderType set implicit or explicit mode
func (d *Device) SetHeaderType(headerType uint8) {
d.loraConf.HeaderType = headerType
if headerType == lora.HeaderImplicit {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)|0x01)
@@ -284,13 +295,10 @@ func (d *Device) SetSpreadingFactor(sf uint8) {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, newValue)
}
// SetTxContinuousMode enable Continuous Tx mode
func (d *Device) SetTxContinuousMode(val bool) {
if val {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)|0x08)
} else {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)&0xf7)
}
// SetTxPower sets the transmitter output (with paBoost ON)
func (d *Device) SetTxPower(txPower int8) {
d.loraConf.LoraTxPowerDBm = txPower
d.SetTxPowerWithPaBoost(txPower, true)
}
// SetCrc Enable CRC generation and check on payload
@@ -304,8 +312,9 @@ func (d *Device) SetCrc(enable bool) {
}
}
func (d *Device) SetPreamble(pLen uint16) {
// Sets preamble length
// SetPreambleLength defines number of preamble
func (d *Device) SetPreambleLength(pLen uint16) {
d.loraConf.Preamble = pLen
d.WriteRegister(SX127X_REG_PREAMBLE_MSB, uint8((pLen>>8)&0xFF))
d.WriteRegister(SX127X_REG_PREAMBLE_LSB, uint8(pLen&0xFF))
}
@@ -331,6 +340,15 @@ func (d *Device) SetIqMode(val uint8) {
}
}
// SetPublicNetwork changes Sync Word to match network type
func (d *Device) SetPublicNetwork(enabled bool) {
if enabled {
d.SetSyncWord(SX127X_LORA_MAC_PUBLIC_SYNCWORD)
} else {
d.SetSyncWord(SX127X_LORA_MAC_PRIVATE_SYNCWORD)
}
}
// Tx sends a lora packet, (with timeout)
func (d *Device) Tx(pkt []uint8, timeoutMs uint32) error {
d.SetOpModeLora()
@@ -342,15 +360,15 @@ func (d *Device) Tx(pkt []uint8, timeoutMs uint32) error {
d.WriteRegister(SX127X_REG_LNA, SX127X_LNA_MAX_GAIN) // Set Low Noise Amplifier to MAX
d.SetFrequency(d.loraConf.Freq)
d.SetPreamble(d.loraConf.Preamble)
d.SetPreambleLength(d.loraConf.Preamble)
d.SetSyncWord(d.loraConf.SyncWord)
d.SetBandwidth(d.loraConf.Bw)
d.SetSpreadingFactor(d.loraConf.Sf)
d.SetIqMode(d.loraConf.Iq)
d.SetCodingRate(d.loraConf.Cr)
d.SetCrc(d.loraConf.Crc == lora.CRCOn)
d.SetTxPower(d.loraConf.LoraTxPowerDBm, true)
d.SetHeaderMode(d.loraConf.HeaderType)
d.SetTxPower(d.loraConf.LoraTxPowerDBm)
d.SetHeaderType(d.loraConf.HeaderType)
d.SetAgcAuto(SX127X_AGC_AUTO_ON)
// set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP
@@ -398,15 +416,15 @@ func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
d.WriteRegister(SX127X_REG_LNA, SX127X_LNA_MAX_GAIN) // Set Low Noise Amplifier to MAX
d.SetFrequency(d.loraConf.Freq)
d.SetPreamble(d.loraConf.Preamble)
d.SetPreambleLength(d.loraConf.Preamble)
d.SetSyncWord(d.loraConf.SyncWord)
d.SetBandwidth(d.loraConf.Bw)
d.SetSpreadingFactor(d.loraConf.Sf)
d.SetIqMode(d.loraConf.Iq)
d.SetCodingRate(d.loraConf.Cr)
d.SetCrc(d.loraConf.Crc == lora.CRCOn)
d.SetTxPower(d.loraConf.LoraTxPowerDBm, true)
d.SetHeaderMode(d.loraConf.HeaderType)
d.SetTxPower(d.loraConf.LoraTxPowerDBm)
d.SetHeaderType(d.loraConf.HeaderType)
d.SetAgcAuto(SX127X_AGC_AUTO_ON)
// set the IRQ mapping DIO0=RxDone DIO1=RxTimeout DIO2=NOP
@@ -415,19 +433,26 @@ func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
// Mask all but RxDone
d.WriteRegister(SX127X_REG_IRQ_FLAGS_MASK, ^(SX127X_IRQ_LORA_RXDONE_MASK | SX127X_IRQ_LORA_RXTOUT_MASK))
// Switch to RX Mode
d.SetOpMode(SX127X_OPMODE_RX_SINGLE) //
// Wait for Radio Event
// Get Radio Event Channel
radioCh := d.GetRadioEventChan()
msg := <-radioCh
if msg.EventType == lora.RadioEventTimeout {
// Single RX mode don't properly handle Timeouts on sx127x, so we use Continuous RX
// Go routine is a workaround to stop the Continuous RX and fire a timeout Event
d.SetOpMode(SX127X_OPMODE_RX)
var msg lora.RadioEvent
select {
case msg = <-radioCh:
if msg.EventType != lora.RadioEventRxDone {
return nil, errors.New("Unexpected Radio Event while RX " + string(0x30+msg.EventType))
}
case <-time.After(time.Millisecond * time.Duration(timeoutMs)):
d.SetOpMode(SX127X_OPMODE_STANDBY)
return nil, nil
} else if msg.EventType != lora.RadioEventRxDone {
return nil, errors.New("Unexpected Radio Event while RX " + string(0x30+msg.EventType))
}
// Get the received payload
d.WriteRegister(SX127X_REG_FIFO_RX_BASE_ADDR, 0)
d.WriteRegister(SX127X_REG_FIFO_ADDR_PTR, 0)
@@ -440,6 +465,15 @@ func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
return d.spiBuffer[:pLen], nil
}
// SetTxContinuousMode enable Continuous Tx mode
func (d *Device) SetTxContinuousMode(val bool) {
if val {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)|0x08)
} else {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)&0xf7)
}
}
//
// HELPER FUNCTIONS
//
+1 -1
View File
@@ -2,4 +2,4 @@ package drivers
// Version returns a user-readable string showing the version of the drivers package for support purposes.
// Update this value before release of new version of software.
const Version = "0.23.0"
const Version = "0.24.0"