mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-07 08:23:42 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fc9dd0670 | |||
| 479beeeece | |||
| 2526660a96 | |||
| 4213d6f582 | |||
| 81cf507e4e | |||
| 86cc0e31f1 | |||
| e3e95cacfe |
@@ -13,11 +13,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: tinygo/tinygo-dev
|
container: tinygo/tinygo-dev
|
||||||
steps:
|
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
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
- name: TinyGo version check
|
- name: TinyGo version check
|
||||||
run: tinygo version
|
run: tinygo version
|
||||||
- name: Enforce Go Formatted Code
|
- name: Enforce Go Formatted Code
|
||||||
|
|||||||
@@ -1,80 +1,3 @@
|
|||||||
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
|
0.23.0
|
||||||
---
|
---
|
||||||
- **new devices**
|
- **new devices**
|
||||||
|
|||||||
@@ -249,9 +249,6 @@ endif
|
|||||||
@md5sum ./build/test.uf2
|
@md5sum ./build/test.uf2
|
||||||
tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go
|
tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go
|
||||||
@md5sum ./build/test.uf2
|
@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)
|
# rwildcard is a recursive version of $(wildcard)
|
||||||
# https://blog.jgc.org/2011/07/gnu-make-recursive-wildcard-function.html
|
# https://blog.jgc.org/2011/07/gnu-make-recursive-wildcard-function.html
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ The following 90 devices are supported.
|
|||||||
| [Servo](https://learn.sparkfun.com/tutorials/hobby-servo-tutorial/all) | PWM |
|
| [Servo](https://learn.sparkfun.com/tutorials/hobby-servo-tutorial/all) | PWM |
|
||||||
| [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO |
|
| [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO |
|
||||||
| [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO |
|
| [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO |
|
||||||
| [SH1106 OLED display](https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf) | I2C / SPI |
|
|
||||||
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
|
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
|
||||||
| [SHTC3 Digital Humidity Sensor (RH/T)](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHTC3_Datasheet.pdf) | I2C |
|
| [SHTC3 Digital Humidity Sensor (RH/T)](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHTC3_Datasheet.pdf) | I2C |
|
||||||
| [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI |
|
| [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI |
|
||||||
@@ -146,7 +145,6 @@ The following 90 devices are supported.
|
|||||||
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
|
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
|
||||||
| [XPT2046 touch controller](http://grobotronics.com/images/datasheets/xpt2046-datasheet.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 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 |
|
| [SSD1289 TFT color display](http://aitendo3.sakura.ne.jp/aitendo_data/product_img/lcd/tft2/M032C1289TP/3.2-SSD1289.pdf) | GPIO |
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
package bmp180 // import "tinygo.org/x/drivers/bmp180"
|
package bmp180 // import "tinygo.org/x/drivers/bmp180"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
@@ -124,21 +123,6 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
|
|||||||
return 1000 * (p + ((x1 + x2 + 3791) >> 4)), nil
|
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
|
// rawTemp returns the sensor's raw values of the temperature
|
||||||
func (d *Device) rawTemp() (int32, error) {
|
func (d *Device) rawTemp() (int32, error) {
|
||||||
d.bus.WriteRegister(uint8(d.Address), REG_CTRL, []byte{CMD_TEMP})
|
d.bus.WriteRegister(uint8(d.Address), REG_CTRL, []byte{CMD_TEMP})
|
||||||
|
|||||||
@@ -28,7 +28,3 @@ const (
|
|||||||
// ULTRAHIGHRESOLUTION is the highest oversampling mode of the pressure measurement.
|
// ULTRAHIGHRESOLUTION is the highest oversampling mode of the pressure measurement.
|
||||||
ULTRAHIGHRESOLUTION
|
ULTRAHIGHRESOLUTION
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ func main() {
|
|||||||
pressure, _ := sensor.ReadPressure()
|
pressure, _ := sensor.ReadPressure()
|
||||||
println("Pressure", float32(pressure)/100000, "hPa")
|
println("Pressure", float32(pressure)/100000, "hPa")
|
||||||
|
|
||||||
altitude, _ := sensor.ReadAltitude()
|
|
||||||
println("Altitude", altitude, "meters")
|
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@@ -1,496 +0,0 @@
|
|||||||
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.
|
|
||||||
func quicktest() {
|
|
||||||
writeCommandOutput("AT", "OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check firmware version.
|
|
||||||
func version() {
|
|
||||||
writeCommandOutput("VER", common.CurrentVersion()+" ("+common.FirmwareVersion()+")")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use to check the ID of the LoRaWAN module, or change the ID.
|
|
||||||
func id(args string) error {
|
|
||||||
cmd := "ID"
|
|
||||||
|
|
||||||
// look for comma in args
|
|
||||||
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":
|
|
||||||
if err := session.SetDevAddr(hexdata); err != nil {
|
|
||||||
writeCommandOutput(cmd, err.Error())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
|
|
||||||
case "DevEui":
|
|
||||||
if err := otaa.SetDevEUI(hexdata); err != nil {
|
|
||||||
writeCommandOutput(cmd, err.Error())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
|
|
||||||
case "AppEui":
|
|
||||||
if err := otaa.SetAppEUI(hexdata); err != nil {
|
|
||||||
writeCommandOutput(cmd, err.Error())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
|
|
||||||
default:
|
|
||||||
return errInvalidCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get
|
|
||||||
switch param {
|
|
||||||
case "DevAddr":
|
|
||||||
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
|
|
||||||
case "DevEui":
|
|
||||||
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
|
|
||||||
case "AppEui":
|
|
||||||
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
|
|
||||||
default:
|
|
||||||
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
|
|
||||||
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
|
|
||||||
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use to reset the module. If module returns error, then reset function is invalid.
|
|
||||||
func reset() error {
|
|
||||||
radio.Reset()
|
|
||||||
|
|
||||||
writeCommandOutput("RESET", "OK")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use to send string format frame which is no need to be confirmed by the server.
|
|
||||||
func msg(data string) error {
|
|
||||||
cmd := "MSG"
|
|
||||||
writeCommandOutput(cmd, "Start")
|
|
||||||
|
|
||||||
if err := radio.Tx([]byte(data), defaultTimeout); err != nil {
|
|
||||||
writeCommandOutput(cmd, err.Error())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Done")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use to send string format frame which must be confirmed by the server
|
|
||||||
func cmsg(data string) error {
|
|
||||||
cmd := "CMSG"
|
|
||||||
writeCommandOutput(cmd, "Start")
|
|
||||||
|
|
||||||
if err := radio.Tx([]byte(data), defaultTimeout); err != nil {
|
|
||||||
writeCommandOutput(cmd, err.Error())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: confirmation
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Done")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use to send hex format frame which is no need to be confirmed by the server
|
|
||||||
func msghex(data string) error {
|
|
||||||
cmd := "MSGHEX"
|
|
||||||
writeCommandOutput(cmd, "Start")
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Done")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use to send hex format frame which must be confirmed by the server.
|
|
||||||
func cmsghex(data string) error {
|
|
||||||
cmd := "CMSGHEX"
|
|
||||||
writeCommandOutput(cmd, "Start")
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Done")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use to send string format LoRaWAN proprietary frames
|
|
||||||
func pmsg(data string) error {
|
|
||||||
cmd := "PMSG"
|
|
||||||
writeCommandOutput(cmd, "Start")
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Done")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use to send hex format LoRaWAN proprietary frames.
|
|
||||||
func pmsghex(data string) error {
|
|
||||||
cmd := "PMSGHEX"
|
|
||||||
writeCommandOutput(cmd, "Start")
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Done")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set PORT number which will be used by MSG/CMSG/MSGHEX/CMSGHEX command to send
|
|
||||||
// message, port number should range from 1 to 255. User should refer to LoRaWAN
|
|
||||||
// specification to choose port.
|
|
||||||
func port(p string) error {
|
|
||||||
cmd := "PMSG"
|
|
||||||
writeCommandOutput(cmd, p)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set ADR function of LoRaWAN module
|
|
||||||
func adr(state string) error {
|
|
||||||
cmd := "ADR"
|
|
||||||
writeCommandOutput(cmd, state)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use LoRaWAN defined DRx to set datarate of LoRaWAN AT modem.
|
|
||||||
func dr(rate string) error {
|
|
||||||
cmd := "DR"
|
|
||||||
writeCommandOutput(cmd, rate)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Channel Configuration
|
|
||||||
func ch(channel string) error {
|
|
||||||
cmd := "CH"
|
|
||||||
writeCommandOutput(cmd, channel)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set and Check Power
|
|
||||||
func power(setting string) error {
|
|
||||||
cmd := "POWER"
|
|
||||||
writeCommandOutput(cmd, setting)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unconfirmed message repeats times.
|
|
||||||
func rept(setting string) error {
|
|
||||||
cmd := "REPT"
|
|
||||||
writeCommandOutput(cmd, setting)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirmed message retry times. Valid range 0~254,
|
|
||||||
// if retry times is less than 2, only one message will
|
|
||||||
// be sent. Random delay 3 - 10s between each retry
|
|
||||||
// (band duty cycle limitation has the priority)
|
|
||||||
func retry(setting string) error {
|
|
||||||
cmd := "RETRY"
|
|
||||||
writeCommandOutput(cmd, setting)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rxwin2(setting string) error {
|
|
||||||
cmd := "RXWIN2"
|
|
||||||
writeCommandOutput(cmd, setting)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rxwin1(setting string) error {
|
|
||||||
cmd := "RXWIN1"
|
|
||||||
writeCommandOutput(cmd, setting)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func key(setting string) error {
|
|
||||||
cmd := "KEY"
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
cmd := "FDEFAULT"
|
|
||||||
writeCommandOutput(cmd, "OK")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mode(setting string) error {
|
|
||||||
cmd := "MODE"
|
|
||||||
writeCommandOutput(cmd, setting)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func beacon(setting string) error {
|
|
||||||
cmd := "BEACON"
|
|
||||||
writeCommandOutput(cmd, "Starting")
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Done")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func class(setting string) error {
|
|
||||||
cmd := "CLASS"
|
|
||||||
writeCommandOutput(cmd, "Starting")
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Done")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func delay(setting string) error {
|
|
||||||
cmd := "DELAY"
|
|
||||||
writeCommandOutput(cmd, setting)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func wdt(setting string) error {
|
|
||||||
cmd := "WDT"
|
|
||||||
writeCommandOutput(cmd, "Not implemented")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lowpower(setting string) error {
|
|
||||||
cmd := "LOWPOWER"
|
|
||||||
writeCommandOutput(cmd, "Not implemented")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func vdd(setting string) error {
|
|
||||||
cmd := "VDD"
|
|
||||||
writeCommandOutput(cmd, "Not implemented")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func temp(setting string) error {
|
|
||||||
cmd := "TEMP"
|
|
||||||
writeCommandOutput(cmd, "Not implemented")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rtc(setting string) error {
|
|
||||||
cmd := "RTC"
|
|
||||||
writeCommandOutput(cmd, "Not implemented")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func eeprom(setting string) error {
|
|
||||||
cmd := "EEPROM"
|
|
||||||
writeCommandOutput(cmd, "Not implemented")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func uartcmd(setting string) error {
|
|
||||||
cmd := "UART"
|
|
||||||
writeCommandOutput(cmd, "Not implemented")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func test(setting string) error {
|
|
||||||
cmd := "TEST"
|
|
||||||
writeCommandOutput(cmd, "Not implemented")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func log(setting string) error {
|
|
||||||
cmd := "LOG"
|
|
||||||
writeCommandOutput(cmd, "Not implemented")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func send(data string) error {
|
|
||||||
cmd := "SEND"
|
|
||||||
writeCommandOutput(cmd, "Start")
|
|
||||||
|
|
||||||
// remove leading/trailing quotes
|
|
||||||
data = strings.Trim(data, "\"'")
|
|
||||||
|
|
||||||
if err := radio.Tx([]byte(data), defaultTimeout); err != nil {
|
|
||||||
writeCommandOutput(cmd, err.Error())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Done")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendhex(data string) error {
|
|
||||||
cmd := "SENDHEX"
|
|
||||||
writeCommandOutput(cmd, "Start")
|
|
||||||
|
|
||||||
// remove leading/trailing quotes
|
|
||||||
data = strings.Trim(data, "\"'")
|
|
||||||
|
|
||||||
// convert data from hex formatted string
|
|
||||||
data = strings.ReplaceAll(data, " ", "")
|
|
||||||
payload, err := hex.DecodeString(data)
|
|
||||||
if err != nil {
|
|
||||||
writeCommandOutput(cmd, err.Error())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := radio.Tx(payload, defaultTimeout); err != nil {
|
|
||||||
writeCommandOutput(cmd, err.Error())
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
writeCommandOutput(cmd, "Done")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func recv(setting string) error {
|
|
||||||
cmd := "RECV"
|
|
||||||
|
|
||||||
data, err := common.Lorarx()
|
|
||||||
if err != nil {
|
|
||||||
writeCommandOutput(cmd, "ERROR "+err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
writeCommandOutput(cmd, string(data))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func recvhex(setting string) error {
|
|
||||||
cmd := "RECVHEX"
|
|
||||||
|
|
||||||
data, err := common.Lorarx()
|
|
||||||
if err != nil {
|
|
||||||
writeCommandOutput(cmd, "ERROR "+err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
writeCommandOutput(cmd, string(data))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func crlf() {
|
|
||||||
uart.Write([]byte("\r\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeCommandOutput(cmd, data string) {
|
|
||||||
uart.Write([]byte("+" + cmd + ": "))
|
|
||||||
uart.Write([]byte(data))
|
|
||||||
crlf()
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
// AT command set console running on the device UART to communicate with
|
|
||||||
// an attached LoRa device.
|
|
||||||
//
|
|
||||||
// Computer <-> UART <-> MCU <-> SPI <-> SX126x/SX127x
|
|
||||||
//
|
|
||||||
// Connect using default baudrate for this hardware, 8-N-1 with your terminal program.
|
|
||||||
// For details on the AT command set, see:
|
|
||||||
// https://files.seeedstudio.com/products/317990687/res/LoRa-E5%20AT%20Command%20Specification_V1.0%20.pdf
|
|
||||||
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
|
|
||||||
var (
|
|
||||||
uart = machine.Serial
|
|
||||||
tx = machine.UART_TX_PIN
|
|
||||||
rx = machine.UART_RX_PIN
|
|
||||||
input = make([]byte, 0, 64)
|
|
||||||
|
|
||||||
radio lora.Radio
|
|
||||||
session *lorawan.Session
|
|
||||||
otaa *lorawan.Otaa
|
|
||||||
|
|
||||||
defaultTimeout uint32 = 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
|
||||||
|
|
||||||
var err error
|
|
||||||
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()
|
|
||||||
|
|
||||||
switch data {
|
|
||||||
case 13:
|
|
||||||
// return key
|
|
||||||
if err := parse(input); err != nil {
|
|
||||||
uart.Write([]byte("ERROR: "))
|
|
||||||
uart.Write([]byte(err.Error()))
|
|
||||||
crlf()
|
|
||||||
}
|
|
||||||
input = input[:0]
|
|
||||||
default:
|
|
||||||
// just capture the character
|
|
||||||
input = append(input, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fail(msg string) {
|
|
||||||
for {
|
|
||||||
uart.Write([]byte(msg))
|
|
||||||
crlf()
|
|
||||||
|
|
||||||
time.Sleep(time.Minute)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errInvalidCommand = errors.New("Invalid command")
|
|
||||||
)
|
|
||||||
|
|
||||||
func parse(data []byte) error {
|
|
||||||
switch {
|
|
||||||
case len(data) < 2, string(data[0:2]) != "AT":
|
|
||||||
return errInvalidCommand
|
|
||||||
case len(data) == 2:
|
|
||||||
// just the AT command by itself
|
|
||||||
quicktest()
|
|
||||||
case len(data) < 6 || data[2] != '+':
|
|
||||||
return errInvalidCommand
|
|
||||||
default:
|
|
||||||
// parse the rest of the command
|
|
||||||
cmd, args, _ := strings.Cut(string(data[3:]), "=")
|
|
||||||
return parseCommand(cmd, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseCommand(cmd, args string) error {
|
|
||||||
switch cmd {
|
|
||||||
case "VER":
|
|
||||||
version()
|
|
||||||
case "ID":
|
|
||||||
id(args)
|
|
||||||
case "RESET":
|
|
||||||
reset()
|
|
||||||
case "MSG":
|
|
||||||
msg(args)
|
|
||||||
case "CMSG":
|
|
||||||
cmsg(args)
|
|
||||||
case "MSGHEX":
|
|
||||||
msghex(args)
|
|
||||||
case "CMSGHEX":
|
|
||||||
cmsghex(args)
|
|
||||||
case "PMSG":
|
|
||||||
pmsg(args)
|
|
||||||
case "PMSGHEX":
|
|
||||||
pmsg(args)
|
|
||||||
case "PORT":
|
|
||||||
port(args)
|
|
||||||
case "ADR":
|
|
||||||
adr(args)
|
|
||||||
case "DR":
|
|
||||||
dr(args)
|
|
||||||
case "CH":
|
|
||||||
ch(args)
|
|
||||||
case "POWER":
|
|
||||||
power(args)
|
|
||||||
case "REPT":
|
|
||||||
rept(args)
|
|
||||||
case "RETRY":
|
|
||||||
retry(args)
|
|
||||||
case "RXWIN2":
|
|
||||||
rxwin2(args)
|
|
||||||
case "RXWIN1":
|
|
||||||
rxwin1(args)
|
|
||||||
case "KEY":
|
|
||||||
key(args)
|
|
||||||
case "FDEFAULT":
|
|
||||||
fdefault(args)
|
|
||||||
case "MODE":
|
|
||||||
mode(args)
|
|
||||||
case "JOIN":
|
|
||||||
join(args)
|
|
||||||
case "BEACON":
|
|
||||||
join(args)
|
|
||||||
case "CLASS":
|
|
||||||
class(args)
|
|
||||||
case "DELAY":
|
|
||||||
delay(args)
|
|
||||||
case "LW":
|
|
||||||
lw(args)
|
|
||||||
case "WDT":
|
|
||||||
wdt(args)
|
|
||||||
case "LOWPOWER":
|
|
||||||
lowpower(args)
|
|
||||||
case "VDD":
|
|
||||||
vdd(args)
|
|
||||||
case "TEMP":
|
|
||||||
temp(args)
|
|
||||||
case "RTC":
|
|
||||||
rtc(args)
|
|
||||||
case "EEPROM":
|
|
||||||
eeprom(args)
|
|
||||||
case "UART":
|
|
||||||
uartcmd(args)
|
|
||||||
case "TEST":
|
|
||||||
test(args)
|
|
||||||
case "LOG":
|
|
||||||
log(args)
|
|
||||||
case "RECV":
|
|
||||||
recv(args)
|
|
||||||
case "RECVHEX":
|
|
||||||
recvhex(args)
|
|
||||||
case "SEND":
|
|
||||||
send(args)
|
|
||||||
case "SENDHEX":
|
|
||||||
sendhex(args)
|
|
||||||
default:
|
|
||||||
return errInvalidCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# 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
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//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)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
// 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++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//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
|
|
||||||
)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
//go:build lgt92
|
|
||||||
|
|
||||||
package common
|
|
||||||
|
|
||||||
import "machine"
|
|
||||||
|
|
||||||
var (
|
|
||||||
rstPin = machine.PB0
|
|
||||||
csPin = machine.PA15
|
|
||||||
dio0Pin = machine.PC13
|
|
||||||
dio1Pin = machine.PB10
|
|
||||||
spi = machine.SPI0
|
|
||||||
)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errRadioNotFound = errors.New("radio not found")
|
|
||||||
errRxTimeout = errors.New("radio RX timeout")
|
|
||||||
)
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
//go:build !featherwing && !lgt92 && !stm32wlx && !sx126x
|
|
||||||
|
|
||||||
package common
|
|
||||||
|
|
||||||
import "tinygo.org/x/drivers/lora"
|
|
||||||
|
|
||||||
// do simulator setup here
|
|
||||||
func SetupLora() (lora.Radio, error) {
|
|
||||||
return &SimLoraRadio{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type SimLoraRadio struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sr *SimLoraRadio) Reset() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sr *SimLoraRadio) Tx(pkt []uint8, timeoutMs uint32) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sr *SimLoraRadio) Rx(timeoutMs uint32) ([]uint8, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sr *SimLoraRadio) SetFrequency(freq uint32) {}
|
|
||||||
func (sr *SimLoraRadio) SetIqMode(mode uint8) {}
|
|
||||||
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 Lorarx() ([]byte, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
//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)
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
//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)
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
//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)
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
const VERSION = "0.0.1"
|
|
||||||
|
|
||||||
func CurrentVersion() string {
|
|
||||||
return VERSION
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
//go:build macropad_rp2040
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/color"
|
|
||||||
"machine"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sh1106"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
display = sh1106.NewSPI(machine.SPI1, machine.OLED_DC, machine.OLED_RST, machine.OLED_CS)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
machine.SPI1.Configure(machine.SPIConfig{
|
|
||||||
Frequency: 48000000,
|
|
||||||
})
|
|
||||||
display.Configure(sh1106.Config{
|
|
||||||
Width: 128,
|
|
||||||
Height: 64,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
display.ClearDisplay()
|
|
||||||
|
|
||||||
x := int16(0)
|
|
||||||
y := int16(0)
|
|
||||||
deltaX := int16(1)
|
|
||||||
deltaY := int16(1)
|
|
||||||
for {
|
|
||||||
pixel := display.GetPixel(x, y)
|
|
||||||
c := color.RGBA{255, 255, 255, 255}
|
|
||||||
if pixel {
|
|
||||||
c = color.RGBA{0, 0, 0, 255}
|
|
||||||
}
|
|
||||||
display.SetPixel(x, y, c)
|
|
||||||
display.Display()
|
|
||||||
|
|
||||||
x += deltaX
|
|
||||||
y += deltaY
|
|
||||||
|
|
||||||
if x == 0 || x == 127 {
|
|
||||||
deltaX = -deltaX
|
|
||||||
}
|
|
||||||
|
|
||||||
if y == 0 || y == 63 {
|
|
||||||
deltaY = -deltaY
|
|
||||||
}
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,8 @@ import (
|
|||||||
"machine"
|
"machine"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tinygo.org/x/drivers/lora"
|
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
"tinygo.org/x/drivers/sx126x"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,11 +23,12 @@ func main() {
|
|||||||
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
// Create the driver
|
// Create the driver
|
||||||
loraRadio = sx126x.New(spi)
|
loraRadio = sx126x.New(machine.SPI3)
|
||||||
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||||
|
|
||||||
// Create radio controller for target
|
// Create RF Switch
|
||||||
loraRadio.SetRadioController(newRadioControl())
|
var radioSwitch rfswitch.CustomSwitch
|
||||||
|
loraRadio.SetRfSwitch(radioSwitch)
|
||||||
|
|
||||||
state := loraRadio.DetectDevice()
|
state := loraRadio.DetectDevice()
|
||||||
if !state {
|
if !state {
|
||||||
@@ -34,18 +36,18 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare for Lora operation
|
// Prepare for Lora operation
|
||||||
loraConf := lora.Config{
|
loraConf := sx126x.LoraConfig{
|
||||||
Freq: lora.MHz_868_1,
|
Freq: FREQ,
|
||||||
Bw: lora.Bandwidth_125_0,
|
Bw: sx126x.SX126X_LORA_BW_500_0,
|
||||||
Sf: lora.SpreadingFactor9,
|
Sf: sx126x.SX126X_LORA_SF9,
|
||||||
Cr: lora.CodingRate4_7,
|
Cr: sx126x.SX126X_LORA_CR_4_7,
|
||||||
HeaderType: lora.HeaderExplicit,
|
HeaderType: sx126x.SX126X_LORA_HEADER_EXPLICIT,
|
||||||
Preamble: 12,
|
Preamble: 12,
|
||||||
Ldr: lora.LowDataRateOptimizeOff,
|
Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF,
|
||||||
Iq: lora.IQStandard,
|
Iq: sx126x.SX126X_LORA_IQ_STANDARD,
|
||||||
Crc: lora.CRCOn,
|
Crc: sx126x.SX126X_LORA_CRC_ON,
|
||||||
SyncWord: lora.SyncPrivate,
|
SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD,
|
||||||
LoraTxPowerDBm: 20,
|
LoraTxPowerDBm: 14,
|
||||||
}
|
}
|
||||||
loraRadio.LoraConfig(loraConf)
|
loraRadio.LoraConfig(loraConf)
|
||||||
|
|
||||||
@@ -73,5 +75,6 @@ func main() {
|
|||||||
|
|
||||||
loraRadio.SetStandby()
|
loraRadio.SetStandby()
|
||||||
time.Sleep(60 * time.Second)
|
time.Sleep(60 * time.Second)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
//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)
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//go:build stm32wlx
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
|
||||||
)
|
|
||||||
|
|
||||||
var spi = machine.SPI3
|
|
||||||
|
|
||||||
func newRadioControl() sx126x.RadioController {
|
|
||||||
return sx126x.NewRadioControl()
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,18 @@ package main
|
|||||||
// module will be in RX mode between two transmissions
|
// module will be in RX mode between two transmissions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"device/stm32"
|
||||||
"machine"
|
"machine"
|
||||||
|
"runtime/interrupt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tinygo.org/x/drivers/lora"
|
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
"tinygo.org/x/drivers/sx126x"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const FREQ = 868100000
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
||||||
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
||||||
@@ -21,19 +26,23 @@ var (
|
|||||||
txmsg = []byte("Hello TinyGO")
|
txmsg = []byte("Hello TinyGO")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
// radioIntHandler will take care of radio interrupts
|
||||||
time.Sleep(3 * time.Second)
|
func radioIntHandler(intr interrupt.Interrupt) {
|
||||||
|
loraRadio.HandleInterrupt()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
println("\n# TinyGo Lora RX/TX test")
|
println("\n# TinyGo Lora RX/TX test")
|
||||||
println("# ----------------------")
|
println("# ----------------------")
|
||||||
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
// Create the driver
|
// Create the driver
|
||||||
loraRadio = sx126x.New(spi)
|
loraRadio = sx126x.New(machine.SPI3)
|
||||||
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||||
|
|
||||||
// Create radio controller for target
|
// Create RF Switch
|
||||||
loraRadio.SetRadioController(newRadioControl())
|
var radioSwitch rfswitch.CustomSwitch
|
||||||
|
loraRadio.SetRfSwitch(radioSwitch)
|
||||||
|
|
||||||
// Detect the device
|
// Detect the device
|
||||||
state := loraRadio.DetectDevice()
|
state := loraRadio.DetectDevice()
|
||||||
@@ -41,17 +50,21 @@ func main() {
|
|||||||
panic("sx126x not detected.")
|
panic("sx126x not detected.")
|
||||||
}
|
}
|
||||||
|
|
||||||
loraConf := lora.Config{
|
// Add interrupt handler for Radio IRQs
|
||||||
Freq: lora.MHz_868_1,
|
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
|
||||||
Bw: lora.Bandwidth_125_0,
|
intr.Enable()
|
||||||
Sf: lora.SpreadingFactor9,
|
|
||||||
Cr: lora.CodingRate4_7,
|
loraConf := sx126x.LoraConfig{
|
||||||
HeaderType: lora.HeaderExplicit,
|
Freq: FREQ,
|
||||||
|
Bw: sx126x.SX126X_LORA_BW_500_0,
|
||||||
|
Sf: sx126x.SX126X_LORA_SF9,
|
||||||
|
Cr: sx126x.SX126X_LORA_CR_4_7,
|
||||||
|
HeaderType: sx126x.SX126X_LORA_HEADER_EXPLICIT,
|
||||||
Preamble: 12,
|
Preamble: 12,
|
||||||
Ldr: lora.LowDataRateOptimizeOff,
|
Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF,
|
||||||
Iq: lora.IQStandard,
|
Iq: sx126x.SX126X_LORA_IQ_STANDARD,
|
||||||
Crc: lora.CRCOn,
|
Crc: sx126x.SX126X_LORA_CRC_ON,
|
||||||
SyncWord: lora.SyncPrivate,
|
SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD,
|
||||||
LoraTxPowerDBm: 20,
|
LoraTxPowerDBm: 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,20 +72,23 @@ func main() {
|
|||||||
|
|
||||||
var count uint
|
var count uint
|
||||||
for {
|
for {
|
||||||
start := time.Now()
|
tStart := time.Now()
|
||||||
|
|
||||||
|
// Blocking RX for LORA_DEFAULT_RXTIMEOUT_MS
|
||||||
|
println("Start Lora RX for 10 sec")
|
||||||
|
for int(time.Now().Sub(tStart).Seconds()) < 10 {
|
||||||
|
buf, err := loraRadio.LoraRx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||||
|
|
||||||
println("main: Receiving Lora for 10 seconds")
|
|
||||||
for time.Since(start) < 10*time.Second {
|
|
||||||
buf, err := loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("RX Error: ", err)
|
println("RX Error: ", err)
|
||||||
} else if buf != nil {
|
} else if buf != nil {
|
||||||
println("Packet Received: len=", len(buf), string(buf))
|
println("Packet Received: len=", len(buf), string(buf))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println("main: End Lora RX")
|
println("END Lora RX")
|
||||||
println("LORA TX size=", len(txmsg), " -> ", string(txmsg))
|
|
||||||
err := loraRadio.Tx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS)
|
println("LORA TX size=", len(txmsg))
|
||||||
|
err := loraRadio.LoraTx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("TX Error:", err)
|
println("TX Error:", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
//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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//go:build stm32wlx
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
|
||||||
)
|
|
||||||
|
|
||||||
var spi = machine.SPI3
|
|
||||||
|
|
||||||
func newRadioControl() sx126x.RadioController {
|
|
||||||
return sx126x.NewRadioControl()
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//go:build lorae5
|
||||||
|
|
||||||
|
package radio
|
||||||
|
|
||||||
|
/*
|
||||||
|
/!\ LoRa-E5 module ONLY transmits through RFO_HP:
|
||||||
|
|
||||||
|
Receive: PA4=1, PA5=0
|
||||||
|
Transmit(high output power, SMPS mode): PA4=0, PA5=1
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomSwitch struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CustomSwitch) InitRFSwitch() {
|
||||||
|
machine.PA4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
machine.PB5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CustomSwitch) SetRfSwitchMode(mode int) error {
|
||||||
|
switch mode {
|
||||||
|
|
||||||
|
case sx126x.RFSWITCH_RX:
|
||||||
|
machine.PA4.Set(true)
|
||||||
|
machine.PB5.Set(false)
|
||||||
|
case sx126x.RFSWITCH_TX_LP:
|
||||||
|
errors.New("RFSWITCH_TX_LP not supported ")
|
||||||
|
case sx126x.RFSWITCH_TX_HP:
|
||||||
|
machine.PA4.Set(false)
|
||||||
|
machine.PB5.Set(true)
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
//go:build nucleowl55jc
|
//go:build nucleowl55jc
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Nucleo WL55JC1
|
Nucleo WL55JC1
|
||||||
RFSwitch
|
RFSwitch
|
||||||
|
|
||||||
+-----------+---------+------------+------------+
|
+-----------+---------+------------+------------+
|
||||||
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
|
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
|
||||||
@@ -13,48 +13,42 @@ RFSwitch
|
|||||||
| RX | HIGH | LOW | HIGH |
|
| RX | HIGH | LOW | HIGH |
|
||||||
+-----------+----------+-----------+------------+
|
+-----------+----------+-----------+------------+
|
||||||
*/
|
*/
|
||||||
package sx126x
|
package rfswitch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"machine"
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RadioControl for Nucleo WL55JC1 board.
|
type CustomSwitch struct {
|
||||||
type RadioControl struct {
|
|
||||||
STM32RadioControl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRadioControl() *RadioControl {
|
func (s CustomSwitch) InitRFSwitch() {
|
||||||
return &RadioControl{STM32RadioControl{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init pins needed for controlling rx/tx
|
|
||||||
func (rc *RadioControl) Init() error {
|
|
||||||
machine.PC4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.PC4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
machine.PC5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.PC5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
machine.PC3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.PC3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RadioControl) SetRfSwitchMode(mode int) error {
|
func (s CustomSwitch) SetRfSwitchMode(mode int) error {
|
||||||
switch mode {
|
switch mode {
|
||||||
|
|
||||||
case RFSWITCH_TX_HP:
|
case sx126x.RFSWITCH_TX_HP:
|
||||||
machine.PC4.Set(false)
|
machine.PC4.Set(false)
|
||||||
machine.PC5.Set(true)
|
machine.PC5.Set(true)
|
||||||
machine.PC3.Set(true)
|
machine.PC3.Set(true)
|
||||||
|
|
||||||
case RFSWITCH_TX_LP:
|
case sx126x.RFSWITCH_TX_LP:
|
||||||
machine.PC4.Set(true)
|
machine.PC4.Set(true)
|
||||||
machine.PC5.Set(true)
|
machine.PC5.Set(true)
|
||||||
machine.PC3.Set(true)
|
machine.PC3.Set(true)
|
||||||
|
|
||||||
case RFSWITCH_RX:
|
case sx126x.RFSWITCH_RX:
|
||||||
machine.PC4.Set(true)
|
machine.PC4.Set(true)
|
||||||
machine.PC5.Set(false)
|
machine.PC5.Set(false)
|
||||||
machine.PC3.Set(true)
|
machine.PC3.Set(true)
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// This example code demonstrates Lora RX/TX With SX127x driver
|
|
||||||
// You need to connect SPI, RST, CS, DIO0 (aka IRQ) and DIO1 to use.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/lora"
|
|
||||||
"tinygo.org/x/drivers/sx127x"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
|
||||||
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
loraRadio *sx127x.Device
|
|
||||||
txmsg = []byte("Hello TinyGO")
|
|
||||||
|
|
||||||
// We assume LoRa Featherwing module is connected to PyBadge:
|
|
||||||
SX127X_PIN_RST = machine.D11
|
|
||||||
SX127X_PIN_CS = machine.D10
|
|
||||||
SX127X_PIN_DIO0 = machine.D6
|
|
||||||
SX127X_PIN_DIO1 = machine.D9
|
|
||||||
SX127X_SPI = machine.SPI0
|
|
||||||
)
|
|
||||||
|
|
||||||
func dioIrqHandler(machine.Pin) {
|
|
||||||
loraRadio.HandleInterrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
println("\n# TinyGo Lora RX/TX test")
|
|
||||||
println("# ----------------------")
|
|
||||||
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
SX127X_PIN_RST.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
|
|
||||||
SX127X_SPI.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
|
|
||||||
|
|
||||||
println("main: create and start SX127x driver")
|
|
||||||
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 {
|
|
||||||
panic("main: sx127x NOT FOUND !!!")
|
|
||||||
} else {
|
|
||||||
println("main: sx127x found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare for Lora Operation
|
|
||||||
loraConf := lora.Config{
|
|
||||||
Freq: lora.MHz_868_1,
|
|
||||||
Bw: lora.Bandwidth_125_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)
|
|
||||||
|
|
||||||
var count uint
|
|
||||||
for {
|
|
||||||
tStart := time.Now()
|
|
||||||
|
|
||||||
println("main: Receiving Lora for 10 seconds")
|
|
||||||
for time.Since(tStart) < 10*time.Second {
|
|
||||||
buf, err := loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
|
||||||
if err != nil {
|
|
||||||
println("RX Error: ", err)
|
|
||||||
} else if buf != nil {
|
|
||||||
println("Packet Received: len=", len(buf), string(buf))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println("main: End Lora RX")
|
|
||||||
println("LORA TX size=", len(txmsg), " -> ", string(txmsg))
|
|
||||||
err := loraRadio.Tx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS)
|
|
||||||
if err != nil {
|
|
||||||
println("TX Error:", err)
|
|
||||||
}
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+6
-46
@@ -13,39 +13,6 @@ import (
|
|||||||
var (
|
var (
|
||||||
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
|
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
|
||||||
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
|
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
|
||||||
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
|
|
||||||
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
|
|
||||||
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
|
|
||||||
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
|
|
||||||
errInvalidGLLSentence = errors.New("invalid GLL NMEA sentence")
|
|
||||||
)
|
|
||||||
|
|
||||||
type GPSError struct {
|
|
||||||
Err error
|
|
||||||
Info string
|
|
||||||
Sentence string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGPSError(err error, sentence string, info string) GPSError {
|
|
||||||
return GPSError{
|
|
||||||
Info: info,
|
|
||||||
Err: err,
|
|
||||||
Sentence: sentence,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ge GPSError) Error() string {
|
|
||||||
return ge.Err.Error() + " " + ge.Info + " " + ge.Sentence
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ge GPSError) Unwrap() error {
|
|
||||||
return ge.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
minimumNMEALength = 7
|
|
||||||
startingDelimiter = '$'
|
|
||||||
checksumDelimiter = '*'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Device wraps a connection to a GPS device.
|
// Device wraps a connection to a GPS device.
|
||||||
@@ -93,11 +60,11 @@ func (gps *Device) readNextSentence() (sentence string) {
|
|||||||
gps.sentence.Reset()
|
gps.sentence.Reset()
|
||||||
var b byte = ' '
|
var b byte = ' '
|
||||||
|
|
||||||
for b != startingDelimiter {
|
for b != '$' {
|
||||||
b = gps.readNextByte()
|
b = gps.readNextByte()
|
||||||
}
|
}
|
||||||
|
|
||||||
for b != checksumDelimiter {
|
for b != '*' {
|
||||||
gps.sentence.WriteByte(b)
|
gps.sentence.WriteByte(b)
|
||||||
b = gps.readNextByte()
|
b = gps.readNextByte()
|
||||||
}
|
}
|
||||||
@@ -159,24 +126,17 @@ func (gps *Device) WriteBytes(bytes []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validSentence checks if a sentence has been received uncorrupted
|
// 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 {
|
func validSentence(sentence string) error {
|
||||||
if len(sentence) < minimumNMEALength || sentence[0] != startingDelimiter || sentence[len(sentence)-3] != checksumDelimiter {
|
if len(sentence) < 4 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
|
||||||
return errInvalidNMEASentenceLength
|
return errInvalidNMEASentenceLength
|
||||||
}
|
}
|
||||||
var cs byte = 0
|
var cs byte = 0
|
||||||
for i := 1; i < len(sentence)-3; i++ {
|
for i := 1; i < len(sentence)-3; i++ {
|
||||||
cs ^= sentence[i]
|
cs ^= sentence[i]
|
||||||
}
|
}
|
||||||
checksum := strings.ToUpper(hex.EncodeToString([]byte{cs}))
|
checksum := hex.EncodeToString([]byte{cs})
|
||||||
if checksum != sentence[len(sentence)-2:len(sentence)] {
|
if (checksum[0] != sentence[len(sentence)-2]) || (checksum[1] != sentence[len(sentence)-1]) {
|
||||||
return newGPSError(errInvalidNMEAChecksum, sentence,
|
return errInvalidNMEAChecksum
|
||||||
"expected "+sentence[len(sentence)-2:len(sentence)]+
|
|
||||||
" got "+checksum)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+32
-47
@@ -1,11 +1,19 @@
|
|||||||
package gps
|
package gps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
|
||||||
|
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
|
||||||
|
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
|
||||||
|
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
|
||||||
|
)
|
||||||
|
|
||||||
// Parser for GPS NMEA sentences.
|
// Parser for GPS NMEA sentences.
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
}
|
}
|
||||||
@@ -43,63 +51,43 @@ func NewParser() Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses a NMEA sentence looking for fix info.
|
// Parse parses a NMEA sentence looking for fix info.
|
||||||
func (parser *Parser) Parse(sentence string) (Fix, error) {
|
func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
|
||||||
var fix Fix
|
|
||||||
if sentence == "" {
|
if sentence == "" {
|
||||||
return fix, errEmptyNMEASentence
|
err = errEmptyNMEASentence
|
||||||
}
|
return
|
||||||
if len(sentence) < 6 {
|
|
||||||
return fix, errInvalidNMEASentenceLength
|
|
||||||
}
|
}
|
||||||
typ := sentence[3:6]
|
typ := sentence[3:6]
|
||||||
switch typ {
|
switch typ {
|
||||||
case "GGA":
|
case "GGA":
|
||||||
// https://docs.novatel.com/OEM7/Content/Logs/GPGGA.htm
|
|
||||||
fields := strings.Split(sentence, ",")
|
fields := strings.Split(sentence, ",")
|
||||||
if len(fields) != 15 {
|
if len(fields) != 15 {
|
||||||
return fix, errInvalidGGASentence
|
err = errInvalidGGASentence
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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.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.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
||||||
|
|
||||||
return fix, nil
|
|
||||||
case "GLL":
|
|
||||||
// https://docs.novatel.com/OEM7/Content/Logs/GPGLL.htm
|
|
||||||
fields := strings.Split(sentence, ",")
|
|
||||||
if len(fields) != 8 {
|
|
||||||
return fix, errInvalidGLLSentence
|
|
||||||
}
|
|
||||||
|
|
||||||
fix.Latitude = findLatitude(fields[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":
|
case "RMC":
|
||||||
// https://docs.novatel.com/OEM7/Content/Logs/GPRMC.htm
|
|
||||||
fields := strings.Split(sentence, ",")
|
fields := strings.Split(sentence, ",")
|
||||||
if len(fields) != 13 {
|
if len(fields) != 13 {
|
||||||
return fix, errInvalidRMCSentence
|
err = errInvalidRMCSentence
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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.Longitude = findLongitude(fields[5], fields[6])
|
||||||
|
fix.Latitude = findLatitude(fields[3], fields[4])
|
||||||
|
fix.Time = findTime(fields[1])
|
||||||
fix.Speed = findSpeed(fields[7])
|
fix.Speed = findSpeed(fields[7])
|
||||||
fix.Heading = findHeading(fields[8])
|
fix.Heading = findHeading(fields[8])
|
||||||
|
fix.Valid = (len(fields[2]) > 0 && fields[2][0:1] == "A")
|
||||||
return fix, nil
|
default:
|
||||||
|
err = errUnknownNMEASentence
|
||||||
}
|
}
|
||||||
|
return
|
||||||
return fix, newGPSError(errUnknownNMEASentence, sentence, typ)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findTime returns the time from an NMEA sentence:
|
// findTime returns the time from an NMEA sentence:
|
||||||
@@ -112,10 +100,7 @@ func findTime(val string) time.Time {
|
|||||||
h, _ := strconv.ParseInt(val[0:2], 10, 8)
|
h, _ := strconv.ParseInt(val[0:2], 10, 8)
|
||||||
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
||||||
s, _ := strconv.ParseInt(val[4:6], 10, 8)
|
s, _ := strconv.ParseInt(val[4:6], 10, 8)
|
||||||
ms := int64(0)
|
ms, _ := strconv.ParseInt(val[7:10], 10, 16)
|
||||||
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)
|
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
@@ -135,11 +120,11 @@ func findAltitude(val string) int32 {
|
|||||||
// $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh
|
// $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh
|
||||||
func findLatitude(val, hemi string) float32 {
|
func findLatitude(val, hemi string) float32 {
|
||||||
if len(val) > 8 {
|
if len(val) > 8 {
|
||||||
dd := val[0:2]
|
var dd = val[0:2]
|
||||||
mm := val[2:]
|
var mm = val[2:]
|
||||||
d, _ := strconv.ParseFloat(dd, 32)
|
var d, _ = strconv.ParseFloat(dd, 32)
|
||||||
m, _ := strconv.ParseFloat(mm, 32)
|
var m, _ = strconv.ParseFloat(mm, 32)
|
||||||
v := float32(d + (m / 60))
|
var v = float32(d + (m / 60))
|
||||||
if hemi == "S" {
|
if hemi == "S" {
|
||||||
v *= -1
|
v *= -1
|
||||||
}
|
}
|
||||||
@@ -148,7 +133,7 @@ func findLatitude(val, hemi string) float32 {
|
|||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// findLongitude returns the longitude from an NMEA sentence:
|
// findLatitude returns the longitude from an NMEA sentence:
|
||||||
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
|
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
|
||||||
func findLongitude(val, hemi string) float32 {
|
func findLongitude(val, hemi string) float32 {
|
||||||
if len(val) > 8 {
|
if len(val) > 8 {
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package lora
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
// Config holds the LoRa configuration parameters
|
|
||||||
type Config struct {
|
|
||||||
Freq uint32 // Frequency
|
|
||||||
Cr uint8 // Coding Rate
|
|
||||||
Sf uint8 // Spread Factor
|
|
||||||
Bw uint8 // Bandwidth
|
|
||||||
Ldr uint8 // Low Data Rate
|
|
||||||
Preamble uint16 // PreambleLength
|
|
||||||
SyncWord uint16 // Sync Word
|
|
||||||
HeaderType uint8 // Header : Implicit/explicit
|
|
||||||
Crc uint8 // CRC : Yes/No
|
|
||||||
Iq uint8 // iq : Standard/inverted
|
|
||||||
LoraTxPowerDBm int8 // Tx power in Dbm
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrUndefinedLoraConf = errors.New("Undefined Lora configuration")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SpreadingFactor5 = 0x05
|
|
||||||
SpreadingFactor6 = 0x06
|
|
||||||
SpreadingFactor7 = 0x07
|
|
||||||
SpreadingFactor8 = 0x08
|
|
||||||
SpreadingFactor9 = 0x09
|
|
||||||
SpreadingFactor10 = 0x0A
|
|
||||||
SpreadingFactor11 = 0x0B
|
|
||||||
SpreadingFactor12 = 0x0C
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
CodingRate4_5 = 0x01 // 7 0 LoRa coding rate: 4/5
|
|
||||||
CodingRate4_6 = 0x02 // 7 0 4/6
|
|
||||||
CodingRate4_7 = 0x03 // 7 0 4/7
|
|
||||||
CodingRate4_8 = 0x04 // 7 0 4/8
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
HeaderExplicit = 0x00 // 7 0 LoRa header mode: explicit
|
|
||||||
HeaderImplicit = 0x01 // 7 0 implicit
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
LowDataRateOptimizeOff = 0x00 // 7 0 LoRa low data rate optimization: disabled
|
|
||||||
LowDataRateOptimizeOn = 0x01 // 7 0 enabled
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
CRCOff = 0x00 // 7 0 LoRa CRC mode: disabled
|
|
||||||
CRCOn = 0x01 // 7 0 enabled
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
IQStandard = 0x00 // 7 0 LoRa IQ setup: standard
|
|
||||||
IQInverted = 0x01 // 7 0 inverted
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Bandwidth_7_8 = iota // 7.8 kHz
|
|
||||||
Bandwidth_10_4 // 10.4 kHz
|
|
||||||
Bandwidth_15_6 // 15.6 kHz
|
|
||||||
Bandwidth_20_8 // 20.8 kHz
|
|
||||||
Bandwidth_31_25 // 31.25 kHz
|
|
||||||
Bandwidth_41_7 // 41.7 kHz
|
|
||||||
Bandwidth_62_5 // 62.5 kHz
|
|
||||||
Bandwidth_125_0 // 125.0 kHz
|
|
||||||
Bandwidth_250_0 // 250.0 kHz
|
|
||||||
Bandwidth_500_0 // 500.0 kHz
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SyncPublic = iota
|
|
||||||
SyncPrivate
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
MHz_868_1 = 868100000
|
|
||||||
MHz_868_5 = 868500000
|
|
||||||
MHz_916_8 = 916800000
|
|
||||||
MHz_923_3 = 923300000
|
|
||||||
)
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package lora
|
|
||||||
|
|
||||||
type Radio interface {
|
|
||||||
Reset()
|
|
||||||
Tx(pkt []uint8, timeoutMs uint32) error
|
|
||||||
Rx(timeoutMs uint32) ([]uint8, error)
|
|
||||||
SetFrequency(freq uint32)
|
|
||||||
SetIqMode(mode uint8)
|
|
||||||
SetCodingRate(cr uint8)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package lora
|
|
||||||
|
|
||||||
const (
|
|
||||||
RadioEventRxDone = iota
|
|
||||||
RadioEventTxDone
|
|
||||||
RadioEventTimeout
|
|
||||||
RadioEventWatchdog
|
|
||||||
RadioEventCrcError
|
|
||||||
RadioEventUnhandled
|
|
||||||
)
|
|
||||||
|
|
||||||
// RadioEvent are used for communicating in the radio Event Channel
|
|
||||||
type RadioEvent struct {
|
|
||||||
EventType int
|
|
||||||
IRQStatus uint16
|
|
||||||
EventData []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRadioEvent() returns a new RadioEvent that can be used in the RadioChannel
|
|
||||||
func NewRadioEvent(eType int, irqStatus uint16, eData []byte) RadioEvent {
|
|
||||||
r := RadioEvent{EventType: eType, IRQStatus: irqStatus, EventData: eData}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
+1
-16
@@ -32,7 +32,7 @@ func (d Device) Connected() bool {
|
|||||||
|
|
||||||
// Configure sets up the device for communication.
|
// Configure sets up the device for communication.
|
||||||
func (d Device) Configure() error {
|
func (d Device) Configure() error {
|
||||||
return d.SetClockSource(CLOCK_INTERNAL)
|
return d.bus.WriteRegister(uint8(d.Address), PWR_MGMT_1, []uint8{0})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAcceleration reads the current acceleration from the device and returns
|
// ReadAcceleration reads the current acceleration from the device and returns
|
||||||
@@ -78,18 +78,3 @@ func (d Device) ReadRotation() (x int32, y int32, z int32) {
|
|||||||
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 2048 * 1000
|
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 2048 * 1000
|
||||||
return
|
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})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -98,29 +98,6 @@ const (
|
|||||||
I2C_PER3_DO = 0x66
|
I2C_PER3_DO = 0x66
|
||||||
I2C_MST_DELAY_CT = 0x67
|
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
|
SIGNAL_PATH_RES = 0x68 // Signal path reset
|
||||||
USER_CTRL = 0x6A // User control
|
USER_CTRL = 0x6A // User control
|
||||||
PWR_MGMT_1 = 0x6B // Power Management 1
|
PWR_MGMT_1 = 0x6B // Power Management 1
|
||||||
|
|||||||
+140
-123
@@ -5,6 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/textproto"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -25,17 +26,43 @@ func (c *Client) Do(req *Request) (*Response, error) {
|
|||||||
req.AddCookie(cookie)
|
req.AddCookie(cookie)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transport := c.Transport
|
||||||
|
if transport == nil {
|
||||||
|
transport = DefaultTransport
|
||||||
|
}
|
||||||
|
res, err := transport.RoundTrip(req)
|
||||||
|
|
||||||
|
if c.Jar != nil {
|
||||||
|
if rc := res.Cookies(); len(rc) > 0 {
|
||||||
|
c.Jar.SetCookies(req.URL, rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultTransport RoundTripper
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
DefaultTransport = &Transport{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
|
||||||
switch req.URL.Scheme {
|
switch req.URL.Scheme {
|
||||||
case "http":
|
case "http":
|
||||||
return c.doHTTP(req)
|
return t.doHTTP(req)
|
||||||
case "https":
|
case "https":
|
||||||
return c.doHTTPS(req)
|
return t.doHTTPS(req)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid schemer : %s", req.URL.Scheme)
|
return nil, fmt.Errorf("invalid schemer : %s", req.URL.Scheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doHTTP(req *Request) (*Response, error) {
|
func (t *Transport) doHTTP(req *Request) (*Response, error) {
|
||||||
// make TCP connection
|
// make TCP connection
|
||||||
ip := net.ParseIP(req.URL.Hostname())
|
ip := net.ParseIP(req.URL.Hostname())
|
||||||
port := 80
|
port := 80
|
||||||
@@ -106,10 +133,10 @@ func (c *Client) doHTTP(req *Request) (*Response, error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.doResp(conn, req)
|
return t.doResp(conn, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doHTTPS(req *Request) (*Response, error) {
|
func (t *Transport) doHTTPS(req *Request) (*Response, error) {
|
||||||
conn, err := tls.Dial("tcp", req.URL.Host, nil)
|
conn, err := tls.Dial("tcp", req.URL.Host, nil)
|
||||||
retry := 0
|
retry := 0
|
||||||
for ; err != nil; conn, err = tls.Dial("tcp", req.URL.Host, nil) {
|
for ; err != nil; conn, err = tls.Dial("tcp", req.URL.Host, nil) {
|
||||||
@@ -167,141 +194,131 @@ func (c *Client) doHTTPS(req *Request) (*Response, error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.doResp(conn, req)
|
return t.doResp(conn, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doResp(conn net.Conn, req *Request) (*Response, error) {
|
func (t *Transport) doResp(conn net.Conn, req *Request) (*Response, error) {
|
||||||
resp := &Response{
|
resp := &Response{
|
||||||
Header: map[string][]string{},
|
Header: map[string][]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header
|
br := bufio.NewReader(conn)
|
||||||
var scanner *bufio.Scanner
|
tp := textproto.NewReader(br)
|
||||||
cont := true
|
|
||||||
ofs := 0
|
|
||||||
remain := int64(0)
|
|
||||||
for cont {
|
|
||||||
for n, err := conn.Read(buf[ofs:]); n > 0; n, err = conn.Read(buf[ofs:]) {
|
|
||||||
if err != nil {
|
|
||||||
println("Read error: " + err.Error())
|
|
||||||
} else {
|
|
||||||
// Take care of the case where "\r\n\r\n" is on the boundary of a buffer
|
|
||||||
start := ofs
|
|
||||||
if start > 3 {
|
|
||||||
start -= 3
|
|
||||||
}
|
|
||||||
idx := bytes.Index(buf[start:ofs+n], []byte("\r\n\r\n"))
|
|
||||||
if idx == -1 {
|
|
||||||
ofs += n
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
idx += start + 4
|
|
||||||
|
|
||||||
scanner = bufio.NewScanner(bytes.NewReader(buf[0 : ofs+n]))
|
for {
|
||||||
if resp.Status == "" && scanner.Scan() {
|
line, err := tp.ReadLine()
|
||||||
status := strings.SplitN(scanner.Text(), " ", 2)
|
if err != nil {
|
||||||
if len(status) != 2 {
|
if err == io.ErrNoProgress {
|
||||||
conn.Close()
|
// default: no timeout
|
||||||
return nil, fmt.Errorf("invalid status : %q", scanner.Text())
|
|
||||||
}
|
|
||||||
resp.Proto = status[0]
|
|
||||||
fmt.Sscanf(status[0], "HTTP/%d.%d", &resp.ProtoMajor, &resp.ProtoMinor)
|
|
||||||
|
|
||||||
resp.Status = status[1]
|
|
||||||
fmt.Sscanf(status[1], "%d", &resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
text := scanner.Text()
|
|
||||||
if text == "" {
|
|
||||||
// end of header
|
|
||||||
if idx < n+ofs {
|
|
||||||
ofs = ofs + n - idx
|
|
||||||
for i := 0; i < ofs; i++ {
|
|
||||||
buf[i] = buf[i+idx]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ofs = 0
|
|
||||||
}
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
header := strings.SplitN(text, ": ", 2)
|
|
||||||
if len(header) != 2 {
|
|
||||||
conn.Close()
|
|
||||||
return nil, fmt.Errorf("invalid header : %q", text)
|
|
||||||
}
|
|
||||||
if resp.Header.Get(header[0]) == "" {
|
|
||||||
resp.Header.Set(header[0], header[1])
|
|
||||||
} else {
|
|
||||||
resp.Header.Add(header[0], header[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.ToLower(header[0]) == "content-length" {
|
|
||||||
resp.ContentLength, err = strconv.ParseInt(header[1], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
remain = resp.ContentLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cont = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Body
|
|
||||||
remain -= int64(ofs)
|
|
||||||
if remain <= 0 {
|
|
||||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:ofs]))
|
|
||||||
if c.Jar != nil {
|
|
||||||
if rc := resp.Cookies(); len(rc) > 0 {
|
|
||||||
c.Jar.SetCookies(req.URL, rc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resp, conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
cont = true
|
|
||||||
lastRequestTime := time.Now()
|
|
||||||
for cont {
|
|
||||||
for {
|
|
||||||
end := ofs + 0x400
|
|
||||||
if len(buf) < end {
|
|
||||||
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", end)
|
|
||||||
}
|
|
||||||
n, err := conn.Read(buf[ofs : ofs+0x400])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status := strings.SplitN(line, " ", 2)
|
||||||
|
if len(status) != 2 {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("invalid status : %q", line)
|
||||||
|
}
|
||||||
|
resp.Proto = status[0]
|
||||||
|
fmt.Sscanf(status[0], "HTTP/%d.%d", &resp.ProtoMajor, &resp.ProtoMinor)
|
||||||
|
|
||||||
|
resp.Status = status[1]
|
||||||
|
fmt.Sscanf(status[1], "%d", &resp.StatusCode)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := tp.ReadMIMEHeader()
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for k, v := range m {
|
||||||
|
//fmt.Printf("%s: %s\n", k, v)
|
||||||
|
|
||||||
|
if strings.ToLower(k) == "content-length" {
|
||||||
|
resp.ContentLength, err = strconv.ParseInt(v[0], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
|
||||||
ofs += n
|
|
||||||
remain -= int64(n)
|
|
||||||
if remain <= 0 {
|
|
||||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:ofs]))
|
|
||||||
cont = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 1000 {
|
|
||||||
conn.Close()
|
|
||||||
return nil, fmt.Errorf("time out")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.Header.Get(k) == "" {
|
||||||
|
resp.Header.Set(k, v[0])
|
||||||
|
v = v[1:]
|
||||||
|
}
|
||||||
|
for _, vv := range v {
|
||||||
|
resp.Header.Add(k, vv)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Jar != nil {
|
if resp.Header.Get("Transfer-Encoding") == "chunked" {
|
||||||
if rc := resp.Cookies(); len(rc) > 0 {
|
// chunked
|
||||||
c.Jar.SetCookies(req.URL, rc)
|
cur := 0
|
||||||
|
end := 0
|
||||||
|
for {
|
||||||
|
length := 0
|
||||||
|
if len(buf) < cur+6 {
|
||||||
|
// This is not a very accurate check, but in many cases it should be fine.
|
||||||
|
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", cur+6)
|
||||||
|
}
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
buf[cur+i], err = br.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
length = i + 1
|
||||||
|
if i > 1 && buf[cur+i-1] == '\r' && buf[cur+i] == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//fmt.Printf("cur:%d length:%d\n", cur, length)
|
||||||
|
|
||||||
|
size, err := strconv.ParseInt(string(buf[cur:cur+length-2]), 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//cur += length
|
||||||
|
//fmt.Printf("cur:%d length:%d size:%d\n", cur, length, size)
|
||||||
|
|
||||||
|
end = cur + int(size) + 2 // size + 2 (\r\n)
|
||||||
|
if len(buf) < end {
|
||||||
|
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", end)
|
||||||
|
}
|
||||||
|
for i := 0; i < int(size)+2; i++ {
|
||||||
|
buf[cur+i], err = br.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur += int(size)
|
||||||
|
|
||||||
|
if size == 0 {
|
||||||
|
end = end - 2
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
//fmt.Printf("%q\n", buf[:end])
|
||||||
|
resp.Body = io.NopCloser(bytes.NewReader(buf[:end]))
|
||||||
|
} else {
|
||||||
|
end := int(resp.ContentLength)
|
||||||
|
if len(buf) < end {
|
||||||
|
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", end)
|
||||||
|
}
|
||||||
|
for i := 0; i < end; i++ {
|
||||||
|
buf[i], err = br.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.Body = io.NopCloser(bytes.NewReader(buf[:end]))
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, conn.Close()
|
return resp, conn.Close()
|
||||||
|
|||||||
+1
-4
@@ -357,10 +357,7 @@ func ParseIP(s string) IP {
|
|||||||
|
|
||||||
// String returns the string form of the IP address ip.
|
// String returns the string form of the IP address ip.
|
||||||
func (ip IP) String() string {
|
func (ip IP) String() string {
|
||||||
if len(ip) < 4 {
|
return string(ip)
|
||||||
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.
|
// Conn is a generic stream-oriented network connection.
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
@@ -71,10 +71,13 @@ func (d *Driver) ConnectTCPSocket(addr, port string) error {
|
|||||||
name[6] = byte(ipaddr[2])
|
name[6] = byte(ipaddr[2])
|
||||||
name[7] = byte(ipaddr[3])
|
name[7] = byte(ipaddr[3])
|
||||||
|
|
||||||
_, err = d.Rpc_lwip_connect(socket, name, uint32(len(name)))
|
result, err := d.Rpc_lwip_connect(socket, name, uint32(len(name)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if result == -1 {
|
||||||
|
return fmt.Errorf("failed to connect to %d.%d.%d.%d port %s", addr[0], addr[1], addr[2], addr[3], port)
|
||||||
|
}
|
||||||
|
|
||||||
readset := []byte{}
|
readset := []byte{}
|
||||||
writeset := []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
writeset := []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||||
@@ -341,8 +344,11 @@ func (d *Driver) ReadSocket(b []byte) (n int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if nn < 0 {
|
if nn == -76 {
|
||||||
return 0, fmt.Errorf("error %d", n)
|
// no data
|
||||||
|
return 0, nil
|
||||||
|
} else if nn < 0 {
|
||||||
|
return 0, fmt.Errorf("error %d", nn)
|
||||||
} else if nn == 0 || nn == -30848 {
|
} else if nn == 0 || nn == -30848 {
|
||||||
return 0, d.DisconnectSocket()
|
return 0, d.DisconnectSocket()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package sh1106
|
|
||||||
|
|
||||||
// Registers
|
|
||||||
const (
|
|
||||||
Address = 0x3C
|
|
||||||
|
|
||||||
SETCONTRAST = 0x81
|
|
||||||
DISPLAYALLON_RESUME = 0xA4
|
|
||||||
DISPLAYALLON = 0xA5
|
|
||||||
NORMALDISPLAY = 0xA6
|
|
||||||
INVERTDISPLAY = 0xA7
|
|
||||||
DISPLAYOFF = 0xAE
|
|
||||||
DISPLAYON = 0xAF
|
|
||||||
SETDISPLAYOFFSET = 0xD3
|
|
||||||
SETCOMPINS = 0xDA
|
|
||||||
SETVCOMDETECT = 0xDB
|
|
||||||
SETDISPLAYCLOCKDIV = 0xD5
|
|
||||||
SETPRECHARGE = 0xD9
|
|
||||||
SETMULTIPLEX = 0xA8
|
|
||||||
SETLOWCOLUMN = 0x00
|
|
||||||
SETHIGHCOLUMN = 0x10
|
|
||||||
SETSTARTLINE = 0x40
|
|
||||||
MEMORYMODE = 0x20
|
|
||||||
COLUMNADDR = 0x21
|
|
||||||
PAGEADDR = 0x22
|
|
||||||
COMSCANINC = 0xC0
|
|
||||||
COMSCANDEC = 0xC8
|
|
||||||
SEGREMAP = 0xA0
|
|
||||||
CHARGEPUMP = 0x8D
|
|
||||||
ACTIVATE_SCROLL = 0x2F
|
|
||||||
DEACTIVATE_SCROLL = 0x2E
|
|
||||||
SET_VERTICAL_SCROLL_AREA = 0xA3
|
|
||||||
RIGHT_HORIZONTAL_SCROLL = 0x26
|
|
||||||
LEFT_HORIZONTAL_SCROLL = 0x27
|
|
||||||
VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29
|
|
||||||
VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A
|
|
||||||
|
|
||||||
EXTERNALVCC VccMode = 0x1
|
|
||||||
SWITCHCAPVCC VccMode = 0x2
|
|
||||||
)
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
// Package sh1106 implements a driver for the SH1106 display controller
|
|
||||||
//
|
|
||||||
// Copied from https://github.com/toyo/tinygo-sh1106 (under BSD 3-clause license)
|
|
||||||
package sh1106 // import "tinygo.org/x/drivers/sh1106"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"image/color"
|
|
||||||
"machine"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Device wraps an SPI connection.
|
|
||||||
type Device struct {
|
|
||||||
bus Buser
|
|
||||||
buffer []byte
|
|
||||||
cmdbuf [1]byte
|
|
||||||
width int16
|
|
||||||
height int16
|
|
||||||
bufferSize int16
|
|
||||||
vccState VccMode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is the configuration for the display
|
|
||||||
type Config struct {
|
|
||||||
Width int16
|
|
||||||
Height int16
|
|
||||||
VccState VccMode
|
|
||||||
Address uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type I2CBus struct {
|
|
||||||
wire drivers.I2C
|
|
||||||
Address uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type SPIBus struct {
|
|
||||||
wire drivers.SPI
|
|
||||||
dcPin machine.Pin
|
|
||||||
resetPin machine.Pin
|
|
||||||
csPin machine.Pin
|
|
||||||
}
|
|
||||||
|
|
||||||
type Buser interface {
|
|
||||||
configure()
|
|
||||||
tx(data []byte, isCommand bool)
|
|
||||||
setAddress(address uint16)
|
|
||||||
}
|
|
||||||
|
|
||||||
type VccMode uint8
|
|
||||||
|
|
||||||
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
|
|
||||||
func NewI2C(bus drivers.I2C) Device {
|
|
||||||
return Device{
|
|
||||||
bus: &I2CBus{
|
|
||||||
wire: bus,
|
|
||||||
Address: Address,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
|
|
||||||
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
|
|
||||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
return Device{
|
|
||||||
bus: &SPIBus{
|
|
||||||
wire: bus,
|
|
||||||
dcPin: dcPin,
|
|
||||||
resetPin: resetPin,
|
|
||||||
csPin: csPin,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure initializes the display with default configuration
|
|
||||||
func (d *Device) Configure(cfg Config) {
|
|
||||||
if cfg.Width != 0 {
|
|
||||||
d.width = cfg.Width
|
|
||||||
} else {
|
|
||||||
d.width = 128
|
|
||||||
}
|
|
||||||
if cfg.Height != 0 {
|
|
||||||
d.height = cfg.Height
|
|
||||||
} else {
|
|
||||||
d.height = 64
|
|
||||||
}
|
|
||||||
if cfg.Address != 0 {
|
|
||||||
d.bus.setAddress(cfg.Address)
|
|
||||||
}
|
|
||||||
if cfg.VccState != 0 {
|
|
||||||
d.vccState = cfg.VccState
|
|
||||||
} else {
|
|
||||||
d.vccState = SWITCHCAPVCC
|
|
||||||
}
|
|
||||||
d.bufferSize = d.width * d.height / 8
|
|
||||||
d.buffer = make([]byte, d.bufferSize)
|
|
||||||
|
|
||||||
d.bus.configure()
|
|
||||||
|
|
||||||
// busyWaitDelay(100 * time.Nanosecond)
|
|
||||||
time.Sleep(100 * time.Nanosecond)
|
|
||||||
d.Command(DISPLAYOFF)
|
|
||||||
d.Command(SETDISPLAYCLOCKDIV)
|
|
||||||
d.Command(0x80)
|
|
||||||
d.Command(SETMULTIPLEX)
|
|
||||||
d.Command(uint8(d.height - 1))
|
|
||||||
d.Command(SETDISPLAYOFFSET)
|
|
||||||
d.Command(0x0)
|
|
||||||
d.Command(SETSTARTLINE | 0x0)
|
|
||||||
d.Command(CHARGEPUMP)
|
|
||||||
if d.vccState == EXTERNALVCC {
|
|
||||||
d.Command(0x10)
|
|
||||||
} else {
|
|
||||||
d.Command(0x14)
|
|
||||||
}
|
|
||||||
d.Command(MEMORYMODE)
|
|
||||||
d.Command(0x00)
|
|
||||||
d.Command(SEGREMAP | 0x1)
|
|
||||||
d.Command(COMSCANDEC)
|
|
||||||
|
|
||||||
if (d.width == 128 && d.height == 64) || (d.width == 64 && d.height == 48) { // 128x64 or 64x48
|
|
||||||
d.Command(SETCOMPINS)
|
|
||||||
d.Command(0x12)
|
|
||||||
d.Command(SETCONTRAST)
|
|
||||||
if d.vccState == EXTERNALVCC {
|
|
||||||
d.Command(0x9F)
|
|
||||||
} else {
|
|
||||||
d.Command(0xCF)
|
|
||||||
}
|
|
||||||
} else if d.width == 128 && d.height == 32 { // 128x32
|
|
||||||
d.Command(SETCOMPINS)
|
|
||||||
d.Command(0x02)
|
|
||||||
d.Command(SETCONTRAST)
|
|
||||||
d.Command(0x8F)
|
|
||||||
} else if d.width == 96 && d.height == 16 { // 96x16
|
|
||||||
d.Command(SETCOMPINS)
|
|
||||||
d.Command(0x2)
|
|
||||||
d.Command(SETCONTRAST)
|
|
||||||
if d.vccState == EXTERNALVCC {
|
|
||||||
d.Command(0x10)
|
|
||||||
} else {
|
|
||||||
d.Command(0xAF)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// fail silently, it might work
|
|
||||||
println("there's no configuration for this display's size")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Command(SETPRECHARGE)
|
|
||||||
if d.vccState == EXTERNALVCC {
|
|
||||||
d.Command(0x22)
|
|
||||||
} else {
|
|
||||||
d.Command(0xF1)
|
|
||||||
}
|
|
||||||
d.Command(SETVCOMDETECT)
|
|
||||||
d.Command(0x40)
|
|
||||||
d.Command(DISPLAYALLON_RESUME)
|
|
||||||
d.Command(NORMALDISPLAY)
|
|
||||||
d.Command(DEACTIVATE_SCROLL)
|
|
||||||
d.Command(DISPLAYON)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearBuffer clears the image buffer
|
|
||||||
func (d *Device) ClearBuffer() {
|
|
||||||
for i := int16(0); i < d.bufferSize; i++ {
|
|
||||||
d.buffer[i] = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearDisplay clears the image buffer and clear the display
|
|
||||||
func (d *Device) ClearDisplay() {
|
|
||||||
d.ClearBuffer()
|
|
||||||
d.Display()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display sends the whole buffer to the screen
|
|
||||||
func (d *Device) Display() error {
|
|
||||||
// In the 128x64 (SPI) screen resetting to 0x0 after 128 times corrupt the buffer
|
|
||||||
// Since we're printing the whole buffer, avoid resetting it
|
|
||||||
if d.width != 128 || d.height != 64 {
|
|
||||||
d.Command(COLUMNADDR)
|
|
||||||
d.Command(0)
|
|
||||||
d.Command(uint8(d.width - 1))
|
|
||||||
d.Command(PAGEADDR)
|
|
||||||
d.Command(0)
|
|
||||||
d.Command(uint8(d.height/8) - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for pg := uint8(0); pg < uint8(d.height/8); pg++ {
|
|
||||||
d.Command(0xB0 | (pg & 0x07)) // SET_PAGE_ADDR
|
|
||||||
d.Command(SETLOWCOLUMN | 2)
|
|
||||||
d.Command(SETHIGHCOLUMN | 0)
|
|
||||||
d.Tx(d.buffer[uint16(pg)*0x80:uint16(pg+1)*0x80], false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPixel enables or disables a pixel in the buffer
|
|
||||||
// color.RGBA{0, 0, 0, 255} is consider transparent, anything else
|
|
||||||
// with enable a pixel on the screen
|
|
||||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
|
||||||
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
byteIndex := x + (y/8)*d.width
|
|
||||||
if c.R != 0 || c.G != 0 || c.B != 0 {
|
|
||||||
d.buffer[byteIndex] |= 1 << uint8(y%8)
|
|
||||||
} else {
|
|
||||||
d.buffer[byteIndex] &^= 1 << uint8(y%8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPixel returns if the specified pixel is on (true) or off (false)
|
|
||||||
func (d *Device) GetPixel(x int16, y int16) bool {
|
|
||||||
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
byteIndex := x + (y/8)*d.width
|
|
||||||
return (d.buffer[byteIndex] >> uint8(y%8) & 0x1) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBuffer changes the whole buffer at once
|
|
||||||
func (d *Device) SetBuffer(buffer []byte) error {
|
|
||||||
if int16(len(buffer)) != d.bufferSize {
|
|
||||||
//return ErrBuffer
|
|
||||||
return errors.New("wrong size buffer")
|
|
||||||
}
|
|
||||||
for i := int16(0); i < d.bufferSize; i++ {
|
|
||||||
d.buffer[i] = buffer[i]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Device) SetScroll(line int16) {
|
|
||||||
d.Command(SETSTARTLINE + uint8(line&0b111111))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command sends a command to the display
|
|
||||||
func (d *Device) Command(command uint8) {
|
|
||||||
d.cmdbuf[0] = command
|
|
||||||
d.bus.tx(d.cmdbuf[:], true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setAddress sets the address to the I2C bus
|
|
||||||
func (b *I2CBus) setAddress(address uint16) {
|
|
||||||
b.Address = address
|
|
||||||
}
|
|
||||||
|
|
||||||
// setAddress does nothing, but it's required to avoid reflection
|
|
||||||
func (b *SPIBus) setAddress(address uint16) {
|
|
||||||
// do nothing
|
|
||||||
println("trying to Configure an address on a SPI device")
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure does nothing, but it's required to avoid reflection
|
|
||||||
func (b *I2CBus) configure() {}
|
|
||||||
|
|
||||||
// configure configures some pins with the SPI bus
|
|
||||||
func (b *SPIBus) configure() {
|
|
||||||
b.csPin.Low()
|
|
||||||
b.dcPin.Low()
|
|
||||||
b.resetPin.Low()
|
|
||||||
|
|
||||||
b.resetPin.High()
|
|
||||||
// busyWaitDelay(time.Millisecond)
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
b.resetPin.Low()
|
|
||||||
// busyWaitDelay(10 * time.Millisecond)
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
b.resetPin.High()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tx sends data to the display
|
|
||||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
|
||||||
d.bus.tx(data, isCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tx sends data to the display (I2CBus implementation)
|
|
||||||
func (b *I2CBus) tx(data []byte, isCommand bool) {
|
|
||||||
if isCommand {
|
|
||||||
b.wire.WriteRegister(uint8(b.Address), 0x00, data)
|
|
||||||
} else {
|
|
||||||
b.wire.WriteRegister(uint8(b.Address), 0x40, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tx sends data to the display (SPIBus implementation)
|
|
||||||
func (b *SPIBus) tx(data []byte, isCommand bool) {
|
|
||||||
if isCommand {
|
|
||||||
b.csPin.High()
|
|
||||||
riseTimeDelay()
|
|
||||||
b.dcPin.Low()
|
|
||||||
b.csPin.Low()
|
|
||||||
|
|
||||||
b.wire.Tx(data, nil)
|
|
||||||
b.csPin.High()
|
|
||||||
} else {
|
|
||||||
b.csPin.High()
|
|
||||||
riseTimeDelay()
|
|
||||||
b.dcPin.High()
|
|
||||||
b.csPin.Low()
|
|
||||||
|
|
||||||
b.wire.Tx(data, nil)
|
|
||||||
b.csPin.High()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the current size of the display.
|
|
||||||
func (d *Device) Size() (w, h int16) {
|
|
||||||
return d.width, d.height
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: is this really necessary? seems to work fine without this on macropad-rp2040 at least
|
|
||||||
func riseTimeDelay() {
|
|
||||||
busyWaitDelay(1 * time.Microsecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func busyWaitDelay(duration time.Duration) {
|
|
||||||
for start := time.Now(); time.Since(start) < duration; {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,56 +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 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
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
//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
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
//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
|
|
||||||
}
|
|
||||||
+57
-36
@@ -206,42 +206,48 @@ const (
|
|||||||
SX126X_PA_RAMP_3400U = 0x07 // 7 0 3400 us
|
SX126X_PA_RAMP_3400U = 0x07 // 7 0 3400 us
|
||||||
|
|
||||||
//SX126X_CMD_SET_MODULATION_PARAMS
|
//SX126X_CMD_SET_MODULATION_PARAMS
|
||||||
SX126X_GFSK_FILTER_NONE = 0x00 // 7 0 GFSK filter: none
|
SX126X_GFSK_FILTER_NONE = 0x00 // 7 0 GFSK filter: none
|
||||||
SX126X_GFSK_FILTER_GAUSS_0_3 = 0x08 // 7 0 Gaussian, BT = 0.3
|
SX126X_GFSK_FILTER_GAUSS_0_3 = 0x08 // 7 0 Gaussian, BT = 0.3
|
||||||
SX126X_GFSK_FILTER_GAUSS_0_5 = 0x09 // 7 0 Gaussian, BT = 0.5
|
SX126X_GFSK_FILTER_GAUSS_0_5 = 0x09 // 7 0 Gaussian, BT = 0.5
|
||||||
SX126X_GFSK_FILTER_GAUSS_0_7 = 0x0A // 7 0 Gaussian, BT = 0.7
|
SX126X_GFSK_FILTER_GAUSS_0_7 = 0x0A // 7 0 Gaussian, BT = 0.7
|
||||||
SX126X_GFSK_FILTER_GAUSS_1 = 0x0B // 7 0 Gaussian, BT = 1
|
SX126X_GFSK_FILTER_GAUSS_1 = 0x0B // 7 0 Gaussian, BT = 1
|
||||||
SX126X_GFSK_RX_BW_4_8 = 0x1F // 7 0 GFSK Rx bandwidth: 4.8 kHz
|
SX126X_GFSK_RX_BW_4_8 = 0x1F // 7 0 GFSK Rx bandwidth: 4.8 kHz
|
||||||
SX126X_GFSK_RX_BW_5_8 = 0x17 // 7 0 5.8 kHz
|
SX126X_GFSK_RX_BW_5_8 = 0x17 // 7 0 5.8 kHz
|
||||||
SX126X_GFSK_RX_BW_7_3 = 0x0F // 7 0 7.3 kHz
|
SX126X_GFSK_RX_BW_7_3 = 0x0F // 7 0 7.3 kHz
|
||||||
SX126X_GFSK_RX_BW_9_7 = 0x1E // 7 0 9.7 kHz
|
SX126X_GFSK_RX_BW_9_7 = 0x1E // 7 0 9.7 kHz
|
||||||
SX126X_GFSK_RX_BW_11_7 = 0x16 // 7 0 11.7 kHz
|
SX126X_GFSK_RX_BW_11_7 = 0x16 // 7 0 11.7 kHz
|
||||||
SX126X_GFSK_RX_BW_14_6 = 0x0E // 7 0 14.6 kHz
|
SX126X_GFSK_RX_BW_14_6 = 0x0E // 7 0 14.6 kHz
|
||||||
SX126X_GFSK_RX_BW_19_5 = 0x1D // 7 0 19.5 kHz
|
SX126X_GFSK_RX_BW_19_5 = 0x1D // 7 0 19.5 kHz
|
||||||
SX126X_GFSK_RX_BW_23_4 = 0x15 // 7 0 23.4 kHz
|
SX126X_GFSK_RX_BW_23_4 = 0x15 // 7 0 23.4 kHz
|
||||||
SX126X_GFSK_RX_BW_29_3 = 0x0D // 7 0 29.3 kHz
|
SX126X_GFSK_RX_BW_29_3 = 0x0D // 7 0 29.3 kHz
|
||||||
SX126X_GFSK_RX_BW_39_0 = 0x1C // 7 0 39.0 kHz
|
SX126X_GFSK_RX_BW_39_0 = 0x1C // 7 0 39.0 kHz
|
||||||
SX126X_GFSK_RX_BW_46_9 = 0x14 // 7 0 46.9 kHz
|
SX126X_GFSK_RX_BW_46_9 = 0x14 // 7 0 46.9 kHz
|
||||||
SX126X_GFSK_RX_BW_58_6 = 0x0C // 7 0 58.6 kHz
|
SX126X_GFSK_RX_BW_58_6 = 0x0C // 7 0 58.6 kHz
|
||||||
SX126X_GFSK_RX_BW_78_2 = 0x1B // 7 0 78.2 kHz
|
SX126X_GFSK_RX_BW_78_2 = 0x1B // 7 0 78.2 kHz
|
||||||
SX126X_GFSK_RX_BW_93_8 = 0x13 // 7 0 93.8 kHz
|
SX126X_GFSK_RX_BW_93_8 = 0x13 // 7 0 93.8 kHz
|
||||||
SX126X_GFSK_RX_BW_117_3 = 0x0B // 7 0 117.3 kHz
|
SX126X_GFSK_RX_BW_117_3 = 0x0B // 7 0 117.3 kHz
|
||||||
SX126X_GFSK_RX_BW_156_2 = 0x1A // 7 0 156.2 kHz
|
SX126X_GFSK_RX_BW_156_2 = 0x1A // 7 0 156.2 kHz
|
||||||
SX126X_GFSK_RX_BW_187_2 = 0x12 // 7 0 187.2 kHz
|
SX126X_GFSK_RX_BW_187_2 = 0x12 // 7 0 187.2 kHz
|
||||||
SX126X_GFSK_RX_BW_234_3 = 0x0A // 7 0 234.3 kHz
|
SX126X_GFSK_RX_BW_234_3 = 0x0A // 7 0 234.3 kHz
|
||||||
SX126X_GFSK_RX_BW_312_0 = 0x19 // 7 0 312.0 kHz
|
SX126X_GFSK_RX_BW_312_0 = 0x19 // 7 0 312.0 kHz
|
||||||
SX126X_GFSK_RX_BW_373_6 = 0x11 // 7 0 373.6 kHz
|
SX126X_GFSK_RX_BW_373_6 = 0x11 // 7 0 373.6 kHz
|
||||||
SX126X_GFSK_RX_BW_467_0 = 0x09 // 7 0 467.0 kHz
|
SX126X_GFSK_RX_BW_467_0 = 0x09 // 7 0 467.0 kHz
|
||||||
SX126X_LORA_BW_7_8 = 0x00 // 7 0 LoRa bandwidth: 7.8 kHz
|
SX126X_LORA_BW_7_8 = 0x00 // 7 0 LoRa bandwidth: 7.8 kHz
|
||||||
SX126X_LORA_BW_10_4 = 0x08 // 7 0 10.4 kHz
|
SX126X_LORA_BW_10_4 = 0x08 // 7 0 10.4 kHz
|
||||||
SX126X_LORA_BW_15_6 = 0x01 // 7 0 15.6 kHz
|
SX126X_LORA_BW_15_6 = 0x01 // 7 0 15.6 kHz
|
||||||
SX126X_LORA_BW_20_8 = 0x09 // 7 0 20.8 kHz
|
SX126X_LORA_BW_20_8 = 0x09 // 7 0 20.8 kHz
|
||||||
SX126X_LORA_BW_31_25 = 0x02 // 7 0 31.25 kHz
|
SX126X_LORA_BW_31_25 = 0x02 // 7 0 31.25 kHz
|
||||||
SX126X_LORA_BW_41_7 = 0x0A // 7 0 41.7 kHz
|
SX126X_LORA_BW_41_7 = 0x0A // 7 0 41.7 kHz
|
||||||
SX126X_LORA_BW_62_5 = 0x03 // 7 0 62.5 kHz
|
SX126X_LORA_BW_62_5 = 0x03 // 7 0 62.5 kHz
|
||||||
SX126X_LORA_BW_125_0 = 0x04 // 7 0 125.0 kHz
|
SX126X_LORA_BW_125_0 = 0x04 // 7 0 125.0 kHz
|
||||||
SX126X_LORA_BW_250_0 = 0x05 // 7 0 250.0 kHz
|
SX126X_LORA_BW_250_0 = 0x05 // 7 0 250.0 kHz
|
||||||
SX126X_LORA_BW_500_0 = 0x06 // 7 0 500.0 kHz
|
SX126X_LORA_BW_500_0 = 0x06 // 7 0 500.0 kHz
|
||||||
|
SX126X_LORA_CR_4_5 = 0x01 // 7 0 LoRa coding rate: 4/5
|
||||||
|
SX126X_LORA_CR_4_6 = 0x02 // 7 0 4/6
|
||||||
|
SX126X_LORA_CR_4_7 = 0x03 // 7 0 4/7
|
||||||
|
SX126X_LORA_CR_4_8 = 0x04 // 7 0 4/8
|
||||||
|
SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF = 0x00 // 7 0 LoRa low data rate optimization: disabled
|
||||||
|
SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON = 0x01 // 7 0 enabled
|
||||||
|
|
||||||
//SX126X_CMD_SET_PACKET_PARAMS
|
//SX126X_CMD_SET_PACKET_PARAMS
|
||||||
SX126X_GFSK_PREAMBLE_DETECT_OFF = 0x00 // 7 0 GFSK minimum preamble length before reception starts: detector disabled
|
SX126X_GFSK_PREAMBLE_DETECT_OFF = 0x00 // 7 0 GFSK minimum preamble length before reception starts: detector disabled
|
||||||
@@ -261,6 +267,12 @@ const (
|
|||||||
SX126X_GFSK_CRC_2_BYTE_INV = 0x06 // 7 0 2 byte, inverted
|
SX126X_GFSK_CRC_2_BYTE_INV = 0x06 // 7 0 2 byte, inverted
|
||||||
SX126X_GFSK_WHITENING_OFF = 0x00 // 7 0 GFSK data whitening: disabled
|
SX126X_GFSK_WHITENING_OFF = 0x00 // 7 0 GFSK data whitening: disabled
|
||||||
SX126X_GFSK_WHITENING_ON = 0x01 // 7 0 enabled
|
SX126X_GFSK_WHITENING_ON = 0x01 // 7 0 enabled
|
||||||
|
SX126X_LORA_HEADER_EXPLICIT = 0x00 // 7 0 LoRa header mode: explicit
|
||||||
|
SX126X_LORA_HEADER_IMPLICIT = 0x01 // 7 0 implicit
|
||||||
|
SX126X_LORA_CRC_OFF = 0x00 // 7 0 LoRa CRC mode: disabled
|
||||||
|
SX126X_LORA_CRC_ON = 0x01 // 7 0 enabled
|
||||||
|
SX126X_LORA_IQ_STANDARD = 0x00 // 7 0 LoRa IQ setup: standard
|
||||||
|
SX126X_LORA_IQ_INVERTED = 0x01 // 7 0 inverted
|
||||||
|
|
||||||
//SX126X_CMD_SET_CAD_PARAMS
|
//SX126X_CMD_SET_CAD_PARAMS
|
||||||
SX126X_CAD_ON_1_SYMB = 0x00 // 7 0 number of symbols used for CAD: 1
|
SX126X_CAD_ON_1_SYMB = 0x00 // 7 0 number of symbols used for CAD: 1
|
||||||
@@ -311,4 +323,13 @@ const (
|
|||||||
|
|
||||||
SX126X_LORA_MAC_PUBLIC_SYNCWORD = 0x3444
|
SX126X_LORA_MAC_PUBLIC_SYNCWORD = 0x3444
|
||||||
SX126X_LORA_MAC_PRIVATE_SYNCWORD = 0x1424
|
SX126X_LORA_MAC_PRIVATE_SYNCWORD = 0x1424
|
||||||
|
|
||||||
|
SX126X_LORA_SF5 = 0x05
|
||||||
|
SX126X_LORA_SF6 = 0x06
|
||||||
|
SX126X_LORA_SF7 = 0x07
|
||||||
|
SX126X_LORA_SF8 = 0x08
|
||||||
|
SX126X_LORA_SF9 = 0x09
|
||||||
|
SX126X_LORA_SF10 = 0x0A
|
||||||
|
SX126X_LORA_SF11 = 0x0B
|
||||||
|
SX126X_LORA_SF12 = 0x0C
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,28 +4,39 @@ package sx126x
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"device/stm32"
|
"device/stm32"
|
||||||
|
"errors"
|
||||||
"runtime/interrupt"
|
"machine"
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// STM32RadioControl helps implement the RadioController interface
|
// New creates a new SX126x connection.
|
||||||
type STM32RadioControl struct {
|
func New(spi drivers.SPI) *Device {
|
||||||
irqHandler func()
|
c := make(chan 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNss sets the NSS line aka chip select for SPI.
|
// SpiSetNss Sets the NSS line
|
||||||
func (rc *STM32RadioControl) SetNss(state bool) error {
|
func (d *Device) SpiSetNss(state bool) {
|
||||||
if state {
|
if state {
|
||||||
stm32.PWR.SUBGHZSPICR.SetBits(stm32.PWR_SUBGHZSPICR_NSS)
|
stm32.PWR.SUBGHZSPICR.SetBits(stm32.PWR_SUBGHZSPICR_NSS)
|
||||||
} else {
|
} else {
|
||||||
stm32.PWR.SUBGHZSPICR.ClearBits(stm32.PWR_SUBGHZSPICR_NSS)
|
stm32.PWR.SUBGHZSPICR.ClearBits(stm32.PWR_SUBGHZSPICR_NSS)
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitWhileBusy wait until the radio is no longer busy
|
// WaitBusy sleep until all busy flags clears
|
||||||
func (rc *STM32RadioControl) WaitWhileBusy() error {
|
func (d *Device) WaitBusy() error {
|
||||||
count := 100
|
count := 100
|
||||||
var rfbusyms, rfbusys bool
|
var rfbusyms, rfbusys bool
|
||||||
for count > 0 {
|
for count > 0 {
|
||||||
@@ -37,11 +48,12 @@ func (rc *STM32RadioControl) WaitWhileBusy() error {
|
|||||||
}
|
}
|
||||||
count--
|
count--
|
||||||
}
|
}
|
||||||
return errWaitWhileBusyTimeout
|
return errors.New("WaitBusy Timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
// init() configures whatever needed for sx126x radio control
|
// SubGhzInit() configures internal SX1262's SPI bus.
|
||||||
func init() {
|
func (d *Device) SubGhzInit() {
|
||||||
|
|
||||||
// Enable APB3 Periph clock and delay
|
// Enable APB3 Periph clock and delay
|
||||||
stm32.RCC.APB3ENR.SetBits(stm32.RCC_APB3ENR_SUBGHZSPIEN)
|
stm32.RCC.APB3ENR.SetBits(stm32.RCC_APB3ENR_SUBGHZSPIEN)
|
||||||
_ = stm32.RCC.APB3ENR.Get()
|
_ = stm32.RCC.APB3ENR.Get()
|
||||||
@@ -68,18 +80,5 @@ func init() {
|
|||||||
stm32.SPI3.CR1.Set(stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI | (0b010 << 3) | stm32.SPI_CR1_SSM)
|
stm32.SPI3.CR1.Set(stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI | (0b010 << 3) | stm32.SPI_CR1_SSM)
|
||||||
stm32.SPI3.CR2.Set(stm32.SPI_CR2_FRXTH | (0b111 << 8))
|
stm32.SPI3.CR2.Set(stm32.SPI_CR2_FRXTH | (0b111 << 8))
|
||||||
stm32.SPI3.CR1.SetBits(stm32.SPI_CR1_SPE)
|
stm32.SPI3.CR1.SetBits(stm32.SPI_CR1_SPE)
|
||||||
}
|
|
||||||
|
|
||||||
func (rc *STM32RadioControl) SetupInterrupts(handler func()) error {
|
|
||||||
irqHandler = handler
|
|
||||||
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, handleInterrupt)
|
|
||||||
intr.Enable()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var irqHandler func()
|
|
||||||
|
|
||||||
func handleInterrupt(interrupt.Interrupt) {
|
|
||||||
irqHandler()
|
|
||||||
}
|
}
|
||||||
+128
-167
@@ -7,19 +7,16 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
"tinygo.org/x/drivers/lora"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// SX126X radio transceiver RF_IN and RF_OUT may be connected
|
||||||
errWaitWhileBusyTimeout = errors.New("WaitWhileBusy Timeout")
|
// to RF Switch. This interface allows the creation of struct
|
||||||
errLowPowerTxNotSupported = errors.New("RFSWITCH_TX_LP not supported")
|
// that can drive the RF Switch (Used in Lora RX and Lora Tx)
|
||||||
errRadioNotFound = errors.New("LoRa radio not found")
|
type RFSwitch interface {
|
||||||
errUnexpectedRxRadioEvent = errors.New("Unexpected Radio Event during RX")
|
InitRFSwitch()
|
||||||
errUnexpectedTxRadioEvent = errors.New("Unexpected Radio Event during TX")
|
SetRfSwitchMode(mode int) error
|
||||||
)
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DEVICE_TYPE_SX1261 = iota
|
DEVICE_TYPE_SX1261 = iota
|
||||||
@@ -33,35 +30,61 @@ const (
|
|||||||
RFSWITCH_TX_HP = iota
|
RFSWITCH_TX_HP = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RadioEventRxDone = iota
|
||||||
|
RadioEventTxDone = iota
|
||||||
|
RadioEventTimeout = iota
|
||||||
|
RadioEventWatchdog = iota
|
||||||
|
RadioEventCrcError = iota
|
||||||
|
RadioEventUnhandled = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// RadioEvent are used for communicating in the radio Event Channel
|
||||||
|
type RadioEvent struct {
|
||||||
|
EventType int
|
||||||
|
IRQStatus uint16
|
||||||
|
EventData []byte
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PERIOD_PER_SEC = (uint32)(1000000 / 15.625) // SX1261 DS 13.1.4
|
PERIOD_PER_SEC = (uint32)(1000000 / 15.625) // SX1261 DS 13.1.4
|
||||||
SPI_BUFFER_SIZE = 256
|
SPI_BUFFER_SIZE = 256
|
||||||
)
|
)
|
||||||
|
|
||||||
// Device wraps an SPI connection to a SX126x device.
|
// Device wraps an SPI connection to a SX127x device.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
spi drivers.SPI // SPI bus for module communication
|
spi drivers.SPI // SPI bus for module communication
|
||||||
rstPin machine.Pin // GPIO for reset pin
|
radioEventChan chan RadioEvent // Channel for Receiving events
|
||||||
radioEventChan chan lora.RadioEvent // Channel for Receiving events
|
loraConf LoraConfig // Current Lora configuration
|
||||||
loraConf lora.Config // Current Lora configuration
|
rfswitch RFSwitch // RF Switch, if any
|
||||||
controller RadioController // to manage interactions with the radio
|
deepSleep bool // Internal Sleep state
|
||||||
deepSleep bool // Internal Sleep state
|
deviceType int // sx1261,sx1262,sx1268 (defaults sx1261)
|
||||||
deviceType int // sx1261,sx1262,sx1268 (defaults sx1261)
|
|
||||||
spiBuffer [SPI_BUFFER_SIZE]uint8
|
spiBuffer [SPI_BUFFER_SIZE]uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new SX126x connection.
|
// Config holds the LoRa configuration parameters
|
||||||
func New(spi drivers.SPI) *Device {
|
type LoraConfig struct {
|
||||||
return &Device{
|
Freq uint32 // Frequency
|
||||||
spi: spi,
|
Cr uint8 // Coding Rate
|
||||||
radioEventChan: make(chan lora.RadioEvent, 10),
|
Sf uint8 // Spread Factor
|
||||||
}
|
Bw uint8 // Bandwidth
|
||||||
|
Ldr uint8 // Low Data Rate
|
||||||
|
Preamble uint16 // PreambleLength
|
||||||
|
SyncWord uint16 // Sync Word
|
||||||
|
HeaderType uint8 // Header : Implicit/explicit
|
||||||
|
Crc uint8 // CRC : Yes/No
|
||||||
|
Iq uint8 // iq : Standard/inverted
|
||||||
|
LoraTxPowerDBm int8 // Tx power in Dbm
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SX126X_RTC_FREQ_IN_HZ uint32 = 64000
|
SX126X_RTC_FREQ_IN_HZ uint32 = 64000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errUndefinedLoraConf = errors.New("Undefined Lora configuration")
|
||||||
|
)
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// Helper functions
|
// Helper functions
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
@@ -77,8 +100,14 @@ func timeoutMsToRtcSteps(timeoutMs uint32) uint32 {
|
|||||||
// Channel and events
|
// Channel and events
|
||||||
//
|
//
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
// NewRadioEvent() returns a new RadioEvent that can be used in the RadioChannel
|
||||||
|
func NewRadioEvent(eType int, irqStatus uint16, eData []byte) RadioEvent {
|
||||||
|
r := RadioEvent{EventType: eType, IRQStatus: irqStatus, EventData: eData}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// Get the RadioEvent channel of the device
|
// Get the RadioEvent channel of the device
|
||||||
func (d *Device) GetRadioEventChan() chan lora.RadioEvent {
|
func (d *Device) GetRadioEventChan() chan RadioEvent {
|
||||||
return d.radioEventChan
|
return d.radioEventChan
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,28 +116,16 @@ func (d *Device) SetDeviceType(devType int) {
|
|||||||
d.deviceType = devType
|
d.deviceType = devType
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRadioControl let you define the RadioController
|
// SetRfSwitch let you define a custom RF Switch driver if needed
|
||||||
func (d *Device) SetRadioController(rc RadioController) error {
|
func (d *Device) SetRfSwitch(rfswitch RFSwitch) {
|
||||||
d.controller = rc
|
d.rfswitch = rfswitch
|
||||||
if err := d.controller.Init(); err != nil {
|
d.rfswitch.InitRFSwitch()
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.controller.SetupInterrupts(d.HandleInterrupt)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// Operational modes functions
|
// Operational modes functions
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
func (d *Device) Reset() {
|
|
||||||
d.rstPin.Low()
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
d.rstPin.High()
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetectDevice() tries to detect the radio module by changing SyncWord value
|
// DetectDevice() tries to detect the radio module by changing SyncWord value
|
||||||
func (d *Device) DetectDevice() bool {
|
func (d *Device) DetectDevice() bool {
|
||||||
bak := d.GetSyncWord()
|
bak := d.GetSyncWord()
|
||||||
@@ -139,18 +156,18 @@ func (d *Device) SetFs() {
|
|||||||
|
|
||||||
// SetTxContinuousWave set device in test mode to generate a continuous wave (RF tone)
|
// SetTxContinuousWave set device in test mode to generate a continuous wave (RF tone)
|
||||||
func (d *Device) SetTxContinuousWave() {
|
func (d *Device) SetTxContinuousWave() {
|
||||||
if d.controller != nil {
|
if d.rfswitch != nil {
|
||||||
d.controller.SetRfSwitchMode(RFSWITCH_TX_HP)
|
d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP)
|
||||||
}
|
}
|
||||||
d.ExecSetCommand(SX126X_CMD_SET_TX_CONTINUOUS_WAVE, []uint8{})
|
d.ExecSetCommand(SX126X_CMD_SET_TX_CONTINUOUS_WAVE, []uint8{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTxContinuousPreamble set device in test mode to constantly modulate LoRa preamble symbols.
|
// SetTxContinuousPreamble set device in test mode to constantly modulate LoRa preamble symbols.
|
||||||
// Take care to initialize all Lora settings like it's done in Tx before calling this function
|
// Take care to initialize all Lora settings like it's done in LoraTx before calling this function
|
||||||
// If you don't init properly all the settings, it'll fail
|
// If you don't init properly all the settings, it'll fail
|
||||||
func (d *Device) SetTxContinuousPreamble() {
|
func (d *Device) SetTxContinuousPreamble() {
|
||||||
if d.controller != nil {
|
if d.rfswitch != nil {
|
||||||
d.controller.SetRfSwitchMode(RFSWITCH_TX_HP)
|
d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP)
|
||||||
}
|
}
|
||||||
d.ExecSetCommand(SX126X_CMD_SET_TX_INFINITE_PREAMBLE, []uint8{})
|
d.ExecSetCommand(SX126X_CMD_SET_TX_INFINITE_PREAMBLE, []uint8{})
|
||||||
}
|
}
|
||||||
@@ -248,25 +265,25 @@ func (d *Device) SetRxTxFallbackMode(fallbackMode uint8) {
|
|||||||
// ReadRegister reads register value
|
// ReadRegister reads register value
|
||||||
func (d *Device) ReadRegister(addr, size uint16) ([]uint8, error) {
|
func (d *Device) ReadRegister(addr, size uint16) ([]uint8, error) {
|
||||||
d.CheckDeviceReady()
|
d.CheckDeviceReady()
|
||||||
d.controller.SetNss(false)
|
d.SpiSetNss(false)
|
||||||
// Send command
|
// Send command
|
||||||
cmd := []uint8{SX126X_CMD_READ_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF), 0x00}
|
cmd := []uint8{SX126X_CMD_READ_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF), 0x00}
|
||||||
d.spi.Tx(cmd, nil)
|
d.spi.Tx(cmd, nil)
|
||||||
ret := d.spiBuffer[0:size]
|
ret := d.spiBuffer[0:size]
|
||||||
d.spi.Tx(nil, ret)
|
d.spi.Tx(nil, ret)
|
||||||
d.controller.SetNss(true)
|
d.SpiSetNss(true)
|
||||||
d.controller.WaitWhileBusy()
|
d.WaitBusy()
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteRegister writes value to register
|
// WriteRegister writes value to register
|
||||||
func (d *Device) WriteRegister(addr uint16, data []uint8) {
|
func (d *Device) WriteRegister(addr uint16, data []uint8) {
|
||||||
d.CheckDeviceReady()
|
d.CheckDeviceReady()
|
||||||
d.controller.SetNss(false)
|
d.SpiSetNss(false)
|
||||||
cmd := []uint8{SX126X_CMD_WRITE_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF)}
|
cmd := []uint8{SX126X_CMD_WRITE_REGISTER, uint8((addr & 0xFF00) >> 8), uint8(addr & 0x00FF)}
|
||||||
d.spi.Tx(append(cmd, data...), nil)
|
d.spi.Tx(append(cmd, data...), nil)
|
||||||
d.controller.SetNss(true)
|
d.SpiSetNss(true)
|
||||||
d.controller.WaitWhileBusy()
|
d.WaitBusy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBuffer write data from current buffer position
|
// WriteBuffer write data from current buffer position
|
||||||
@@ -370,11 +387,11 @@ func (d *Device) SetPacketType(packetType uint8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetSyncWord defines the Sync Word to yse
|
// SetSyncWord defines the Sync Word to yse
|
||||||
func (d *Device) SetSyncWord(sw uint16) {
|
func (d *Device) SetSyncWord(syncword uint16) {
|
||||||
var p [2]uint8
|
var p [2]uint8
|
||||||
d.loraConf.SyncWord = sw
|
d.loraConf.SyncWord = syncword
|
||||||
p[0] = uint8((d.loraConf.SyncWord >> 8) & 0xFF)
|
p[0] = uint8((syncword >> 8) & 0xFF)
|
||||||
p[1] = uint8((d.loraConf.SyncWord >> 0) & 0xFF)
|
p[1] = uint8((syncword >> 0) & 0xFF)
|
||||||
d.WriteRegister(SX126X_REG_LORA_SYNC_WORD_MSB, p[:])
|
d.WriteRegister(SX126X_REG_LORA_SYNC_WORD_MSB, p[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,8 +402,8 @@ func (d *Device) GetSyncWord() uint16 {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPublicNetwork sets Sync Word to 0x3444 (Public) or 0x1424 (Private)
|
// SetLoraPublicNetwork sets Sync Word to 0x3444 (Public) or 0x1424 (Private)
|
||||||
func (d *Device) SetPublicNetwork(enable bool) {
|
func (d *Device) SetLoraPublicNetwork(enable bool) {
|
||||||
if enable {
|
if enable {
|
||||||
d.SetSyncWord(SX126X_LORA_MAC_PUBLIC_SYNCWORD)
|
d.SetSyncWord(SX126X_LORA_MAC_PUBLIC_SYNCWORD)
|
||||||
} else {
|
} else {
|
||||||
@@ -479,12 +496,12 @@ func (d *Device) SetModulationParams(spreadingFactor, bandwidth, codingRate, low
|
|||||||
// CheckDeviceReady sleep until all busy flags clears
|
// CheckDeviceReady sleep until all busy flags clears
|
||||||
func (d *Device) CheckDeviceReady() error {
|
func (d *Device) CheckDeviceReady() error {
|
||||||
if d.deepSleep == true {
|
if d.deepSleep == true {
|
||||||
d.controller.SetNss(false)
|
d.SpiSetNss(false)
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(time.Millisecond)
|
||||||
d.controller.SetNss(true)
|
d.SpiSetNss(true)
|
||||||
d.deepSleep = false
|
d.deepSleep = false
|
||||||
}
|
}
|
||||||
return d.controller.WaitWhileBusy()
|
return d.WaitBusy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecSetCommand send a command to configure the peripheral
|
// ExecSetCommand send a command to configure the peripheral
|
||||||
@@ -495,24 +512,24 @@ func (d *Device) ExecSetCommand(cmd uint8, buf []uint8) {
|
|||||||
} else {
|
} else {
|
||||||
d.deepSleep = false
|
d.deepSleep = false
|
||||||
}
|
}
|
||||||
d.controller.SetNss(false)
|
d.SpiSetNss(false)
|
||||||
// Send command and params
|
// Send command and params
|
||||||
d.spi.Tx(append([]uint8{cmd}, buf...), nil)
|
d.spi.Tx(append([]uint8{cmd}, buf...), nil)
|
||||||
d.controller.SetNss(true)
|
d.SpiSetNss(true)
|
||||||
if cmd != SX126X_CMD_SET_SLEEP {
|
if cmd != SX126X_CMD_SET_SLEEP {
|
||||||
d.controller.WaitWhileBusy()
|
d.WaitBusy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecGetCommand queries the peripheral the peripheral
|
// ExecGetCommand queries the peripheral the peripheral
|
||||||
func (d *Device) ExecGetCommand(cmd uint8, size uint8) []uint8 {
|
func (d *Device) ExecGetCommand(cmd uint8, size uint8) []uint8 {
|
||||||
d.CheckDeviceReady()
|
d.CheckDeviceReady()
|
||||||
d.controller.SetNss(false)
|
d.SpiSetNss(false)
|
||||||
// Send the command and flush first status byte (as not used)
|
// Send the command and flush first status byte (as not used)
|
||||||
d.spi.Tx([]uint8{cmd, 0x00}, nil)
|
d.spi.Tx([]uint8{cmd, 0x00}, nil)
|
||||||
d.spi.Tx(nil, d.spiBuffer[:size])
|
d.spi.Tx(nil, d.spiBuffer[:size])
|
||||||
d.controller.SetNss(true)
|
d.SpiSetNss(true)
|
||||||
d.controller.WaitWhileBusy()
|
d.WaitBusy()
|
||||||
return d.spiBuffer[:size]
|
return d.spiBuffer[:size]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,78 +537,59 @@ func (d *Device) ExecGetCommand(cmd uint8, size uint8) []uint8 {
|
|||||||
// Configuration
|
// Configuration
|
||||||
//
|
//
|
||||||
|
|
||||||
// SetFrequency() Sets current Lora Frequency
|
// SetLoraFrequency() Sets current Lora Frequency
|
||||||
// NB: Change will be applied at next RX / TX
|
// NB: Change will be applied at next RX / TX
|
||||||
func (d *Device) SetFrequency(freq uint32) {
|
func (d *Device) SetLoraFrequency(freq uint32) {
|
||||||
d.loraConf.Freq = freq
|
d.loraConf.Freq = d.loraConf.Freq
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetIqMode() defines the current IQ Mode (Standard/Inverted)
|
// SetLoraIqMode() defines the current IQ Mode (Standard/Inverted)
|
||||||
// NB: Change will be applied at next RX / TX
|
// NB: Change will be applied at next RX / TX
|
||||||
func (d *Device) SetIqMode(mode uint8) {
|
func (d *Device) SetLoraIqMode(mode uint8) {
|
||||||
if mode == 0 {
|
if mode == 0 {
|
||||||
d.loraConf.Iq = lora.IQStandard
|
d.loraConf.Iq = SX126X_LORA_IQ_STANDARD
|
||||||
} else {
|
} else {
|
||||||
d.loraConf.Iq = lora.IQInverted
|
d.loraConf.Iq = SX126X_LORA_IQ_INVERTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCodingRate() sets current Lora Coding Rate
|
// SetLoraCodingRate() sets current Lora Coding Rate
|
||||||
// NB: Change will be applied at next RX / TX
|
// NB: Change will be applied at next RX / TX
|
||||||
func (d *Device) SetCodingRate(cr uint8) {
|
func (d *Device) SetLoraCodingRate(cr uint8) {
|
||||||
d.loraConf.Cr = cr
|
d.loraConf.Cr = cr
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBandwidth() sets current Lora Bandwidth
|
// SetLoraBandwidth() sets current Lora Bandwidth
|
||||||
// NB: Change will be applied at next RX / TX
|
// NB: Change will be applied at next RX / TX
|
||||||
func (d *Device) SetBandwidth(bw uint8) {
|
func (d *Device) SetLoraBandwidth(bw uint8) {
|
||||||
d.loraConf.Bw = bw
|
d.loraConf.Cr = bw
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCrc() sets current CRC mode (ON/OFF)
|
// SetLoraCrc() sets current CRC mode (ON/OFF)
|
||||||
// NB: Change will be applied at next RX / TX
|
// NB: Change will be applied at next RX / TX
|
||||||
func (d *Device) SetCrc(enable bool) {
|
func (d *Device) SetLoraCrc(enable bool) {
|
||||||
if enable {
|
if enable {
|
||||||
d.loraConf.Crc = lora.CRCOn
|
d.loraConf.Crc = SX126X_LORA_CRC_ON
|
||||||
} else {
|
} else {
|
||||||
d.loraConf.Crc = lora.CRCOn
|
d.loraConf.Crc = SX126X_LORA_CRC_OFF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSpreadingFactor sets current Lora Spreading Factor
|
// SetLoraSpreadingFactor setc surrent Lora Spreading Factor
|
||||||
// NB: Change will be applied at next RX / TX
|
// NB: Change will be applied at next RX / TX
|
||||||
func (d *Device) SetSpreadingFactor(sf uint8) {
|
func (d *Device) SetLoraSpreadingFactor(sf uint8) {
|
||||||
d.loraConf.Sf = sf
|
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
|
// Lora functions
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|
||||||
// LoraConfig() defines Lora configuration for next Lora operations
|
// LoraConfig() defines Lora configuration for next Lora operations
|
||||||
func (d *Device) LoraConfig(cnf lora.Config) {
|
func (d *Device) LoraConfig(cnf LoraConfig) {
|
||||||
// Save given configuration
|
// Save given configuration
|
||||||
d.loraConf = cnf
|
d.loraConf = cnf
|
||||||
d.loraConf.SyncWord = syncword(int(cnf.SyncWord))
|
|
||||||
// Switch to standby prior to configuration changes
|
// Switch to standby prior to configuration changes
|
||||||
d.SetStandby()
|
d.SetStandby()
|
||||||
// Clear errors, disable radio interrupts for the moment
|
// Clear errors, disable radio interrupts for the moment
|
||||||
@@ -601,25 +599,24 @@ func (d *Device) LoraConfig(cnf lora.Config) {
|
|||||||
// Define radio operation mode
|
// Define radio operation mode
|
||||||
d.SetPacketType(SX126X_PACKET_TYPE_LORA)
|
d.SetPacketType(SX126X_PACKET_TYPE_LORA)
|
||||||
d.SetRfFrequency(d.loraConf.Freq)
|
d.SetRfFrequency(d.loraConf.Freq)
|
||||||
d.SetModulationParams(d.loraConf.Sf, bandwidth(d.loraConf.Bw), d.loraConf.Cr, d.loraConf.Ldr)
|
d.SetModulationParams(d.loraConf.Sf, d.loraConf.Bw, d.loraConf.Cr, d.loraConf.Ldr)
|
||||||
d.SetTxParams(d.loraConf.LoraTxPowerDBm, SX126X_PA_RAMP_200U)
|
d.SetTxParams(d.loraConf.LoraTxPowerDBm, SX126X_PA_RAMP_200U)
|
||||||
d.SetSyncWord(d.loraConf.SyncWord)
|
d.SetSyncWord(d.loraConf.SyncWord)
|
||||||
d.SetBufferBaseAddress(0, 0)
|
d.SetBufferBaseAddress(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tx sends a lora packet, (with timeout)
|
// LoraTx sends a lora packet, (with timeout)
|
||||||
func (d *Device) Tx(pkt []uint8, timeoutMs uint32) error {
|
func (d *Device) LoraTx(pkt []uint8, timeoutMs uint32) error {
|
||||||
if d.loraConf.Freq == 0 {
|
|
||||||
return lora.ErrUndefinedLoraConf
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.controller != nil {
|
if d.loraConf.Freq == 0 {
|
||||||
err := d.controller.SetRfSwitchMode(RFSWITCH_TX_HP)
|
return errUndefinedLoraConf
|
||||||
|
}
|
||||||
|
if d.rfswitch != nil {
|
||||||
|
err := d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.ClearIrqStatus(SX126X_IRQ_ALL)
|
d.ClearIrqStatus(SX126X_IRQ_ALL)
|
||||||
irqVal := uint16(SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR)
|
irqVal := uint16(SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR)
|
||||||
d.SetStandby()
|
d.SetStandby()
|
||||||
@@ -628,48 +625,46 @@ func (d *Device) Tx(pkt []uint8, timeoutMs uint32) error {
|
|||||||
d.SetTxParams(d.loraConf.LoraTxPowerDBm, SX126X_PA_RAMP_200U)
|
d.SetTxParams(d.loraConf.LoraTxPowerDBm, SX126X_PA_RAMP_200U)
|
||||||
d.SetBufferBaseAddress(0, 0)
|
d.SetBufferBaseAddress(0, 0)
|
||||||
d.WriteBuffer(pkt)
|
d.WriteBuffer(pkt)
|
||||||
d.SetModulationParams(d.loraConf.Sf, bandwidth(d.loraConf.Bw), d.loraConf.Cr, d.loraConf.Ldr)
|
d.SetModulationParams(d.loraConf.Sf, d.loraConf.Bw, d.loraConf.Cr, d.loraConf.Ldr)
|
||||||
d.SetPacketParam(d.loraConf.Preamble, d.loraConf.HeaderType, d.loraConf.Crc, uint8(len(pkt)), d.loraConf.Iq)
|
d.SetPacketParam(d.loraConf.Preamble, d.loraConf.HeaderType, d.loraConf.Crc, uint8(len(pkt)), d.loraConf.Iq)
|
||||||
d.SetDioIrqParams(irqVal, irqVal, SX126X_IRQ_NONE, SX126X_IRQ_NONE)
|
d.SetDioIrqParams(irqVal, irqVal, SX126X_IRQ_NONE, SX126X_IRQ_NONE)
|
||||||
d.SetSyncWord(d.loraConf.SyncWord)
|
d.SetSyncWord(d.loraConf.SyncWord)
|
||||||
d.SetTx(timeoutMsToRtcSteps(timeoutMs))
|
d.SetTx(timeoutMsToRtcSteps(timeoutMs))
|
||||||
|
|
||||||
msg := <-d.GetRadioEventChan()
|
msg := <-d.GetRadioEventChan()
|
||||||
if msg.EventType != lora.RadioEventTxDone {
|
if msg.EventType != RadioEventTxDone {
|
||||||
return errUnexpectedTxRadioEvent
|
return errors.New("Unexpected Radio Event while TX")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoraRx tries to receive a Lora packet (with timeout in milliseconds)
|
// LoraRx tries to receive a Lora packet (with timeout in milliseconds)
|
||||||
func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
|
func (d *Device) LoraRx(timeoutMs uint32) ([]uint8, error) {
|
||||||
if d.loraConf.Freq == 0 {
|
|
||||||
return nil, lora.ErrUndefinedLoraConf
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.controller != nil {
|
if d.loraConf.Freq == 0 {
|
||||||
err := d.controller.SetRfSwitchMode(RFSWITCH_RX)
|
return nil, errUndefinedLoraConf
|
||||||
|
}
|
||||||
|
if d.rfswitch != nil {
|
||||||
|
err := d.rfswitch.SetRfSwitchMode(RFSWITCH_RX)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.ClearIrqStatus(SX126X_IRQ_ALL)
|
d.ClearIrqStatus(SX126X_IRQ_ALL)
|
||||||
irqVal := uint16(SX126X_IRQ_RX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR)
|
irqVal := uint16(SX126X_IRQ_RX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR)
|
||||||
d.SetStandby()
|
d.SetStandby()
|
||||||
d.SetBufferBaseAddress(0, 0)
|
d.SetBufferBaseAddress(0, 0)
|
||||||
d.SetRfFrequency(d.loraConf.Freq)
|
d.SetModulationParams(d.loraConf.Sf, d.loraConf.Bw, d.loraConf.Cr, d.loraConf.Ldr)
|
||||||
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.SetPacketParam(d.loraConf.Preamble, d.loraConf.HeaderType, d.loraConf.Crc, 0xFF, d.loraConf.Iq)
|
||||||
d.SetDioIrqParams(irqVal, irqVal, SX126X_IRQ_NONE, SX126X_IRQ_NONE)
|
d.SetDioIrqParams(irqVal, irqVal, SX126X_IRQ_NONE, SX126X_IRQ_NONE)
|
||||||
d.SetRx(timeoutMsToRtcSteps(timeoutMs))
|
d.SetRx(timeoutMsToRtcSteps(timeoutMs))
|
||||||
|
|
||||||
msg := <-d.GetRadioEventChan()
|
msg := <-d.GetRadioEventChan()
|
||||||
|
|
||||||
if msg.EventType == lora.RadioEventTimeout {
|
if msg.EventType == RadioEventTimeout {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
} else if msg.EventType != lora.RadioEventRxDone {
|
} else if msg.EventType != RadioEventRxDone {
|
||||||
return nil, errUnexpectedRxRadioEvent
|
return nil, errors.New("Unexpected Radio Event while RX")
|
||||||
}
|
}
|
||||||
|
|
||||||
pLen, pStart := d.GetRxBufferStatus()
|
pLen, pStart := d.GetRxBufferStatus()
|
||||||
@@ -688,53 +683,19 @@ func (d *Device) HandleInterrupt() {
|
|||||||
rChan := d.GetRadioEventChan()
|
rChan := d.GetRadioEventChan()
|
||||||
|
|
||||||
if (st & SX126X_IRQ_RX_DONE) > 0 {
|
if (st & SX126X_IRQ_RX_DONE) > 0 {
|
||||||
rChan <- lora.NewRadioEvent(lora.RadioEventRxDone, st, nil)
|
rChan <- NewRadioEvent(RadioEventRxDone, st, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st & SX126X_IRQ_TX_DONE) > 0 {
|
if (st & SX126X_IRQ_TX_DONE) > 0 {
|
||||||
rChan <- lora.NewRadioEvent(lora.RadioEventTxDone, st, nil)
|
rChan <- NewRadioEvent(RadioEventTxDone, st, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st & SX126X_IRQ_TIMEOUT) > 0 {
|
if (st & SX126X_IRQ_TIMEOUT) > 0 {
|
||||||
rChan <- lora.NewRadioEvent(lora.RadioEventTimeout, st, nil)
|
rChan <- NewRadioEvent(RadioEventTimeout, st, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st & SX126X_IRQ_CRC_ERR) > 0 {
|
if (st & SX126X_IRQ_CRC_ERR) > 0 {
|
||||||
rChan <- lora.NewRadioEvent(lora.RadioEventCrcError, st, nil)
|
rChan <- NewRadioEvent(RadioEventCrcError, st, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func bandwidth(bw uint8) uint8 {
|
|
||||||
switch bw {
|
|
||||||
case lora.Bandwidth_7_8:
|
|
||||||
return SX126X_LORA_BW_7_8
|
|
||||||
case lora.Bandwidth_10_4:
|
|
||||||
return SX126X_LORA_BW_10_4
|
|
||||||
case lora.Bandwidth_15_6:
|
|
||||||
return SX126X_LORA_BW_15_6
|
|
||||||
case lora.Bandwidth_20_8:
|
|
||||||
return SX126X_LORA_BW_20_8
|
|
||||||
case lora.Bandwidth_31_25:
|
|
||||||
return SX126X_LORA_BW_31_25
|
|
||||||
case lora.Bandwidth_41_7:
|
|
||||||
return SX126X_LORA_BW_41_7
|
|
||||||
case lora.Bandwidth_62_5:
|
|
||||||
return SX126X_LORA_BW_62_5
|
|
||||||
case lora.Bandwidth_125_0:
|
|
||||||
return SX126X_LORA_BW_125_0
|
|
||||||
case lora.Bandwidth_250_0:
|
|
||||||
return SX126X_LORA_BW_250_0
|
|
||||||
case lora.Bandwidth_500_0:
|
|
||||||
return SX126X_LORA_BW_500_0
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func syncword(sw int) uint16 {
|
|
||||||
if sw == lora.SyncPublic {
|
|
||||||
return SX126X_LORA_MAC_PUBLIC_SYNCWORD
|
|
||||||
}
|
|
||||||
return SX126X_LORA_MAC_PRIVATE_SYNCWORD
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package sx127x
|
|
||||||
|
|
||||||
const (
|
|
||||||
// registers
|
|
||||||
SX127X_REG_FIFO = 0x00
|
|
||||||
SX127X_REG_OP_MODE = 0x01
|
|
||||||
SX127X_REG_FRF_MSB = 0x06
|
|
||||||
SX127X_REG_FRF_MID = 0x07
|
|
||||||
SX127X_REG_FRF_LSB = 0x08
|
|
||||||
SX127X_REG_PA_CONFIG = 0x09
|
|
||||||
SX127X_REG_PA_RAMP = 0x0a
|
|
||||||
SX127X_REG_OCP = 0x0b
|
|
||||||
SX127X_REG_LNA = 0x0c
|
|
||||||
SX127X_REG_FIFO_ADDR_PTR = 0x0d
|
|
||||||
SX127X_REG_FIFO_TX_BASE_ADDR = 0x0e
|
|
||||||
SX127X_REG_FIFO_RX_BASE_ADDR = 0x0f
|
|
||||||
SX127X_REG_FIFO_RX_CURRENT_ADDR = 0x10
|
|
||||||
SX127X_REG_IRQ_FLAGS_MASK = 0x11
|
|
||||||
SX127X_REG_IRQ_FLAGS = 0x12
|
|
||||||
SX127X_REG_RX_NB_BYTES = 0x13
|
|
||||||
SX127X_REG_PKT_SNR_VALUE = 0x19
|
|
||||||
SX127X_REG_PKT_RSSI_VALUE = 0x1a
|
|
||||||
SX127X_REG_RSSI_VALUE = 0x1b
|
|
||||||
SX127X_REG_MODEM_CONFIG_1 = 0x1d
|
|
||||||
SX127X_REG_MODEM_CONFIG_2 = 0x1e
|
|
||||||
SX127X_REG_SYMB_TIMEOUT_LSB = 0x1f
|
|
||||||
SX127X_REG_PREAMBLE_MSB = 0x20
|
|
||||||
SX127X_REG_PREAMBLE_LSB = 0x21
|
|
||||||
SX127X_REG_PAYLOAD_LENGTH = 0x22
|
|
||||||
SX127X_REG_MAX_PAYLOAD_LENGTH = 0x23
|
|
||||||
SX127X_REG_HOP_PERIOD = 0x24
|
|
||||||
SX127X_REG_MODEM_CONFIG_3 = 0x26
|
|
||||||
SX127X_REG_FREQ_ERROR_MSB = 0x28
|
|
||||||
SX127X_REG_FREQ_ERROR_MID = 0x29
|
|
||||||
SX127X_REG_FREQ_ERROR_LSB = 0x2a
|
|
||||||
SX127X_REG_RSSI_WIDEBAND = 0x2c
|
|
||||||
SX127X_REG_DETECTION_OPTIMIZE = 0x31
|
|
||||||
SX127X_REG_INVERTIQ = 0x33
|
|
||||||
SX127X_REG_DETECTION_THRESHOLD = 0x37
|
|
||||||
SX127X_REG_SYNC_WORD = 0x39
|
|
||||||
SX127X_REG_INVERTIQ2 = 0x3b
|
|
||||||
SX127X_REG_DIO_MAPPING_1 = 0x40
|
|
||||||
SX127X_REG_DIO_MAPPING_2 = 0x41
|
|
||||||
SX127X_REG_VERSION = 0x42
|
|
||||||
SX127X_REG_PA_DAC = 0x4d
|
|
||||||
// PA config
|
|
||||||
SX127X_PA_BOOST = 0x80
|
|
||||||
|
|
||||||
// Bits masking the corresponding IRQs from the radio
|
|
||||||
SX127X_IRQ_LORA_RXTOUT_MASK = uint8(0x80)
|
|
||||||
SX127X_IRQ_LORA_RXDONE_MASK = uint8(0x40)
|
|
||||||
SX127X_IRQ_LORA_CRCERR_MASK = uint8(0x20)
|
|
||||||
SX127X_IRQ_LORA_HEADER_MASK = uint8(0x10)
|
|
||||||
SX127X_IRQ_LORA_TXDONE_MASK = uint8(0x08)
|
|
||||||
SX127X_IRQ_LORA_CDDONE_MASK = uint8(0x04)
|
|
||||||
SX127X_IRQ_LORA_FHSSCH_MASK = uint8(0x02)
|
|
||||||
SX127X_IRQ_LORA_CDDETD_MASK = uint8(0x01)
|
|
||||||
|
|
||||||
// DIO function mappings D0D1D2D3
|
|
||||||
SX127X_MAP_DIO0_LORA_RXDONE = uint8(0x00) // 00------
|
|
||||||
SX127X_MAP_DIO0_LORA_TXDONE = uint8(0x40) // 01------
|
|
||||||
SX127X_MAP_DIO1_LORA_RXTOUT = uint8(0x00) // --00----
|
|
||||||
SX127X_MAP_DIO1_LORA_NOP = uint8(0x30) // --11----
|
|
||||||
SX127X_MAP_DIO2_LORA_NOP = uint8(0xC0) // ----11--
|
|
||||||
|
|
||||||
SX127X_PAYLOAD_LENGTH = uint8(0x40)
|
|
||||||
|
|
||||||
// Low Noise Amp
|
|
||||||
SX127X_LNA_MAX_GAIN = uint8(0x23)
|
|
||||||
SX127X_LNA_OFF_GAIN = uint8(0x00)
|
|
||||||
SX127X_LNA_LOW_GAIN = uint8(0x20)
|
|
||||||
|
|
||||||
// Bandwidth
|
|
||||||
SX127X_LORA_BW_7_8 = uint8(0x00)
|
|
||||||
SX127X_LORA_BW_10_4 = uint8(0x01)
|
|
||||||
SX127X_LORA_BW_15_6 = uint8(0x02)
|
|
||||||
SX127X_LORA_BW_20_8 = uint8(0x03)
|
|
||||||
SX127X_LORA_BW_31_25 = uint8(0x04)
|
|
||||||
SX127X_LORA_BW_41_7 = uint8(0x05)
|
|
||||||
SX127X_LORA_BW_62_5 = uint8(0x06)
|
|
||||||
SX127X_LORA_BW_125_0 = uint8(0x07)
|
|
||||||
SX127X_LORA_BW_250_0 = uint8(0x08)
|
|
||||||
SX127X_LORA_BW_500_0 = uint8(0x09)
|
|
||||||
// Automatic gain control
|
|
||||||
SX127X_AGC_AUTO_OFF = uint8(0x00)
|
|
||||||
SX127X_AGC_AUTO_ON = uint8(0x01)
|
|
||||||
// Operation modes
|
|
||||||
SX127X_OPMODE_LORA = uint8(0x80)
|
|
||||||
SX127X_OPMODE_MASK = uint8(0x07)
|
|
||||||
SX127X_OPMODE_SLEEP = uint8(0x00)
|
|
||||||
SX127X_OPMODE_STANDBY = uint8(0x01)
|
|
||||||
SX127X_OPMODE_FSTX = uint8(0x02)
|
|
||||||
SX127X_OPMODE_TX = uint8(0x03)
|
|
||||||
SX127X_OPMODE_FSRX = uint8(0x04)
|
|
||||||
SX127X_OPMODE_RX = uint8(0x05)
|
|
||||||
SX127X_OPMODE_RX_SINGLE = uint8(0x06)
|
|
||||||
SX127X_OPMODE_CAD = uint8(0x07)
|
|
||||||
|
|
||||||
SX127X_LORA_MAC_PUBLIC_SYNCWORD = 0x34
|
|
||||||
SX127X_LORA_MAC_PRIVATE_SYNCWORD = 0x14
|
|
||||||
)
|
|
||||||
@@ -1,564 +0,0 @@
|
|||||||
// Package sx127x provides a driver for SX127x LoRa transceivers.
|
|
||||||
// References:
|
|
||||||
// https://electronics.stackexchange.com/questions/394296/can-t-get-simple-lora-receiver-to-work
|
|
||||||
// https://www.st.com/resource/en/user_manual/dm00300436-stm32-lora-expansion-package-for-stm32cube-stmicroelectronics.pdf
|
|
||||||
package sx127x
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"machine"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
|
||||||
"tinygo.org/x/drivers/lora"
|
|
||||||
)
|
|
||||||
|
|
||||||
// So we can keep track of the origin of interruption
|
|
||||||
const (
|
|
||||||
SPI_BUFFER_SIZE = 256
|
|
||||||
)
|
|
||||||
|
|
||||||
// Device wraps an SPI connection to a SX127x device.
|
|
||||||
type Device struct {
|
|
||||||
spi drivers.SPI // SPI bus for module communication
|
|
||||||
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
|
|
||||||
packetIndex uint8 // FIXME ... useless ?
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
//
|
|
||||||
// Channel and events
|
|
||||||
//
|
|
||||||
// --------------------------------------------------
|
|
||||||
// Get the RadioEvent channel of the device
|
|
||||||
func (d *Device) GetRadioEventChan() chan lora.RadioEvent {
|
|
||||||
return d.radioEventChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new SX127x connection. The SPI bus must already be configured.
|
|
||||||
func New(spi machine.SPI, rstPin machine.Pin) *Device {
|
|
||||||
k := Device{
|
|
||||||
spi: spi,
|
|
||||||
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()
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
d.rstPin.High()
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetectDevice checks if device responds on the SPI bus
|
|
||||||
func (d *Device) DetectDevice() bool {
|
|
||||||
id := d.GetVersion()
|
|
||||||
return (id == 0x12)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadRegister reads register value
|
|
||||||
func (d *Device) ReadRegister(reg uint8) uint8 {
|
|
||||||
d.controller.SetNss(false)
|
|
||||||
d.spi.Tx([]byte{reg & 0x7f}, nil)
|
|
||||||
var value [1]byte
|
|
||||||
d.spi.Tx(nil, value[:])
|
|
||||||
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.controller.SetNss(false)
|
|
||||||
d.spi.Tx([]byte{reg | 0x80}, nil)
|
|
||||||
d.spi.Tx([]byte{value}, response[:])
|
|
||||||
d.controller.SetNss(true)
|
|
||||||
return response[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOpMode changes the sx1276 mode
|
|
||||||
func (d *Device) SetOpMode(mode uint8) {
|
|
||||||
cur := d.ReadRegister(SX127X_REG_OP_MODE)
|
|
||||||
new := (cur & (^SX127X_OPMODE_MASK)) | mode
|
|
||||||
d.WriteRegister(SX127X_REG_OP_MODE, new)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOpMode changes the sx1276 mode
|
|
||||||
func (d *Device) SetOpModeLora() {
|
|
||||||
d.WriteRegister(SX127X_REG_OP_MODE, SX127X_OPMODE_LORA)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVersion returns hardware version of sx1276 chipset
|
|
||||||
func (d *Device) GetVersion() uint8 {
|
|
||||||
return (d.ReadRegister(SX127X_REG_VERSION))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTransmitting tests if a packet transmission is in progress
|
|
||||||
func (d *Device) IsTransmitting() bool {
|
|
||||||
return (d.ReadRegister(SX127X_REG_OP_MODE) & SX127X_OPMODE_TX) == SX127X_OPMODE_TX
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastPacketRSSI gives the RSSI of the last packet received
|
|
||||||
func (d *Device) LastPacketRSSI() uint8 {
|
|
||||||
// section 5.5.5
|
|
||||||
var adjustValue uint8 = 157
|
|
||||||
if d.loraConf.Freq < 868000000 {
|
|
||||||
adjustValue = 164
|
|
||||||
}
|
|
||||||
return d.ReadRegister(SX127X_REG_PKT_RSSI_VALUE) - adjustValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastPacketSNR gives the SNR of the last packet received
|
|
||||||
func (d *Device) LastPacketSNR() uint8 {
|
|
||||||
return uint8(d.ReadRegister(SX127X_REG_PKT_SNR_VALUE) / 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRSSI returns current RSSI
|
|
||||||
func (d *Device) GetRSSI() uint8 {
|
|
||||||
return d.ReadRegister(SX127X_REG_RSSI_VALUE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// GetBandwidth returns the bandwidth the LoRa module is using
|
|
||||||
func (d *Device) GetBandwidth() int32 {
|
|
||||||
return int32(d.loraConf.Bw)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// SetTxPowerWithPaBoost sets the transmitter output power and may activate paBoost
|
|
||||||
func (d *Device) SetTxPowerWithPaBoost(txPower int8, paBoost bool) {
|
|
||||||
if !paBoost {
|
|
||||||
// RFO
|
|
||||||
if txPower < 0 {
|
|
||||||
txPower = 0
|
|
||||||
} else if txPower > 14 {
|
|
||||||
txPower = 14
|
|
||||||
}
|
|
||||||
d.WriteRegister(SX127X_REG_PA_CONFIG, uint8(0x70)|uint8(txPower))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
//PA_BOOST
|
|
||||||
if txPower > 17 {
|
|
||||||
if txPower > 20 {
|
|
||||||
txPower = 20
|
|
||||||
}
|
|
||||||
|
|
||||||
txPower -= 3
|
|
||||||
|
|
||||||
// High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.)
|
|
||||||
d.WriteRegister(SX127X_REG_PA_DAC, 0x87)
|
|
||||||
d.SetOCP(140)
|
|
||||||
} else {
|
|
||||||
if txPower < 2 {
|
|
||||||
txPower = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
d.WriteRegister(SX127X_REG_PA_DAC, 0x84)
|
|
||||||
d.SetOCP(100)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
d.WriteRegister(SX127X_REG_PA_CONFIG, uint8(SX127X_PA_BOOST)|uint8(txPower-2))
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------
|
|
||||||
// Internal functions
|
|
||||||
// ---------------
|
|
||||||
|
|
||||||
// SetRxTimeout defines RX Timeout expressed as number of symbols
|
|
||||||
// Default timeout is 64 * Ts
|
|
||||||
func (d *Device) SetRxTimeout(tmoutSymb uint8) {
|
|
||||||
d.WriteRegister(SX127X_REG_SYMB_TIMEOUT_LSB, tmoutSymb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOCP defines Overload Current Protection configuration
|
|
||||||
func (d *Device) SetOCP(mA uint8) {
|
|
||||||
ocpTrim := uint8(27)
|
|
||||||
if mA < 45 {
|
|
||||||
mA = 45
|
|
||||||
}
|
|
||||||
if mA <= 120 {
|
|
||||||
ocpTrim = (mA - 45) / 5
|
|
||||||
} else if mA <= 240 {
|
|
||||||
ocpTrim = (mA + 30) / 10
|
|
||||||
}
|
|
||||||
d.WriteRegister(SX127X_REG_OCP, 0x20|(0x1F&ocpTrim))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAgcAutoOn enables Automatic Gain Control
|
|
||||||
func (d *Device) SetAgcAuto(val uint8) {
|
|
||||||
if val == SX127X_AGC_AUTO_ON {
|
|
||||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)|0x04)
|
|
||||||
} else {
|
|
||||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)&0xfb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLowDataRateOptimize enables Low Data Rate Optimization
|
|
||||||
func (d *Device) SetLowDataRateOptim(val uint8) {
|
|
||||||
if val == lora.LowDataRateOptimizeOn {
|
|
||||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)|0x08)
|
|
||||||
} else {
|
|
||||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)&0xf7)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLowFrequencyModeOn enables Low Data Rate Optimization
|
|
||||||
func (d *Device) SetLowFrequencyModeOn(val bool) {
|
|
||||||
if val {
|
|
||||||
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)|0x04)
|
|
||||||
} else {
|
|
||||||
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)&0xfb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHopPeriod sets number of symbol periods between frequency hops. (0 = disabled).
|
|
||||||
func (d *Device) SetHopPeriod(val uint8) {
|
|
||||||
d.WriteRegister(SX127X_REG_HOP_PERIOD, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// LORA FUNCTIONS
|
|
||||||
//
|
|
||||||
|
|
||||||
// LoraConfig() defines Lora configuration for next Lora operations
|
|
||||||
func (d *Device) LoraConfig(cnf lora.Config) {
|
|
||||||
// Save given configuration
|
|
||||||
d.loraConf = cnf
|
|
||||||
d.loraConf.SyncWord = syncword(int(cnf.SyncWord))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFrequency updates the frequency the LoRa module is using
|
|
||||||
func (d *Device) SetFrequency(frequency uint32) {
|
|
||||||
d.loraConf.Freq = frequency
|
|
||||||
var frf = (uint64(frequency) << 19) / 32000000
|
|
||||||
d.WriteRegister(SX127X_REG_FRF_MSB, uint8(frf>>16))
|
|
||||||
d.WriteRegister(SX127X_REG_FRF_MID, uint8(frf>>8))
|
|
||||||
d.WriteRegister(SX127X_REG_FRF_LSB, uint8(frf>>0))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBandwidth updates the bandwidth the LoRa module is using
|
|
||||||
func (d *Device) SetBandwidth(bw uint8) {
|
|
||||||
d.loraConf.Bw = bandwidth(bw)
|
|
||||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, (d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)&0x0f)|(bw<<4))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCodingRate updates the coding rate the LoRa module is using
|
|
||||||
func (d *Device) SetCodingRate(cr uint8) {
|
|
||||||
d.loraConf.Cr = cr
|
|
||||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, (d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)&0xf1)|(cr<<1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
} else {
|
|
||||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)&0xfe)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSpreadingFactor changes spreading factor
|
|
||||||
func (d *Device) SetSpreadingFactor(sf uint8) {
|
|
||||||
d.loraConf.Sf = sf
|
|
||||||
if sf == lora.SpreadingFactor6 {
|
|
||||||
d.WriteRegister(SX127X_REG_DETECTION_OPTIMIZE, 0xc5)
|
|
||||||
d.WriteRegister(SX127X_REG_DETECTION_THRESHOLD, 0x0c)
|
|
||||||
} else {
|
|
||||||
d.WriteRegister(SX127X_REG_DETECTION_OPTIMIZE, 0xc3)
|
|
||||||
d.WriteRegister(SX127X_REG_DETECTION_THRESHOLD, 0x0a)
|
|
||||||
}
|
|
||||||
var newValue = (d.ReadRegister(SX127X_REG_MODEM_CONFIG_2) & 0x0f) | ((sf << 4) & 0xf0)
|
|
||||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
func (d *Device) SetCrc(enable bool) {
|
|
||||||
if enable {
|
|
||||||
d.loraConf.Crc = lora.CRCOn
|
|
||||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)|0x04)
|
|
||||||
} else {
|
|
||||||
d.loraConf.Crc = lora.CRCOff
|
|
||||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)&0xfb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSyncWord defines sync word
|
|
||||||
func (d *Device) SetSyncWord(syncWord uint16) {
|
|
||||||
d.loraConf.SyncWord = syncWord
|
|
||||||
sw := uint8(syncWord & 0xFF)
|
|
||||||
d.WriteRegister(SX127X_REG_SYNC_WORD, sw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetIQMode Sets I/Q polarity configuration
|
|
||||||
func (d *Device) SetIqMode(val uint8) {
|
|
||||||
d.loraConf.Iq = val
|
|
||||||
if val == lora.IQStandard {
|
|
||||||
//Set IQ to normal values
|
|
||||||
d.WriteRegister(SX127X_REG_INVERTIQ, 0x27)
|
|
||||||
d.WriteRegister(SX127X_REG_INVERTIQ2, 0x1D)
|
|
||||||
} else {
|
|
||||||
//Invert IQ Back
|
|
||||||
d.WriteRegister(SX127X_REG_INVERTIQ, 0x66)
|
|
||||||
d.WriteRegister(SX127X_REG_INVERTIQ2, 0x19)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
d.SetOpMode(SX127X_OPMODE_SLEEP)
|
|
||||||
|
|
||||||
d.SetHopPeriod(0x00)
|
|
||||||
d.SetLowFrequencyModeOn(false) // High freq mode
|
|
||||||
d.WriteRegister(SX127X_REG_PA_RAMP, (d.ReadRegister(SX127X_REG_PA_RAMP)&0xF0)|0x08) // set PA ramp-up time 50 uSec
|
|
||||||
d.WriteRegister(SX127X_REG_LNA, SX127X_LNA_MAX_GAIN) // Set Low Noise Amplifier to MAX
|
|
||||||
|
|
||||||
d.SetFrequency(d.loraConf.Freq)
|
|
||||||
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)
|
|
||||||
d.SetHeaderType(d.loraConf.HeaderType)
|
|
||||||
d.SetAgcAuto(SX127X_AGC_AUTO_ON)
|
|
||||||
|
|
||||||
// set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP
|
|
||||||
d.WriteRegister(SX127X_REG_DIO_MAPPING_1, SX127X_MAP_DIO0_LORA_TXDONE|SX127X_MAP_DIO1_LORA_NOP|SX127X_MAP_DIO2_LORA_NOP)
|
|
||||||
// Clear all radio IRQ Flags
|
|
||||||
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
|
|
||||||
// Mask all but TxDone
|
|
||||||
d.WriteRegister(SX127X_REG_IRQ_FLAGS_MASK, ^SX127X_IRQ_LORA_TXDONE_MASK)
|
|
||||||
|
|
||||||
// initialize the payload size and address pointers
|
|
||||||
d.WriteRegister(SX127X_REG_PAYLOAD_LENGTH, uint8(len(pkt)))
|
|
||||||
d.WriteRegister(SX127X_REG_FIFO_TX_BASE_ADDR, 0)
|
|
||||||
d.WriteRegister(SX127X_REG_FIFO_ADDR_PTR, 0)
|
|
||||||
|
|
||||||
// FIFO OPs cannot take place in Sleep mode !!!
|
|
||||||
d.SetOpMode(SX127X_OPMODE_STANDBY)
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
// Copy payload to FIFO // TODO: Bulk
|
|
||||||
for i := 0; i < len(pkt); i++ {
|
|
||||||
d.WriteRegister(SX127X_REG_FIFO, pkt[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable TX
|
|
||||||
d.SetOpMode(SX127X_OPMODE_TX)
|
|
||||||
|
|
||||||
msg := <-d.GetRadioEventChan()
|
|
||||||
if msg.EventType != lora.RadioEventTxDone {
|
|
||||||
return errors.New("Unexpected Radio Event while TX " + string(0x30+msg.EventType))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rx tries to receive a Lora packet (with timeout in milliseconds)
|
|
||||||
func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
|
|
||||||
if d.loraConf.Freq == 0 {
|
|
||||||
return nil, lora.ErrUndefinedLoraConf
|
|
||||||
}
|
|
||||||
|
|
||||||
d.SetOpModeLora()
|
|
||||||
d.SetOpMode(SX127X_OPMODE_SLEEP)
|
|
||||||
|
|
||||||
d.SetHopPeriod(0x00)
|
|
||||||
d.SetLowFrequencyModeOn(false) // High freq mode
|
|
||||||
d.WriteRegister(SX127X_REG_PA_RAMP, (d.ReadRegister(SX127X_REG_PA_RAMP)&0xF0)|0x08) // set PA ramp-up time 50 uSec
|
|
||||||
d.WriteRegister(SX127X_REG_LNA, SX127X_LNA_MAX_GAIN) // Set Low Noise Amplifier to MAX
|
|
||||||
|
|
||||||
d.SetFrequency(d.loraConf.Freq)
|
|
||||||
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)
|
|
||||||
d.SetHeaderType(d.loraConf.HeaderType)
|
|
||||||
d.SetAgcAuto(SX127X_AGC_AUTO_ON)
|
|
||||||
|
|
||||||
// set the IRQ mapping DIO0=RxDone DIO1=RxTimeout DIO2=NOP
|
|
||||||
d.WriteRegister(SX127X_REG_DIO_MAPPING_1, SX127X_MAP_DIO0_LORA_RXDONE|SX127X_MAP_DIO1_LORA_RXTOUT|SX127X_MAP_DIO2_LORA_NOP)
|
|
||||||
// Clear all radio IRQ Flags
|
|
||||||
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))
|
|
||||||
|
|
||||||
// Get Radio Event Channel
|
|
||||||
radioCh := d.GetRadioEventChan()
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the received payload
|
|
||||||
d.WriteRegister(SX127X_REG_FIFO_RX_BASE_ADDR, 0)
|
|
||||||
d.WriteRegister(SX127X_REG_FIFO_ADDR_PTR, 0)
|
|
||||||
|
|
||||||
pLen := d.ReadRegister(SX127X_REG_RX_NB_BYTES)
|
|
||||||
d.WriteRegister(SX127X_REG_FIFO_ADDR_PTR, d.ReadRegister(SX127X_REG_FIFO_RX_CURRENT_ADDR))
|
|
||||||
|
|
||||||
for i := uint8(0); i < pLen; i++ {
|
|
||||||
d.spiBuffer[i] = d.ReadRegister(SX127X_REG_FIFO)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
//
|
|
||||||
|
|
||||||
// PrintRegisters outputs the sx127x transceiver registers
|
|
||||||
func (d *Device) PrintRegisters(compact bool) {
|
|
||||||
for i := uint8(0); i < 128; i++ {
|
|
||||||
v := d.ReadRegister(i)
|
|
||||||
print(v, " ")
|
|
||||||
}
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintRegisters outputs the sx127x transceiver registers
|
|
||||||
func (d *Device) RandomU32() uint32 {
|
|
||||||
// Disable ALL irqs
|
|
||||||
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
|
|
||||||
d.SetOpModeLora()
|
|
||||||
d.SetOpMode(SX127X_OPMODE_SLEEP)
|
|
||||||
d.SetFrequency(d.loraConf.Freq)
|
|
||||||
d.SetOpMode(SX127X_OPMODE_RX)
|
|
||||||
rnd := uint32(0)
|
|
||||||
for i := 0; i < 32; i++ {
|
|
||||||
time.Sleep(time.Millisecond * 10)
|
|
||||||
// Unfiltered RSSI value reading. Only takes the LSB value
|
|
||||||
rnd |= (uint32(d.ReadRegister(SX127X_REG_RSSI_WIDEBAND)) & 0x01) << i
|
|
||||||
}
|
|
||||||
return rnd
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleInterrupt must be called by main code on DIO state change.
|
|
||||||
func (d *Device) HandleInterrupt() {
|
|
||||||
// Get IRQ and clear
|
|
||||||
st := d.ReadRegister(SX127X_REG_IRQ_FLAGS)
|
|
||||||
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
|
|
||||||
|
|
||||||
rChan := d.GetRadioEventChan()
|
|
||||||
|
|
||||||
if (st & SX127X_IRQ_LORA_RXDONE_MASK) > 0 {
|
|
||||||
rChan <- lora.NewRadioEvent(lora.RadioEventRxDone, uint16(st), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (st & SX127X_IRQ_LORA_TXDONE_MASK) > 0 {
|
|
||||||
rChan <- lora.NewRadioEvent(lora.RadioEventTxDone, uint16(st), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (st & SX127X_IRQ_LORA_RXTOUT_MASK) > 0 {
|
|
||||||
rChan <- lora.NewRadioEvent(lora.RadioEventTimeout, uint16(st), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (st & SX127X_IRQ_LORA_CRCERR_MASK) > 0 {
|
|
||||||
rChan <- lora.NewRadioEvent(lora.RadioEventCrcError, uint16(st), nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func bandwidth(bw uint8) uint8 {
|
|
||||||
switch bw {
|
|
||||||
case lora.Bandwidth_7_8:
|
|
||||||
return SX127X_LORA_BW_7_8
|
|
||||||
case lora.Bandwidth_10_4:
|
|
||||||
return SX127X_LORA_BW_10_4
|
|
||||||
case lora.Bandwidth_15_6:
|
|
||||||
return SX127X_LORA_BW_15_6
|
|
||||||
case lora.Bandwidth_20_8:
|
|
||||||
return SX127X_LORA_BW_20_8
|
|
||||||
case lora.Bandwidth_31_25:
|
|
||||||
return SX127X_LORA_BW_31_25
|
|
||||||
case lora.Bandwidth_41_7:
|
|
||||||
return SX127X_LORA_BW_41_7
|
|
||||||
case lora.Bandwidth_62_5:
|
|
||||||
return SX127X_LORA_BW_62_5
|
|
||||||
case lora.Bandwidth_125_0:
|
|
||||||
return SX127X_LORA_BW_125_0
|
|
||||||
case lora.Bandwidth_250_0:
|
|
||||||
return SX127X_LORA_BW_250_0
|
|
||||||
case lora.Bandwidth_500_0:
|
|
||||||
return SX127X_LORA_BW_500_0
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func syncword(sw int) uint16 {
|
|
||||||
if sw == lora.SyncPublic {
|
|
||||||
return SX127X_LORA_MAC_PUBLIC_SYNCWORD
|
|
||||||
}
|
|
||||||
return SX127X_LORA_MAC_PRIVATE_SYNCWORD
|
|
||||||
}
|
|
||||||
+1
-1
@@ -2,4 +2,4 @@ package drivers
|
|||||||
|
|
||||||
// Version returns a user-readable string showing the version of the drivers package for support purposes.
|
// 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.
|
// Update this value before release of new version of software.
|
||||||
const Version = "0.24.0"
|
const Version = "0.23.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user