Compare commits

...

46 Commits

Author SHA1 Message Date
deadprogram 91909cf84a lorawan: increment devnonce don't just create a new one each join attempt
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-16 00:18:38 +01:00
deadprogram 8ff5e7095e sx126x: actually set the frequency when calling SetFrequency()
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 23:10:31 +01:00
deadprogram b0286910c4 lorawan: increment devnonce don't just create a new one each join attempt
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 21:28:30 +01:00
deadprogram 2bb10bf6b9 lorawan: simplify GetRand16() function
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram 632cc964d5 lorawan: set devNonce to new random value on each attempt to join
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram e296fa822b lorawan: increase timeout values for tx/rx
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram d09ee42742 examples: set pin mappings for sx126x external board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram 10458f377f sx126x: pre-define all errors to avoid heap allocations
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram 2174b039a5 sx126x: move RadioController into separate file for clarity
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram 23b0068e53 sx126x: correct RX/TX pin mapping for TheThingsIndustries GNSE board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram 7372929a35 sx126x: there is no dio0 just dio1
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram 68b1b14347 sx126x: refactor to RadioController interface to more easily handle non-STM32WL boards and remove duplicated code
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram 1ba616c6a6 lora/lorawan: set functions for keys
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram c149f3d458 examples: lorawan atcmd outputs needed info after Join to reuse, aka 'OTAA provisioning'
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram b096e7f2db lora/lorawan: completed functions needed for Join OTAA process
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-15 19:29:50 +01:00
deadprogram ae2c06592d gps: improve error handling
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-14 13:27:08 +01:00
deadprogram da791bf9f4 gps: add support for GLL sentence type, add original sentence to gps errors
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-14 12:32:45 +01:00
deadprogram c1637d0b79 gps: improve error handling
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-14 11:15:12 +01:00
sago35 999f42dc03 net/http: add PostForm() 2023-01-11 21:29:12 +01:00
Olivier Fauchon a1b47cd778 lorawan: Add test in Makefile 2023-01-11 07:48:30 +01:00
Olivier Fauchon 889536f7f0 Lorawan uplink support and new lorawan 'basic-demo' example 2023-01-11 07:48:30 +01:00
deadprogram 301e6bc35b examples: improvements to README for LoRaWAN atcmd example
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram 49b390f7bc examples: lorawan atcmd able to join
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram a62fc01d81 lora/lorawan: implement minimal Join() function, and supporting functions
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram 36dfdf568e examples: move atcmd into lorawan
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram 7b5a3970ca lorawan: add initial LoRaWAN stack support
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram 3433364586 examples: example with LoRa AT command set implementation
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-10 22:45:01 +01:00
deadprogram 338be928c9 sx126x, sx127x: remove extra Lora in functions to avoid stuttering
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
deadprogram 98943393a5 sx127x: correct header type implicit/explcit as they were reversed
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
deadprogram 6b817faed3 sx126x: add Reset() and needed pin
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
deadprogram 9b14ee3ec5 sx127x: lora rx/tx example working, now assumes pybadge+featherwing
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
deadprogram 6d51370512 lora: created shared RadioEvent
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
deadprogram 2005d7d483 lora: move shared config for sx126x/sx127x to single package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-09 07:36:07 +01:00
Olivier Fauchon f15eec822f sx127x: Ajout du support d'interruptions RXTimeout (DIO1) 2023-01-09 07:36:07 +01:00
Olivier Fauchon dc4f0c974a sx127x: Major cleanup/rewrite, and tests pass with lorawan stack tests 2023-01-09 07:36:07 +01:00
Olivier Fauchon 28ddb2681d sx127x: Driver for Semtech sx127x radio modules
This first version of the driver has been tested with BB-FRM9x module
2023-01-09 07:36:07 +01:00
BCG 877342d0ff add suport for SH1106 display driver 2022-12-23 09:30:37 +01:00
deadprogram 8b3075fdba build: remove older format build tags
Signed-off-by: deadprogram <ron@hybridgroup.com>
2022-12-21 17:47:14 -05:00
Kenneth Bell 7d58709b7d bme280: improve config support 2022-12-20 11:13:43 +01:00
Jason Del Ponte 789bbbd89f Update DHT22 receive to use runtime/interrupt
Fixes #483 by updating the DHT22 device to use runtime/interrupt instead
of machine.UART1 for interrupt disabling while receiving signals from the
device.
2022-12-20 11:10:52 +01:00
Lucas Bremgartner ea944c8933 buzzer: no tone during rest 2022-12-19 22:01:39 +01:00
Lucas Bremgartner d27859453c buzzer: make all note durations float64 2022-12-19 22:01:39 +01:00
Damian Gryski 0ccd979f23 examples: fix odd if sequence found by static analysis 2022-12-04 21:26:47 +01:00
John Clark a888570c8c fix broken link for lora sx1261 2022-11-23 22:13:43 +01:00
Michael Meister 5b2c2f800d vl53l1x: Add getter for the effective SPAD count
Implement function to get the effective SPAD count.

Signed-off-by: Michael Meister <michael.meister@bytesatwork.ch>
2022-11-15 17:58:24 +01:00
Scott Feldman 0ec887c9d8 wifinina: add support for http server (#480)
* wifinina: add support for http server

This adds http.ListenAndServe() implementation for wifinina.  Included
is an example webserver, basically copied from the rtl8720dn/webserver.

Tested on Arduino Nano RP2040 Connect.  Also tried testing on Arduino
Nano33 IoT but test panics with "out of memory".  There's only 32K of
SRAM on the Nano33, vs 264K on the rp2040 Connect.
2022-11-14 22:24:15 +01:00
134 changed files with 4534 additions and 504 deletions
+3
View File
@@ -249,6 +249,9 @@ endif
@md5sum ./build/test.uf2
tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go
@md5sum ./build/test.uf2
tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/lora/lorawan/atcmd/
@md5sum ./build/test.hex
# rwildcard is a recursive version of $(wildcard)
# https://blog.jgc.org/2011/07/gnu-make-recursive-wildcard-function.html
+4 -3
View File
@@ -112,7 +112,7 @@ The following 90 devices are supported.
| [P1AM-100 Base Controller](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) | SPI |
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
| [PCF8563 real time clock](https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf) | I2C |
| [QMI8658C accelerometer/gyroscope](https://www.qstcorp.com/upload/pdf/202202/%EF%BC%88%E5%B7%B2%E4%BC%A0%EF%BC%89QMI8658C%20datasheet%20rev%200.9.pdf) | I2C |
| [QMI8658C accelerometer/gyroscope](https://www.qstcorp.com/upload/pdf/202202/%EF%BC%88%E5%B7%B2%E4%BC%A0%EF%BC%89QMI8658C%20datasheet%20rev%200.9.pdf) | I2C |
| [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO |
| [RTL8720DN 2.4G/5G Dual Bands Wireless and BLE5.0](https://www.seeedstudio.com/Realtek8720DN-2-4G-5G-Dual-Bands-Wireless-and-BLE5-0-Combo-Module-p-4442.html) | UART |
| [SCD4x CO2 Sensor](https://sensirion.com/media/documents/C4B87CE6/627C2DCD/CD_DS_SCD40_SCD41_Datasheet_D1.pdf) | I2C |
@@ -120,6 +120,7 @@ The following 90 devices are supported.
| [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 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 |
| [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 |
@@ -141,10 +142,10 @@ The following 90 devices are supported.
| [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI |
| [Waveshare 2.9" e-paper display (V1)](https://www.waveshare.com/w/upload/e/e6/2.9inch_e-Paper_Datasheet.pdf) | SPI |
| [Waveshare 4.2" e-paper B/W display](https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf) | SPI |
| [Waveshare GC9A01 TFT round display](https://www.waveshare.com/w/upload/5/5e/GC9A01A.pdf) | SPI |
| [Waveshare GC9A01 TFT round display](https://www.waveshare.com/w/upload/5/5e/GC9A01A.pdf) | SPI |
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
| [XPT2046 touch controller](http://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf) | GPIO |
| [Semtech SX126x Lora](https://www.semtech.com/products/wireless-rf/lora-transceiv-ers/sx1261) | SPI |
| [Semtech SX126x Lora](https://www.semtech.com/products/wireless-rf/lora-connect/sx1261) | SPI |
| [SSD1289 TFT color display](http://aitendo3.sakura.ne.jp/aitendo_data/product_img/lcd/tft2/M032C1289TP/3.2-SSD1289.pdf) | GPIO |
## Contributing
-1
View File
@@ -1,5 +1,4 @@
//go:build !nano_33_ble
// +build !nano_33_ble
package apds9960
-1
View File
@@ -1,5 +1,4 @@
//go:build nano_33_ble
// +build nano_33_ble
package apds9960
+115 -5
View File
@@ -7,6 +7,7 @@ package bme280
import (
"math"
"time"
"tinygo.org/x/drivers"
)
@@ -33,11 +34,27 @@ type calibrationCoefficients struct {
h6 int8
}
type Oversampling byte
type Mode byte
type FilterCoefficient byte
type Period byte
// Config contains settings for filtering, sampling, and modes of operation
type Config struct {
Pressure Oversampling
Temperature Oversampling
Humidity Oversampling
Period Period
Mode Mode
IIR FilterCoefficient
}
// Device wraps an I2C connection to a BME280 device.
type Device struct {
bus drivers.I2C
Address uint16
calibrationCoefficients calibrationCoefficients
Config Config
}
// New creates a new BME280 connection. The I2C bus must already be
@@ -51,9 +68,34 @@ func New(bus drivers.I2C) Device {
}
}
// Configure sets up the device for communication and
// read the calibration coefficientes.
// ConfigureWithSettings sets up the device for communication and
// read the calibration coefficients.
//
// The default configuration is the Indoor Navigation settings
// from the BME280 datasheet.
func (d *Device) Configure() {
d.ConfigureWithSettings(Config{})
}
// ConfigureWithSettings sets up the device for communication and
// read the calibration coefficients.
//
// The default configuration if config is left at defaults is
// the Indoor Navigation settings from the BME280 datasheet.
func (d *Device) ConfigureWithSettings(config Config) {
d.Config = config
// If config is not initialized, use Indoor Navigation defaults.
if d.Config == (Config{}) {
d.Config = Config{
Mode: ModeNormal,
Period: Period0_5ms,
Temperature: Sampling2X,
Humidity: Sampling1X,
Pressure: Sampling16X,
IIR: Coeff16,
}
}
var data [24]byte
err := d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION, data[:])
@@ -93,10 +135,18 @@ func (d *Device) Configure() {
d.calibrationCoefficients.h4 = 0 + (int16(h2lsb[3]) << 4) | (int16(h2lsb[4] & 0x0F))
d.calibrationCoefficients.h5 = 0 + (int16(h2lsb[5]) << 4) | (int16(h2lsb[4]) >> 4)
d.bus.WriteRegister(uint8(d.Address), CTRL_HUMIDITY_ADDR, []byte{0x3f})
d.bus.WriteRegister(uint8(d.Address), CTRL_MEAS_ADDR, []byte{0xB7})
d.bus.WriteRegister(uint8(d.Address), CTRL_CONFIG, []byte{0x00})
d.Reset()
d.bus.WriteRegister(uint8(d.Address), CTRL_CONFIG, []byte{byte(d.Config.Period<<5) | byte(d.Config.IIR<<2)})
d.bus.WriteRegister(uint8(d.Address), CTRL_HUMIDITY_ADDR, []byte{byte(d.Config.Humidity)})
// Normal mode, start measuring now
if d.Config.Mode == ModeNormal {
d.bus.WriteRegister(uint8(d.Address), CTRL_MEAS_ADDR, []byte{
byte(d.Config.Temperature<<5) |
byte(d.Config.Pressure<<2) |
byte(d.Config.Mode)})
}
}
// Connected returns whether a BME280 has been found.
@@ -112,6 +162,20 @@ func (d *Device) Reset() {
d.bus.WriteRegister(uint8(d.Address), CMD_RESET, []byte{0xB6})
}
// SetMode can set the device to Sleep, Normal or Forced mode
//
// Calling this method is optional, Configure can be used to set the
// initial mode if no mode change is desired. This method is most
// useful to switch between Sleep and Normal modes.
func (d *Device) SetMode(mode Mode) {
d.Config.Mode = mode
d.bus.WriteRegister(uint8(d.Address), CTRL_MEAS_ADDR, []byte{
byte(d.Config.Temperature<<5) |
byte(d.Config.Pressure<<2) |
byte(d.Config.Mode)})
}
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (int32, error) {
data, err := d.readData()
@@ -186,6 +250,16 @@ func readIntLE(msb byte, lsb byte) int16 {
// readData does a burst read from 0xF7 to 0xF0 according to the datasheet
// resulting in an slice with 8 bytes 0-2 = pressure / 3-5 = temperature / 6-7 = humidity
func (d *Device) readData() (data [8]byte, err error) {
if d.Config.Mode == ModeForced {
// Write the CTRL_MEAS register to trigger a measurement
d.bus.WriteRegister(uint8(d.Address), CTRL_MEAS_ADDR, []byte{
byte(d.Config.Temperature<<5) |
byte(d.Config.Pressure<<2) |
byte(d.Config.Mode)})
time.Sleep(d.measurementDelay())
}
err = d.bus.ReadRegister(uint8(d.Address), REG_PRESSURE, data[:])
if err != nil {
println(err)
@@ -256,3 +330,39 @@ func (d *Device) calculateHumidity(data [8]byte, tFine int32) int32 {
return int32(100 * h)
}
// measurementDelay returns how much time each measurement will take
// on the device.
//
// This is used in forced mode to wait until a measurement is complete.
func (d *Device) measurementDelay() time.Duration {
const MeasOffset = 1250
const MeasDur = 2300
const HumMeasOffset = 575
const MeasScalingFactor = 1000
// delay is based on over-sampling rate - this table converts from
// setting to number samples
sampleRateConv := []int{0, 1, 2, 4, 8, 16}
tempOsr := 16
if d.Config.Temperature <= Sampling16X {
tempOsr = sampleRateConv[d.Config.Temperature]
}
presOsr := 16
if d.Config.Temperature <= Sampling16X {
presOsr = sampleRateConv[d.Config.Pressure]
}
humOsr := 16
if d.Config.Temperature <= Sampling16X {
humOsr = sampleRateConv[d.Config.Humidity]
}
max_delay := ((MeasOffset + (MeasDur * tempOsr) +
((MeasDur * presOsr) + HumMeasOffset) +
((MeasDur * humOsr) + HumMeasOffset)) / MeasScalingFactor)
return time.Duration(max_delay) * time.Millisecond
}
+44
View File
@@ -20,6 +20,50 @@ const (
CHIP_ID = 0x60
)
// Increasing sampling rate increases precision but also the wait time for measurements. The datasheet has a table of
// suggested values for oversampling, output data rates, and iir filter coefficients by use case.
const (
SamplingOff Oversampling = iota
Sampling1X
Sampling2X
Sampling4X
Sampling8X
Sampling16X
)
// In normal mode (the default) the sensor takes masurements periodically. In forced
// mode, the sensor takes a measurement only when requested.
//
// For use-cases with infrequent sampling, forced mode is more power efficient.
const (
ModeNormal Mode = 0x03
ModeForced Mode = 0x01
ModeSleep Mode = 0x00
)
// IIR filter coefficients, higher values means steadier measurements but slower reaction times
const (
Coeff0 FilterCoefficient = iota
Coeff2
Coeff4
Coeff8
Coeff16
)
// Period of standby in normal mode which controls how often measurements are taken
//
// Note Period10ms and Period20ms are out of sequence, but are per the datasheet
const (
Period0_5ms Period = 0b000
Period62_5ms = 0b001
Period125ms = 0b010
Period250ms = 0b011
Period500ms = 0b100
Period1000ms = 0b101
Period10ms = 0b110
Period20ms = 0b111
)
const (
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
)
+6
View File
@@ -54,6 +54,12 @@ func (l *Device) Tone(hz, duration float64) (err error) {
tempo := ((60 / l.BPM) * (duration * 1000))
// no tone during rest, just let the duration pass.
if hz == Rest {
time.Sleep(time.Duration(tempo) * time.Millisecond)
return
}
for i := 0.0; i < tempo*1000; i += tone * 2.0 {
if err = l.On(); err != nil {
return
+3 -3
View File
@@ -1,9 +1,9 @@
package buzzer
const (
Whole = 4
Half = 2
Quarter = 1
Whole = 4.0
Half = 2.0
Quarter = 1.0
Eighth = 0.500
)
-1
View File
@@ -1,5 +1,4 @@
//go:build tinygo
// +build tinygo
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
-1
View File
@@ -1,5 +1,4 @@
//go:build mimxrt1062 || stm32f405 || atsamd51 || stm32f103xx || k210 || stm32f407
// +build mimxrt1062 stm32f405 atsamd51 stm32f103xx k210 stm32f407
package dht // import "tinygo.org/x/drivers/dht"
-1
View File
@@ -1,5 +1,4 @@
//go:build !mimxrt1062 && !stm32f405 && !atsamd51 && !stm32f103xx && !k210 && !stm32f407
// +build !mimxrt1062,!stm32f405,!atsamd51,!stm32f103xx,!k210,!stm32f407
package dht // import "tinygo.org/x/drivers/dht"
+3 -3
View File
@@ -1,5 +1,4 @@
//go:build tinygo
// +build tinygo
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
@@ -11,6 +10,7 @@ package dht // import "tinygo.org/x/drivers/dht"
import (
"machine"
"runtime/interrupt"
"time"
)
@@ -160,8 +160,8 @@ func (t *device) read() error {
// interrupts
func receiveSignals(pin machine.Pin, result []counter) {
i := uint8(0)
machine.UART1.Interrupt.Disable()
defer machine.UART1.Interrupt.Enable()
mask := interrupt.Disable()
defer interrupt.Restore(mask)
for ; i < 40; i++ {
result[i*2] = expectChange(pin, false)
result[i*2+1] = expectChange(pin, true)
-1
View File
@@ -1,5 +1,4 @@
//go:build tinygo
// +build tinygo
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
-1
View File
@@ -1,5 +1,4 @@
//go:build tinygo
// +build tinygo
package dht // import "tinygo.org/x/drivers/dht"
-1
View File
@@ -1,5 +1,4 @@
//go:build m5stack_core2
// +build m5stack_core2
package main
@@ -1,5 +1,4 @@
//go:build m5stack_core2
// +build m5stack_core2
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build atsamd21
// +build atsamd21
package initdisplay
-1
View File
@@ -1,5 +1,4 @@
//go:build feather_m0 || feather_m4 || feather_m4_can || feather_nrf52840 || feather_nrf52840_sense || feather_stm32f405 || feather_rp2040
// +build feather_m0 feather_m4 feather_m4_can feather_nrf52840 feather_nrf52840_sense feather_stm32f405 feather_rp2040
package initdisplay
-1
View File
@@ -1,5 +1,4 @@
//go:build m5stack
// +build m5stack
package initdisplay
@@ -1,5 +1,4 @@
//go:build m5stack_core2
// +build m5stack_core2
package initdisplay
-1
View File
@@ -1,5 +1,4 @@
//go:build pyportal
// +build pyportal
package initdisplay
@@ -1,5 +1,4 @@
//go:build wioterminal
// +build wioterminal
package initdisplay
+41
View File
@@ -0,0 +1,41 @@
# 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/
```
+485
View File
@@ -0,0 +1,485 @@
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"
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()
}
+76
View File
@@ -0,0 +1,76 @@
// 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"
)
// 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)
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)
}
}
+115
View File
@@ -0,0 +1,115 @@
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
}
@@ -0,0 +1,35 @@
# 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
```
@@ -0,0 +1,11 @@
//go:build !customkeys
package main
// 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})
}
+107
View File
@@ -0,0 +1,107 @@
// 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"
)
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{}
// Initial Lora modulation configuration
loraConf := lora.Config{
Freq: 868100000,
Bw: lora.Bandwidth_125_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
Preamble: 12,
Ldr: lora.LowDataRateOptimizeOff,
Iq: lora.IQStandard,
Crc: lora.CRCOn,
SyncWord: lora.SyncPublic,
LoraTxPowerDBm: 20,
}
radio.LoraConfig(loraConf)
// Connect the lorawan with the Lora Radio device.
lorawan.UseRadio(radio)
// Configure AppEUI, DevEUI, APPKey
setLorawanKeys()
// Try to connect Lorawan network
if err := loraConnect(); err != nil {
failMessage(err)
}
// 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++
}
}
+10
View File
@@ -0,0 +1,10 @@
package common
import (
"errors"
)
var (
errRadioNotFound = errors.New("radio not found")
errRxTimeout = errors.New("radio RX timeout")
)
+40
View File
@@ -0,0 +1,40 @@
//go:build !featherwing && !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
}
+65
View File
@@ -0,0 +1,65 @@
//go:build stm32wlx
package common
import (
"machine"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/sx126x"
)
const (
FREQ = 868100000
LORA_DEFAULT_RXTIMEOUT_MS = 1000
LORA_DEFAULT_TXTIMEOUT_MS = 5000
)
var (
loraRadio *sx126x.Device
)
var spi = machine.SPI3
func newRadioControl() sx126x.RadioController {
return sx126x.NewRadioControl()
}
// do sx126x setup here
func SetupLora() (lora.Radio, error) {
loraRadio = sx126x.New(spi)
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
// Create radio controller for target
loraRadio.SetRadioController(newRadioControl())
if state := loraRadio.DetectDevice(); !state {
return nil, errRadioNotFound
}
loraConf := lora.Config{
Freq: FREQ,
Bw: lora.Bandwidth_500_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
Preamble: 12,
Ldr: lora.LowDataRateOptimizeOff,
Iq: lora.IQStandard,
Crc: lora.CRCOn,
SyncWord: lora.SyncPrivate,
LoraTxPowerDBm: 20,
}
loraRadio.LoraConfig(loraConf)
return loraRadio, nil
}
func FirmwareVersion() string {
return "sx126x"
}
func Lorarx() ([]byte, error) {
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
}
+69
View File
@@ -0,0 +1,69 @@
//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
}
loraConf := lora.Config{
Freq: FREQ,
Bw: lora.Bandwidth_500_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
Preamble: 12,
Ldr: lora.LowDataRateOptimizeOff,
Iq: lora.IQStandard,
Crc: lora.CRCOn,
SyncWord: lora.SyncPrivate,
LoraTxPowerDBm: 20,
}
loraRadio.LoraConfig(loraConf)
return loraRadio, nil
}
func FirmwareVersion() string {
return "sx126x"
}
func Lorarx() ([]byte, error) {
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
}
+85
View File
@@ -0,0 +1,85 @@
//go:build featherwing
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 (
// We assume LoRa Featherwing module is connected to PyBadge:
rstPin = machine.D11
csPin = machine.D10
dio0Pin = machine.D6
dio1Pin = machine.D9
spi = machine.SPI0
loraRadio *sx127x.Device
)
// do sx127x setup here
func SetupLora() (lora.Radio, error) {
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dio0Pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
spi.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
loraRadio = sx127x.New(spi, csPin, rstPin)
loraRadio.Reset()
if state := loraRadio.DetectDevice(); !state {
return nil, errRadioNotFound
}
// Setup DIO0 interrupt Handling
if err := dio0Pin.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO0 pin interrupt:", err.Error())
}
// Setup DIO1 interrupt Handling
if err := dio1Pin.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO1 pin interrupt:", err.Error())
}
// Prepare for Lora Operation
loraConf := lora.Config{
Freq: FREQ,
Bw: lora.Bandwidth_125_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
Preamble: 12,
Iq: lora.IQStandard,
Crc: lora.CRCOn,
SyncWord: lora.SyncPublic,
LoraTxPowerDBm: 20,
}
loraRadio.LoraConfig(loraConf)
return loraRadio, nil
}
func dioIrqHandler(machine.Pin) {
loraRadio.HandleInterrupt()
}
func FirmwareVersion() string {
v := loraRadio.GetVersion()
return "sx127x v" + strconv.Itoa(int(v))
}
func Lorarx() ([]byte, error) {
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
}
+7
View File
@@ -0,0 +1,7 @@
package common
const VERSION = "0.0.1"
func CurrentVersion() string {
return VERSION
}
+1 -3
View File
@@ -100,9 +100,7 @@ func getCurrentTime(conn *net.UDPSerialConn) (time.Time, error) {
} else if n == 0 {
continue // no packet received yet
} else if n != NTP_PACKET_SIZE {
if n != NTP_PACKET_SIZE {
return time.Time{}, fmt.Errorf("expected NTP packet size of %d: %d", NTP_PACKET_SIZE, n)
}
return time.Time{}, fmt.Errorf("expected NTP packet size of %d: %d", NTP_PACKET_SIZE, n)
}
return parseNTPpacket(), nil
}
-1
View File
@@ -1,5 +1,4 @@
//go:build feather_m4 || feather_m4_can || feather_nrf52840
// +build feather_m4 feather_m4_can feather_nrf52840
package main
@@ -1,5 +1,4 @@
//go:build grandcentral_m4
// +build grandcentral_m4
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build atsamd21 && !p1am_100
// +build atsamd21,!p1am_100
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build p1am_100
// +build p1am_100
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build pygamer
// +build pygamer
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build pyportal
// +build pyportal
package main
@@ -1,5 +1,4 @@
//go:build thingplus_rp2040
// +build thingplus_rp2040
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build wioterminal
// +build wioterminal
package main
@@ -1,5 +1,4 @@
//go:build tinygo
// +build tinygo
package console
-1
View File
@@ -1,5 +1,4 @@
//go:build feather_m4 || feather_m4_can || feather_nrf52840
// +build feather_m4 feather_m4_can feather_nrf52840
package main
@@ -1,5 +1,4 @@
//go:build grandcentral_m4
// +build grandcentral_m4
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build atsamd21 && !p1am_100
// +build atsamd21,!p1am_100
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build p1am_100
// +build p1am_100
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build pygamer
// +build pygamer
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build pyportal
// +build pyportal
package main
@@ -1,5 +1,4 @@
//go:build thingplus_rp2040
// +build thingplus_rp2040
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build wioterminal
// +build wioterminal
package main
+56
View File
@@ -0,0 +1,56 @@
//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,8 +5,7 @@ import (
"machine"
"time"
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/sx126x"
)
@@ -23,12 +22,11 @@ func main() {
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
// Create the driver
loraRadio = sx126x.New(machine.SPI3)
loraRadio = sx126x.New(spi)
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
// Create RF Switch
var radioSwitch rfswitch.CustomSwitch
loraRadio.SetRfSwitch(radioSwitch)
// Create radio controller for target
loraRadio.SetRadioController(newRadioControl())
state := loraRadio.DetectDevice()
if !state {
@@ -36,17 +34,17 @@ func main() {
}
// Prepare for Lora operation
loraConf := sx126x.LoraConfig{
loraConf := lora.Config{
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,
Bw: lora.Bandwidth_500_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
Preamble: 12,
Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF,
Iq: sx126x.SX126X_LORA_IQ_STANDARD,
Crc: sx126x.SX126X_LORA_CRC_ON,
SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD,
Ldr: lora.LowDataRateOptimizeOff,
Iq: lora.IQStandard,
Crc: lora.CRCOn,
SyncWord: lora.SyncPrivate,
LoraTxPowerDBm: 14,
}
loraRadio.LoraConfig(loraConf)
@@ -75,6 +73,5 @@ func main() {
loraRadio.SetStandby()
time.Sleep(60 * time.Second)
}
}
+19
View File
@@ -0,0 +1,19 @@
//go:build !stm32wlx
package main
import (
"machine"
"tinygo.org/x/drivers/sx126x"
)
var (
spi = machine.SPI0
nssPin, busyPin, dio1Pin = machine.GP17, machine.GP10, machine.GP11
rxPin, txLowPin, txHighPin = machine.GP13, machine.GP12, machine.GP12
)
func newRadioControl() sx126x.RadioController {
return sx126x.NewRadioControl(nssPin, busyPin, dio1Pin, rxPin, txLowPin, txHighPin)
}
@@ -0,0 +1,15 @@
//go:build stm32wlx
package main
import (
"machine"
"tinygo.org/x/drivers/sx126x"
)
var spi = machine.SPI3
func newRadioControl() sx126x.RadioController {
return sx126x.NewRadioControl()
}
+22 -36
View File
@@ -4,13 +4,10 @@ package main
// module will be in RX mode between two transmissions
import (
"device/stm32"
"machine"
"runtime/interrupt"
"time"
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/sx126x"
)
@@ -26,23 +23,19 @@ var (
txmsg = []byte("Hello TinyGO")
)
// radioIntHandler will take care of radio interrupts
func radioIntHandler(intr interrupt.Interrupt) {
loraRadio.HandleInterrupt()
}
func main() {
time.Sleep(3 * time.Second)
println("\n# TinyGo Lora RX/TX test")
println("# ----------------------")
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
// Create the driver
loraRadio = sx126x.New(machine.SPI3)
loraRadio = sx126x.New(spi)
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
// Create RF Switch
var radioSwitch rfswitch.CustomSwitch
loraRadio.SetRfSwitch(radioSwitch)
// Create radio controller for target
loraRadio.SetRadioController(newRadioControl())
// Detect the device
state := loraRadio.DetectDevice()
@@ -50,21 +43,17 @@ func main() {
panic("sx126x not detected.")
}
// Add interrupt handler for Radio IRQs
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
intr.Enable()
loraConf := sx126x.LoraConfig{
loraConf := lora.Config{
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,
Bw: lora.Bandwidth_500_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
Preamble: 12,
Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF,
Iq: sx126x.SX126X_LORA_IQ_STANDARD,
Crc: sx126x.SX126X_LORA_CRC_ON,
SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD,
Ldr: lora.LowDataRateOptimizeOff,
Iq: lora.IQStandard,
Crc: lora.CRCOn,
SyncWord: lora.SyncPrivate,
LoraTxPowerDBm: 20,
}
@@ -72,23 +61,20 @@ func main() {
var count uint
for {
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)
start := time.Now()
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 {
println("RX Error: ", err)
} else if buf != nil {
println("Packet Received: len=", len(buf), string(buf))
}
}
println("END Lora RX")
println("LORA TX size=", len(txmsg))
err := loraRadio.LoraTx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS)
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)
}
+29
View File
@@ -0,0 +1,29 @@
//go:build !stm32wlx
package main
import (
"machine"
"tinygo.org/x/drivers/sx126x"
)
var (
spi = machine.SPI1
nssPin, busyPin, dio1Pin = machine.GP13, machine.GP6, machine.GP7
rxPin, txLowPin, txHighPin = machine.GP9, machine.GP8, machine.GP8
)
func newRadioControl() sx126x.RadioController {
return sx126x.NewRadioControl(nssPin, busyPin, dio1Pin, rxPin, txLowPin, txHighPin)
}
func init() {
spi.Configure(machine.SPIConfig{
Mode: 0,
Frequency: 8 * 1e6,
SDO: machine.SPI1_SDO_PIN,
SDI: machine.SPI1_SDI_PIN,
SCK: machine.SPI1_SCK_PIN,
})
}
@@ -0,0 +1,15 @@
//go:build stm32wlx
package main
import (
"machine"
"tinygo.org/x/drivers/sx126x"
)
var spi = machine.SPI3
func newRadioControl() sx126x.RadioController {
return sx126x.NewRadioControl()
}
-61
View File
@@ -1,61 +0,0 @@
//go:build gnse
// +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
}
-43
View File
@@ -1,43 +0,0 @@
//go:build lorae5
// +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
}
+106
View File
@@ -0,0 +1,106 @@
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 FREQ = 868100000
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_PIN_CS.Configure(machine.PinConfig{Mode: machine.PinOutput})
SX127X_PIN_DIO0.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
SX127X_PIN_DIO1.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
SX127X_SPI.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
println("main: create and start SX127x driver")
loraRadio = sx127x.New(SX127X_SPI, SX127X_PIN_CS, SX127X_PIN_RST)
loraRadio.Reset()
state := loraRadio.DetectDevice()
if !state {
panic("main: sx127x NOT FOUND !!!")
} else {
println("main: sx127x found")
}
// Setup DIO0 interrupt Handling
if err := SX127X_PIN_DIO0.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO0 pin interrupt:", err.Error())
}
// Setup DIO1 interrupt Handling
if err := SX127X_PIN_DIO1.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO1 pin interrupt:", err.Error())
}
// Prepare for Lora Operation
loraConf := lora.Config{
Freq: FREQ,
Bw: lora.Bandwidth_500_0,
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++
}
}
-1
View File
@@ -1,5 +1,4 @@
//go:build espat
// +build espat
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build rtl8720dn
// +build rtl8720dn
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build wifinina
// +build wifinina
package main
+1 -3
View File
@@ -111,9 +111,7 @@ func getCurrentTime(conn *net.UDPSerialConn) (time.Time, error) {
} else if n == 0 {
continue // no packet received yet
} else if n != NTP_PACKET_SIZE {
if n != NTP_PACKET_SIZE {
return time.Time{}, fmt.Errorf("expected NTP packet size of %d: %d", NTP_PACKET_SIZE, n)
}
return time.Time{}, fmt.Errorf("expected NTP packet size of %d: %d", NTP_PACKET_SIZE, n)
}
return parseNTPpacket(), nil
}
-1
View File
@@ -1,5 +1,4 @@
//go:build nano_rp2040
// +build nano_rp2040
// This examples shows how to control RGB LED connected to
// NINA-W102 chip on Arduino Nano RP2040 Connect board
+209
View File
@@ -0,0 +1,209 @@
package main
import (
"fmt"
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/net/http"
"tinygo.org/x/drivers/wifinina"
)
// You can override the settings with the init() in another source code:
//
// func init() {
// ssid = "your-ssid"
// pass = "your-password"
// }
//
// Or use -ldflags option on tinygo command to set at compile-time:
//
// tinygo flash ... -ldflags '-X "main.ssid=xxx" -X "main.pass=xxx"' ...
//
var (
ssid string
pass string
)
var led = machine.LED
func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
spi := machine.NINA_SPI
spi.Configure(machine.SPIConfig{
Frequency: 8 * 1e6,
SDO: machine.NINA_SDO,
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
adaptor := wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
time.Sleep(2 * time.Second)
println("Connecting to " + ssid)
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil {
return err
}
println("Connected.")
time.Sleep(2 * time.Second)
ip, subnet, gateway, err := adaptor.GetIP()
if err != nil {
return err
}
fmt.Printf("IP Address : %s\r\n", ip)
fmt.Printf("Mask : %s\r\n", subnet)
fmt.Printf("Gateway : %s\r\n", gateway)
http.UseDriver(adaptor)
http.HandleFunc("/", root)
http.HandleFunc("/hello", hello)
http.HandleFunc("/cnt", cnt)
http.HandleFunc("/6", sixlines)
http.HandleFunc("/off", LED_OFF)
http.HandleFunc("/on", LED_ON)
return http.ListenAndServe(":80", nil)
}
func root(w http.ResponseWriter, r *http.Request) {
access := 1
cookie, err := r.Cookie("access")
if err != nil {
if err == http.ErrNoCookie {
cookie = &http.Cookie{
Name: "access",
Value: "1",
}
} else {
http.Error(w, fmt.Sprintf("%s", err.Error()), http.StatusBadRequest)
return
}
} else {
v, err := strconv.ParseInt(cookie.Value, 10, 0)
if err != nil {
http.Error(w, fmt.Sprintf("invalid cookie.Value : %s", cookie.Value), http.StatusBadRequest)
return
}
cookie.Value = fmt.Sprintf("%d", v+1)
access = int(v) + 1
}
http.SetCookie(w, cookie)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
<html>
<head>
<title>TinyGo HTTP Server</title>
<script language="javascript" type="text/javascript">
var counter = 0
function ledOn() { fetch("/on"); }
function ledOff() { fetch("/off"); }
function fetchCnt() { fetch("/cnt").then(response => response.json()).then(json => { counter = json.cnt; cnt.innerHTML = counter; }); }
function incrCnt() { counter = counter + 1; fetch("/cnt?cnt=" + counter, { method: 'POST' }).then(response => response.json()).then(json => { counter = json.cnt; cnt.innerHTML = counter; }); }
function setCnt() { fetch("/cnt", {
method: "POST",
body: "cnt=" + document.getElementsByName("cnt")[0].value,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}).then(response => response.json()).then(json => { counter = json.cnt; cnt.innerHTML = counter; }); return false; }
function onLoad() { fetchCnt(); }
</script>
</head>
<body onLoad="onLoad()">
<h5>TinyGo HTTP Server</h5>
<p>
access: %d
</p>
<a href="/hello">/hello</a><br>
<a href="/6">/6</a><br>
<p>
LED<br>
<a href="javascript:ledOn();">/on</a><br>
<a href="javascript:ledOff();">/off</a><br>
</p>
<p>
<a href="/cnt">/cnt</a><br>
cnt: <span id="cnt"></span><br>
<a href="javascript:incrCnt()">incrCnt()</a><br>
<form id="form1" style="display: inline" onSubmit="return setCnt()">
<input type="text" name="cnt">
<input type="button" value="set cnt", onClick="setCnt()">
</form>
</p>
</body>
</html>
`, access)
}
func sixlines(w http.ResponseWriter, r *http.Request) {
// https://fukuno.jig.jp/3267
fmt.Fprint(w, `<body onload='onkeydown=e=>K=parseInt(e.key[5]||6,28)/3-8;Z=X=[B=A=12];Y=_=>`+
`{for(C=[q=c=i=4];f=i--*K;c-=!Z[h+(K+6?p+K:C[i]=p*A-(p/9|0)*145)])p=B[i];for(c?0:K+6?h+=K:B=C;`+
`i=K=q--;f+=Z[A+p])X[p=h+B[q]]=1;h+=A;if(f|B)for(Z=X,X=[l=228],B=[[-7,-20,6,h=17,-9,3,3][t=++t%7]-4,0,1,t-6?-A:2];l--;)`+
`for(l%A?l-=l%A*!Z[l]:(P++,c=l+=A);--c>A;)Z[c]=Z[c-A];for(S="";i<240;S+=X[i]|(X[i]=Z[i]|=++i%A<2|i>228)?i%A?"■":"■<br>":" ");`+
`D.innerHTML=S+P;setTimeout(Y,i-P)};Y(h=K=t=P=0)'id=D>`)
}
func LED_ON(w http.ResponseWriter, r *http.Request) {
led.High()
w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
fmt.Fprintf(w, "led.High()")
}
func LED_OFF(w http.ResponseWriter, r *http.Request) {
led.Low()
w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
fmt.Fprintf(w, "led.Low()")
}
func hello(w http.ResponseWriter, r *http.Request) {
w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
fmt.Fprintf(w, "hello")
}
var counter int
func cnt(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
if r.Method == "POST" {
c := r.Form.Get("cnt")
if c != "" {
i64, _ := strconv.ParseInt(c, 0, 0)
counter = int(i64)
}
}
w.Header().Set(`Content-Type`, `application/json`)
fmt.Fprintf(w, `{"cnt": %d}`, counter)
}
func message(msg string) {
println(msg, "\r")
}
-1
View File
@@ -1,5 +1,4 @@
//go:build arduino
// +build arduino
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build digispark
// +build digispark
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build !digispark && !arduino && !qtpy && !m5stamp_c3 && !thingplus_rp2040
// +build !digispark,!arduino,!qtpy,!m5stamp_c3,!thingplus_rp2040
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build qtpy || m5stamp_c3
// +build qtpy m5stamp_c3
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build qtpy
// +build qtpy
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build thingplus_rp2040
// +build thingplus_rp2040
package main
-1
View File
@@ -1,5 +1,4 @@
//go:build atsamd51
// +build atsamd51
package flash
+33 -4
View File
@@ -13,8 +13,35 @@ import (
var (
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
errInvalidGLLSentence = errors.New("invalid GLL NMEA sentence")
)
type GPSError struct {
Err error
Info string
Sentence string
}
func newGPSError(err error, sentence string, info string) GPSError {
return GPSError{
Info: info,
Err: err,
Sentence: sentence,
}
}
func (ge GPSError) Error() string {
return ge.Err.Error() + " " + ge.Info + " " + ge.Sentence
}
func (ge GPSError) Unwrap() error {
return ge.Err
}
// Device wraps a connection to a GPS device.
type Device struct {
buffer []byte
@@ -127,16 +154,18 @@ func (gps *Device) WriteBytes(bytes []byte) {
// validSentence checks if a sentence has been received uncorrupted
func validSentence(sentence string) error {
if len(sentence) < 4 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
if len(sentence) < 7 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
return errInvalidNMEASentenceLength
}
var cs byte = 0
for i := 1; i < len(sentence)-3; i++ {
cs ^= sentence[i]
}
checksum := hex.EncodeToString([]byte{cs})
if (checksum[0] != sentence[len(sentence)-2]) || (checksum[1] != sentence[len(sentence)-1]) {
return errInvalidNMEAChecksum
checksum := strings.ToUpper(hex.EncodeToString([]byte{cs}))
if checksum != sentence[len(sentence)-2:len(sentence)] {
return newGPSError(errInvalidNMEAChecksum, sentence,
"expected "+sentence[len(sentence)-2:len(sentence)]+
" got "+checksum)
}
return nil
+35 -20
View File
@@ -1,19 +1,11 @@
package gps
import (
"errors"
"strconv"
"strings"
"time"
)
var (
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
)
// Parser for GPS NMEA sentences.
type Parser struct {
}
@@ -51,18 +43,21 @@ func NewParser() Parser {
}
// Parse parses a NMEA sentence looking for fix info.
func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
func (parser *Parser) Parse(sentence string) (Fix, error) {
var fix Fix
if sentence == "" {
err = errEmptyNMEASentence
return
return fix, errEmptyNMEASentence
}
if len(sentence) < 6 {
return fix, errInvalidNMEASentenceLength
}
typ := sentence[3:6]
switch typ {
case "GGA":
// https://docs.novatel.com/OEM7/Content/Logs/GPGGA.htm
fields := strings.Split(sentence, ",")
if len(fields) != 15 {
err = errInvalidGGASentence
return
return fix, errInvalidGGASentence
}
fix.Altitude = findAltitude(fields[9])
@@ -71,11 +66,27 @@ func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
fix.Latitude = findLatitude(fields[2], fields[3])
fix.Time = findTime(fields[1])
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
return fix, nil
case "GLL":
// https://docs.novatel.com/OEM7/Content/Logs/GPGLL.htm
fields := strings.Split(sentence, ",")
if len(fields) != 8 {
return fix, errInvalidGLLSentence
}
fix.Latitude = findLatitude(fields[2], fields[3])
fix.Longitude = findLongitude(fields[4], fields[5])
fix.Time = findTime(fields[6])
fix.Valid = (fields[7] == "A")
return fix, nil
case "RMC":
// https://docs.novatel.com/OEM7/Content/Logs/GPRMC.htm
fields := strings.Split(sentence, ",")
if len(fields) != 13 {
err = errInvalidRMCSentence
return
return fix, errInvalidRMCSentence
}
fix.Longitude = findLongitude(fields[5], fields[6])
@@ -83,11 +94,12 @@ func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
fix.Time = findTime(fields[1])
fix.Speed = findSpeed(fields[7])
fix.Heading = findHeading(fields[8])
fix.Valid = (len(fields[2]) > 0 && fields[2][0:1] == "A")
default:
err = errUnknownNMEASentence
fix.Valid = (len(fields[2]) > 0 && fields[2] == "A")
return fix, nil
}
return
return fix, newGPSError(errUnknownNMEASentence, sentence, typ)
}
// findTime returns the time from an NMEA sentence:
@@ -100,7 +112,10 @@ func findTime(val string) time.Time {
h, _ := strconv.ParseInt(val[0:2], 10, 8)
m, _ := strconv.ParseInt(val[2:4], 10, 8)
s, _ := strconv.ParseInt(val[4:6], 10, 8)
ms, _ := strconv.ParseInt(val[7:10], 10, 16)
ms := int64(0)
if len(val) == 10 {
ms, _ = strconv.ParseInt(val[7:10], 10, 16)
}
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
return t
-1
View File
@@ -1,5 +1,4 @@
//go:build !nano_33_ble
// +build !nano_33_ble
package hts221
-1
View File
@@ -1,5 +1,4 @@
//go:build nano_33_ble
// +build nano_33_ble
package hts221
-1
View File
@@ -1,5 +1,4 @@
//go:build atsamd51 || atsame5x
// +build atsamd51 atsame5x
package i2csoft
-1
View File
@@ -1,5 +1,4 @@
//go:build esp32
// +build esp32
package i2csoft
-1
View File
@@ -1,5 +1,4 @@
//go:build nrf52840
// +build nrf52840
package i2csoft
-1
View File
@@ -1,5 +1,4 @@
//go:build !esp32 && !atsamd51 && !atsame5x && !stm32f4 && !rp2040 && !nrf52840
// +build !esp32,!atsamd51,!atsame5x,!stm32f4,!rp2040,!nrf52840
package i2csoft
-1
View File
@@ -1,5 +1,4 @@
//go:build rp2040
// +build rp2040
package i2csoft
-1
View File
@@ -1,5 +1,4 @@
//go:build stm32f4
// +build stm32f4
package i2csoft
-1
View File
@@ -1,5 +1,4 @@
//go:build atsamd51
// +build atsamd51
package ili9341
-1
View File
@@ -1,5 +1,4 @@
//go:build !atsamd51 && !atsame5x && !atsamd21
// +build !atsamd51,!atsame5x,!atsamd21
package ili9341
-1
View File
@@ -1,5 +1,4 @@
//go:build atsamd21
// +build atsamd21
package ili9341
-1
View File
@@ -1,5 +1,4 @@
//go:build atsamd51 || atsame5x
// +build atsamd51 atsame5x
package ili9341
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build ignore
// +build ignore
package main
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gofuzz
// +build gofuzz
package png
+78
View File
@@ -0,0 +1,78 @@
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
)
+101
View File
@@ -0,0 +1,101 @@
package lorawan
import (
"errors"
"tinygo.org/x/drivers/lora"
)
var (
ErrNoJoinAcceptReceived = errors.New("no JoinAccept packet received")
ErrNoRadioAttached = errors.New("no LoRa radio attached")
ErrInvalidEuiLength = errors.New("invalid EUI length")
ErrInvalidAppKeyLength = errors.New("invalid AppKey length")
ErrInvalidPacketLength = errors.New("invalid packet length")
ErrInvalidDevAddrLength = errors.New("invalid DevAddr length")
ErrInvalidMic = errors.New("invalid Mic")
ErrFrmPayloadTooLarge = errors.New("FRM payload too large")
ErrInvalidNetIDLength = errors.New("invalid NetID length")
ErrInvalidNwkSKeyLength = errors.New("invalid NwkSKey length")
ErrInvalidAppSKeyLength = errors.New("invalid AppSKey length")
)
const (
LORA_TX_TIMEOUT = 2000
LORA_RX_TIMEOUT = 5000
)
var (
ActiveRadio lora.Radio
Retries = 15
)
func UseRadio(r lora.Radio) {
if ActiveRadio != nil {
panic("lorawan.ActiveRadio is already set")
}
ActiveRadio = r
}
func Join(otaa *Otaa, session *Session) error {
var resp []uint8
if ActiveRadio == nil {
return ErrNoRadioAttached
}
otaa.Init()
// Send join packet
payload, err := otaa.GenerateJoinRequest()
if err != nil {
return err
}
ActiveRadio.SetCrc(true)
ActiveRadio.SetIqMode(0) // IQ Standard
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
if err != nil {
return err
}
// Wait for JoinAccept
ActiveRadio.SetIqMode(1) // IQ Inverted
for i := 0; i < Retries; i++ {
resp, err = ActiveRadio.Rx(LORA_RX_TIMEOUT)
if err != nil {
return err
}
if resp != nil {
break
}
}
if resp == nil {
return ErrNoJoinAcceptReceived
}
err = otaa.DecodeJoinAccept(resp, session)
if err != nil {
return err
}
return nil
}
func SendUplink(data []uint8, session *Session) error {
payload, err := session.GenMessage(0, []byte(data))
if err != nil {
return err
}
ActiveRadio.SetCrc(true)
ActiveRadio.SetIqMode(0) // IQ Standard
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
if err != nil {
return err
}
return nil
}
func ListenDownlink() error {
return nil
}
+20
View File
@@ -0,0 +1,20 @@
package lorawan
import "crypto/rand"
// reverseBytes reverses order of a given byte slice
func reverseBytes(s []byte) []byte {
result := make([]byte, len(s))
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = s[j], s[i]
}
return result
}
// GetRand16 returns 2 random bytes
func GetRand16() ([2]uint8, error) {
var randomBytes [2]byte
_, err := rand.Read(randomBytes[:])
return randomBytes, err
}
+253
View File
@@ -0,0 +1,253 @@
package lorawan
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"hash"
"unsafe"
)
type cmacHash struct {
ciph cipher.Block
k1 []byte
k2 []byte
data []byte
x []byte
}
const (
Size = aes.BlockSize
blockSize = Size
)
var (
subkeyZero []byte
subkeyRb []byte
)
func init() {
subkeyZero = bytes.Repeat([]byte{0x00}, blockSize)
subkeyRb = append(bytes.Repeat([]byte{0x00}, blockSize-1), 0x87)
}
// New returns an AES-CMAC hash using the supplied key. The key must be 16, 24,
// or 32 bytes long.
func NewCmac(key []byte) (hash.Hash, error) {
// Create a cipher.
ciph, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Set up the hash object.
h := &cmacHash{ciph: ciph}
h.k1, h.k2 = generateSubkeys(ciph)
h.Reset()
return h, nil
}
func Xor(dst []byte, a []byte, b []byte) error {
if len(dst) != len(a) || len(a) != len(b) {
panic("crypto/Xor: bad length")
}
for i, _ := range a {
dst[i] = a[i] ^ b[i]
}
return nil
}
func (h *cmacHash) Reset() {
h.data = h.data[:0]
h.x = make([]byte, blockSize)
}
func (h *cmacHash) BlockSize() int {
return h.ciph.BlockSize()
}
func (h *cmacHash) Size() int {
return h.ciph.BlockSize()
}
func (h *cmacHash) Sum(b []byte) []byte {
dataLen := len(h.data)
// We should have at most one block left.
if dataLen > blockSize {
panic("cmacHash err1")
}
// Calculate M_last.
mLast := make([]byte, blockSize)
if dataLen == blockSize {
Xor(mLast, h.data, h.k1)
} else {
// TODO(jacobsa): Accept a destination buffer in common.PadBlock and
// simplify this code.
Xor(mLast, PadBlock(h.data), h.k2)
}
y := make([]byte, blockSize)
Xor(y, mLast, h.x)
result := make([]byte, blockSize)
h.ciph.Encrypt(result, y)
b = append(b, result...)
return b
}
func (h *cmacHash) Write(p []byte) (n int, err error) {
n = len(p)
// First step: consume enough data to expand h.data to a full block, if
// possible.
{
toConsume := blockSize - len(h.data)
if toConsume > len(p) {
toConsume = len(p)
}
h.data = append(h.data, p[:toConsume]...)
p = p[toConsume:]
}
// If there's no data left in p, it means h.data might not be a full block.
// Even if it is, we're not sure it's the final block, which we must treat
// specially. So we must stop here.
if len(p) == 0 {
return
}
// h.data is a full block and is not the last; process it.
h.writeBlocks(h.data)
h.data = h.data[:0]
// Consume any further full blocks in p that we're sure aren't the last. Note
// that we're sure that len(p) is greater than zero here.
blocksToProcess := (len(p) - 1) / blockSize
bytesToProcess := blocksToProcess * blockSize
h.writeBlocks(p[:bytesToProcess])
p = p[bytesToProcess:]
// Store the rest for later.
h.data = append(h.data, p...)
return
}
func (h *cmacHash) writeBlocks(p []byte) {
y := make([]byte, blockSize)
for off := 0; off < len(p); off += blockSize {
block := p[off : off+blockSize]
xorBlock(
unsafe.Pointer(&y[0]),
unsafe.Pointer(&h.x[0]),
unsafe.Pointer(&block[0]))
h.ciph.Encrypt(h.x, y)
}
return
}
func xorBlock(
dstPtr unsafe.Pointer,
aPtr unsafe.Pointer,
bPtr unsafe.Pointer) {
// Check assumptions. (These are compile-time constants, so this should
// compile out.)
const wordSize = unsafe.Sizeof(uintptr(0))
if blockSize != 4*wordSize {
panic("xorBlock err1")
}
// Convert.
a := (*[4]uintptr)(aPtr)
b := (*[4]uintptr)(bPtr)
dst := (*[4]uintptr)(dstPtr)
// Compute.
dst[0] = a[0] ^ b[0]
dst[1] = a[1] ^ b[1]
dst[2] = a[2] ^ b[2]
dst[3] = a[3] ^ b[3]
}
func PadBlock(block []byte) []byte {
blockLen := len(block)
if blockLen >= aes.BlockSize {
panic("PadBlock input must be less than 16 bytes.")
}
result := make([]byte, aes.BlockSize)
copy(result, block)
result[blockLen] = 0x80
return result
}
// Given the supplied cipher, whose block size must be 16 bytes, return two
// subkeys that can be used in MAC generation. See section 5.3 of NIST SP
// 800-38B. Note that the other NIST-approved block size of 8 bytes is not
// supported by this function.
func generateSubkeys(ciph cipher.Block) (k1 []byte, k2 []byte) {
if ciph.BlockSize() != blockSize {
panic("generateSubkeys requires a cipher with a block size of 16 bytes.")
}
// Step 1
l := make([]byte, blockSize)
ciph.Encrypt(l, subkeyZero)
// Step 2: Derive the first subkey.
if Msb(l) == 0 {
// TODO(jacobsa): Accept a destination buffer in ShiftLeft and then hoist
// the allocation in the else branch below.
k1 = ShiftLeft(l)
} else {
k1 = make([]byte, blockSize)
Xor(k1, ShiftLeft(l), subkeyRb)
}
// Step 3: Derive the second subkey.
if Msb(k1) == 0 {
k2 = ShiftLeft(k1)
} else {
k2 = make([]byte, blockSize)
Xor(k2, ShiftLeft(k1), subkeyRb)
}
return
}
func ShiftLeft(b []byte) []byte {
l := len(b)
if l == 0 {
panic("shiftLeft requires a non-empty buffer.")
}
output := make([]byte, l)
overflow := byte(0)
for i := int(l - 1); i >= 0; i-- {
output[i] = b[i] << 1
output[i] |= overflow
overflow = (b[i] & 0x80) >> 7
}
return output
}
// Msb returns the most significant bit of the supplied data (which must be
// non-empty). This is the MSB(L) function of RFC 4493.
func Msb(buf []byte) uint8 {
if len(buf) == 0 {
panic("msb requires non-empty buffer.")
}
return buf[0] >> 7
}
+38
View File
@@ -0,0 +1,38 @@
package lorawan
import (
"encoding/binary"
)
// genPayloadMIC computes MIC given the payload and the key
func genPayloadMIC(payload []uint8, key [16]uint8) [4]uint8 {
var mic [4]uint8
hash, _ := NewCmac(key[:])
hash.Write(payload)
hb := hash.Sum([]byte{})
copy(mic[:], hb[0:4])
return mic
}
func calcMessageMIC(payload []uint8, key [16]uint8, dir uint8, addr []byte, fCnt uint32, lenMessage uint8) [4]uint8 {
var b0 []byte
b0 = append(b0, 0x49, 0x00, 0x00, 0x00, 0x00)
b0 = append(b0, dir)
b0 = append(b0, addr[:]...)
var b [4]byte
binary.LittleEndian.PutUint32(b[:], fCnt)
b0 = append(b0, b[:]...)
b0 = append(b0, 0x00)
b0 = append(b0, lenMessage)
var full []byte
full = append(full, b0...)
full = append(full, payload...)
var mic [4]uint8
hash, _ := NewCmac(key[:])
hash.Write(full)
hb := hash.Sum([]byte{})
copy(mic[:], hb[0:4])
return mic
}
+186
View File
@@ -0,0 +1,186 @@
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
}
+137
View File
@@ -0,0 +1,137 @@
package lorawan
import (
"crypto/aes"
"encoding/binary"
"encoding/hex"
"math"
)
// Session is used to store session data of a LoRaWAN session
type Session struct {
NwkSKey [16]uint8
AppSKey [16]uint8
DevAddr [4]uint8
FCntDown uint32
FCntUp uint32
CFList [16]uint8
RXDelay uint8
DLSettings uint8
}
// SetDevAddr configures the Session DevAddr
func (s *Session) SetDevAddr(devAddr []uint8) error {
if len(devAddr) != 4 {
return ErrInvalidDevAddrLength
}
copy(s.DevAddr[:], devAddr)
return nil
}
// GetDevAddr returns the Session DevAddr
func (s *Session) GetDevAddr() string {
return hex.EncodeToString(s.DevAddr[:])
}
// SetNwkSKey configures the Session NwkSKey
func (s *Session) SetNwkSKey(nwkSKey []uint8) error {
if len(nwkSKey) != 16 {
return ErrInvalidNwkSKeyLength
}
copy(s.NwkSKey[:], nwkSKey)
return nil
}
// GetNwkSKey returns the Session NwkSKey
func (s *Session) GetNwkSKey() string {
return hex.EncodeToString(s.NwkSKey[:])
}
// SetAppSKey configures the Session AppSKey
func (s *Session) SetAppSKey(appSKey []uint8) error {
if len(appSKey) != 16 {
return ErrInvalidAppSKeyLength
}
copy(s.AppSKey[:], appSKey)
return nil
}
// GetAppSKey returns the Session AppSKey
func (s *Session) GetAppSKey() string {
return hex.EncodeToString(s.AppSKey[:])
}
// GenMessage generates an uplink message.
func (s *Session) GenMessage(dir uint8, payload []uint8) ([]uint8, error) {
var buf []uint8
buf = append(buf, 0b01000000) // FHDR Unconfirmed up
buf = append(buf, s.DevAddr[:]...)
// FCtl : No ADR, No RFU, No ACK, No FPending, No FOpt
buf = append(buf, 0x00)
// FCnt Up
buf = append(buf, uint8(s.FCntUp&0xFF), uint8((s.FCntUp>>8)&0xFF))
// FPort=1
buf = append(buf, 0x01)
fCnt := uint32(0)
if dir == 0 {
fCnt = s.FCntUp
s.FCntUp++
} else {
fCnt = s.FCntDown
}
data, err := s.genFRMPayload(dir, fCnt, payload, false)
if err != nil {
return nil, err
}
buf = append(buf, data[:]...)
mic := calcMessageMIC(buf, s.NwkSKey, dir, s.DevAddr[:], fCnt, uint8(len(buf)))
buf = append(buf, mic[:]...)
return buf, nil
}
func (s *Session) genFRMPayload(dir uint8, fCnt uint32, payload []byte, isFOpts bool) ([]byte, error) {
k := len(payload) / aes.BlockSize
if len(payload)%aes.BlockSize != 0 {
k++
}
if k > math.MaxUint8 {
return nil, ErrFrmPayloadTooLarge
}
encrypted := make([]byte, 0, k*16)
cipher, err := aes.NewCipher(s.AppSKey[:])
if err != nil {
panic(err)
}
var a [aes.BlockSize]byte
a[0] = 0x01
a[5] = dir
copy(a[6:10], s.DevAddr[:])
binary.LittleEndian.PutUint32(a[10:14], fCnt)
var ss [aes.BlockSize]byte
var b [aes.BlockSize]byte
for i := uint8(0); i < uint8(k); i++ {
copy(b[:], payload[i*aes.BlockSize:])
if !isFOpts {
a[15] = i + 1
}
cipher.Encrypt(ss[:], a[:])
for j := 0; j < aes.BlockSize; j++ {
b[j] = b[j] ^ ss[j]
}
encrypted = append(encrypted, b[:]...)
}
return encrypted[:len(payload)], nil
}
+14
View File
@@ -0,0 +1,14 @@
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)
LoraConfig(cnf Config)
}

Some files were not shown because too many files have changed in this diff Show More