mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-18 05:33:58 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 349b5ca87e |
@@ -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 |
|
||||||
@@ -145,7 +144,7 @@ The following 90 devices are supported.
|
|||||||
| [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 |
|
| [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-transceiv-ers/sx1261) | 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
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !nano_33_ble
|
//go:build !nano_33_ble
|
||||||
|
// +build !nano_33_ble
|
||||||
|
|
||||||
package apds9960
|
package apds9960
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build nano_33_ble
|
//go:build nano_33_ble
|
||||||
|
// +build nano_33_ble
|
||||||
|
|
||||||
package apds9960
|
package apds9960
|
||||||
|
|
||||||
|
|||||||
+5
-115
@@ -7,7 +7,6 @@ package bme280
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"time"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
)
|
)
|
||||||
@@ -34,27 +33,11 @@ type calibrationCoefficients struct {
|
|||||||
h6 int8
|
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.
|
// Device wraps an I2C connection to a BME280 device.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
bus drivers.I2C
|
bus drivers.I2C
|
||||||
Address uint16
|
Address uint16
|
||||||
calibrationCoefficients calibrationCoefficients
|
calibrationCoefficients calibrationCoefficients
|
||||||
Config Config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new BME280 connection. The I2C bus must already be
|
// New creates a new BME280 connection. The I2C bus must already be
|
||||||
@@ -68,34 +51,9 @@ func New(bus drivers.I2C) Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureWithSettings sets up the device for communication and
|
// Configure sets up the device for communication and
|
||||||
// read the calibration coefficients.
|
// read the calibration coefficientes.
|
||||||
//
|
|
||||||
// The default configuration is the Indoor Navigation settings
|
|
||||||
// from the BME280 datasheet.
|
|
||||||
func (d *Device) Configure() {
|
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
|
var data [24]byte
|
||||||
err := d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION, data[:])
|
err := d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION, data[:])
|
||||||
@@ -135,18 +93,10 @@ func (d *Device) ConfigureWithSettings(config Config) {
|
|||||||
d.calibrationCoefficients.h4 = 0 + (int16(h2lsb[3]) << 4) | (int16(h2lsb[4] & 0x0F))
|
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.calibrationCoefficients.h5 = 0 + (int16(h2lsb[5]) << 4) | (int16(h2lsb[4]) >> 4)
|
||||||
|
|
||||||
d.Reset()
|
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.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.
|
// Connected returns whether a BME280 has been found.
|
||||||
@@ -162,20 +112,6 @@ func (d *Device) Reset() {
|
|||||||
d.bus.WriteRegister(uint8(d.Address), CMD_RESET, []byte{0xB6})
|
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)
|
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
|
||||||
func (d *Device) ReadTemperature() (int32, error) {
|
func (d *Device) ReadTemperature() (int32, error) {
|
||||||
data, err := d.readData()
|
data, err := d.readData()
|
||||||
@@ -250,16 +186,6 @@ func readIntLE(msb byte, lsb byte) int16 {
|
|||||||
// readData does a burst read from 0xF7 to 0xF0 according to the datasheet
|
// 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
|
// 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) {
|
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[:])
|
err = d.bus.ReadRegister(uint8(d.Address), REG_PRESSURE, data[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println(err)
|
println(err)
|
||||||
@@ -330,39 +256,3 @@ func (d *Device) calculateHumidity(data [8]byte, tFine int32) int32 {
|
|||||||
return int32(100 * h)
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,50 +20,6 @@ const (
|
|||||||
CHIP_ID = 0x60
|
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 (
|
const (
|
||||||
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
|
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,12 +54,6 @@ func (l *Device) Tone(hz, duration float64) (err error) {
|
|||||||
|
|
||||||
tempo := ((60 / l.BPM) * (duration * 1000))
|
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 {
|
for i := 0.0; i < tempo*1000; i += tone * 2.0 {
|
||||||
if err = l.On(); err != nil {
|
if err = l.On(); err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
package buzzer
|
package buzzer
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Whole = 4.0
|
Whole = 4
|
||||||
Half = 2.0
|
Half = 2
|
||||||
Quarter = 1.0
|
Quarter = 1
|
||||||
Eighth = 0.500
|
Eighth = 0.500
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build tinygo
|
//go:build tinygo
|
||||||
|
// +build tinygo
|
||||||
|
|
||||||
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build mimxrt1062 || stm32f405 || atsamd51 || stm32f103xx || k210 || stm32f407
|
//go:build mimxrt1062 || stm32f405 || atsamd51 || stm32f103xx || k210 || stm32f407
|
||||||
|
// +build mimxrt1062 stm32f405 atsamd51 stm32f103xx k210 stm32f407
|
||||||
|
|
||||||
package dht // import "tinygo.org/x/drivers/dht"
|
package dht // import "tinygo.org/x/drivers/dht"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !mimxrt1062 && !stm32f405 && !atsamd51 && !stm32f103xx && !k210 && !stm32f407
|
//go:build !mimxrt1062 && !stm32f405 && !atsamd51 && !stm32f103xx && !k210 && !stm32f407
|
||||||
|
// +build !mimxrt1062,!stm32f405,!atsamd51,!stm32f103xx,!k210,!stm32f407
|
||||||
|
|
||||||
package dht // import "tinygo.org/x/drivers/dht"
|
package dht // import "tinygo.org/x/drivers/dht"
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,5 @@
|
|||||||
//go:build tinygo
|
//go:build tinygo
|
||||||
|
// +build tinygo
|
||||||
|
|
||||||
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
||||||
//
|
//
|
||||||
@@ -10,7 +11,6 @@ package dht // import "tinygo.org/x/drivers/dht"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"machine"
|
"machine"
|
||||||
"runtime/interrupt"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -160,8 +160,8 @@ func (t *device) read() error {
|
|||||||
// interrupts
|
// interrupts
|
||||||
func receiveSignals(pin machine.Pin, result []counter) {
|
func receiveSignals(pin machine.Pin, result []counter) {
|
||||||
i := uint8(0)
|
i := uint8(0)
|
||||||
mask := interrupt.Disable()
|
machine.UART1.Interrupt.Disable()
|
||||||
defer interrupt.Restore(mask)
|
defer machine.UART1.Interrupt.Enable()
|
||||||
for ; i < 40; i++ {
|
for ; i < 40; i++ {
|
||||||
result[i*2] = expectChange(pin, false)
|
result[i*2] = expectChange(pin, false)
|
||||||
result[i*2+1] = expectChange(pin, true)
|
result[i*2+1] = expectChange(pin, true)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build tinygo
|
//go:build tinygo
|
||||||
|
// +build tinygo
|
||||||
|
|
||||||
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build tinygo
|
//go:build tinygo
|
||||||
|
// +build tinygo
|
||||||
|
|
||||||
package dht // import "tinygo.org/x/drivers/dht"
|
package dht // import "tinygo.org/x/drivers/dht"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build m5stack_core2
|
//go:build m5stack_core2
|
||||||
|
// +build m5stack_core2
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build m5stack_core2
|
//go:build m5stack_core2
|
||||||
|
// +build m5stack_core2
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build atsamd21
|
//go:build atsamd21
|
||||||
|
// +build atsamd21
|
||||||
|
|
||||||
package initdisplay
|
package initdisplay
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build feather_m0 || feather_m4 || feather_m4_can || feather_nrf52840 || feather_nrf52840_sense || feather_stm32f405 || feather_rp2040
|
//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
|
package initdisplay
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build m5stack
|
//go:build m5stack
|
||||||
|
// +build m5stack
|
||||||
|
|
||||||
package initdisplay
|
package initdisplay
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build m5stack_core2
|
//go:build m5stack_core2
|
||||||
|
// +build m5stack_core2
|
||||||
|
|
||||||
package initdisplay
|
package initdisplay
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build pyportal
|
//go:build pyportal
|
||||||
|
// +build pyportal
|
||||||
|
|
||||||
package initdisplay
|
package initdisplay
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build wioterminal
|
//go:build wioterminal
|
||||||
|
// +build wioterminal
|
||||||
|
|
||||||
package initdisplay
|
package initdisplay
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +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/
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,485 +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"
|
|
||||||
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,76 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,35 +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
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
//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})
|
|
||||||
}
|
|
||||||
@@ -1,107 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
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++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 && !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,65 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
loraConf := lora.Config{
|
|
||||||
Freq: FREQ,
|
|
||||||
Bw: lora.Bandwidth_500_0,
|
|
||||||
Sf: lora.SpreadingFactor9,
|
|
||||||
Cr: lora.CodingRate4_7,
|
|
||||||
HeaderType: lora.HeaderExplicit,
|
|
||||||
Preamble: 12,
|
|
||||||
Ldr: lora.LowDataRateOptimizeOff,
|
|
||||||
Iq: lora.IQStandard,
|
|
||||||
Crc: lora.CRCOn,
|
|
||||||
SyncWord: lora.SyncPrivate,
|
|
||||||
LoraTxPowerDBm: 20,
|
|
||||||
}
|
|
||||||
|
|
||||||
loraRadio.LoraConfig(loraConf)
|
|
||||||
|
|
||||||
return loraRadio, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FirmwareVersion() string {
|
|
||||||
return "sx126x"
|
|
||||||
}
|
|
||||||
|
|
||||||
func Lorarx() ([]byte, error) {
|
|
||||||
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
|
||||||
}
|
|
||||||
@@ -1,69 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
loraConf := lora.Config{
|
|
||||||
Freq: FREQ,
|
|
||||||
Bw: lora.Bandwidth_500_0,
|
|
||||||
Sf: lora.SpreadingFactor9,
|
|
||||||
Cr: lora.CodingRate4_7,
|
|
||||||
HeaderType: lora.HeaderExplicit,
|
|
||||||
Preamble: 12,
|
|
||||||
Ldr: lora.LowDataRateOptimizeOff,
|
|
||||||
Iq: lora.IQStandard,
|
|
||||||
Crc: lora.CRCOn,
|
|
||||||
SyncWord: lora.SyncPrivate,
|
|
||||||
LoraTxPowerDBm: 20,
|
|
||||||
}
|
|
||||||
|
|
||||||
loraRadio.LoraConfig(loraConf)
|
|
||||||
|
|
||||||
return loraRadio, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FirmwareVersion() string {
|
|
||||||
return "sx126x"
|
|
||||||
}
|
|
||||||
|
|
||||||
func Lorarx() ([]byte, error) {
|
|
||||||
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
//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)
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
const VERSION = "0.0.1"
|
|
||||||
|
|
||||||
func CurrentVersion() string {
|
|
||||||
return VERSION
|
|
||||||
}
|
|
||||||
@@ -100,8 +100,10 @@ func getCurrentTime(conn *net.UDPSerialConn) (time.Time, error) {
|
|||||||
} else if n == 0 {
|
} else if n == 0 {
|
||||||
continue // no packet received yet
|
continue // no packet received yet
|
||||||
} else if n != NTP_PACKET_SIZE {
|
} 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
|
return parseNTPpacket(), nil
|
||||||
}
|
}
|
||||||
return time.Time{}, errors.New("no packet received after 1 second")
|
return time.Time{}, errors.New("no packet received after 1 second")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build feather_m4 || feather_m4_can || feather_nrf52840
|
//go:build feather_m4 || feather_m4_can || feather_nrf52840
|
||||||
|
// +build feather_m4 feather_m4_can feather_nrf52840
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build grandcentral_m4
|
//go:build grandcentral_m4
|
||||||
|
// +build grandcentral_m4
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build atsamd21 && !p1am_100
|
//go:build atsamd21 && !p1am_100
|
||||||
|
// +build atsamd21,!p1am_100
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build p1am_100
|
//go:build p1am_100
|
||||||
|
// +build p1am_100
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build pygamer
|
//go:build pygamer
|
||||||
|
// +build pygamer
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build pyportal
|
//go:build pyportal
|
||||||
|
// +build pyportal
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build thingplus_rp2040
|
//go:build thingplus_rp2040
|
||||||
|
// +build thingplus_rp2040
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build wioterminal
|
//go:build wioterminal
|
||||||
|
// +build wioterminal
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build tinygo
|
//go:build tinygo
|
||||||
|
// +build tinygo
|
||||||
|
|
||||||
package console
|
package console
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build feather_m4 || feather_m4_can || feather_nrf52840
|
//go:build feather_m4 || feather_m4_can || feather_nrf52840
|
||||||
|
// +build feather_m4 feather_m4_can feather_nrf52840
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build grandcentral_m4
|
//go:build grandcentral_m4
|
||||||
|
// +build grandcentral_m4
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build atsamd21 && !p1am_100
|
//go:build atsamd21 && !p1am_100
|
||||||
|
// +build atsamd21,!p1am_100
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build p1am_100
|
//go:build p1am_100
|
||||||
|
// +build p1am_100
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build pygamer
|
//go:build pygamer
|
||||||
|
// +build pygamer
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build pyportal
|
//go:build pyportal
|
||||||
|
// +build pyportal
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build thingplus_rp2040
|
//go:build thingplus_rp2040
|
||||||
|
// +build thingplus_rp2040
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build wioterminal
|
//go:build wioterminal
|
||||||
|
// +build wioterminal
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -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,17 +36,17 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare for Lora operation
|
// Prepare for Lora operation
|
||||||
loraConf := lora.Config{
|
loraConf := sx126x.LoraConfig{
|
||||||
Freq: FREQ,
|
Freq: FREQ,
|
||||||
Bw: lora.Bandwidth_500_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: 14,
|
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,10 +4,13 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,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()
|
||||||
@@ -43,17 +50,21 @@ func main() {
|
|||||||
panic("sx126x not detected.")
|
panic("sx126x not detected.")
|
||||||
}
|
}
|
||||||
|
|
||||||
loraConf := lora.Config{
|
// Add interrupt handler for Radio IRQs
|
||||||
|
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
|
||||||
|
intr.Enable()
|
||||||
|
|
||||||
|
loraConf := sx126x.LoraConfig{
|
||||||
Freq: FREQ,
|
Freq: FREQ,
|
||||||
Bw: lora.Bandwidth_500_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: 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,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,61 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
//go:build nucleowl55jc
|
//go:build nucleowl55jc
|
||||||
|
// +build nucleowl55jc
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Nucleo WL55JC1
|
Nucleo WL55JC1
|
||||||
RFSwitch
|
RFSwitch
|
||||||
|
|
||||||
+-----------+---------+------------+------------+
|
+-----------+---------+------------+------------+
|
||||||
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
|
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
|
||||||
@@ -13,48 +14,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,106 +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 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,4 +1,5 @@
|
|||||||
//go:build espat
|
//go:build espat
|
||||||
|
// +build espat
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build rtl8720dn
|
//go:build rtl8720dn
|
||||||
|
// +build rtl8720dn
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build wifinina
|
//go:build wifinina
|
||||||
|
// +build wifinina
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -111,8 +111,10 @@ func getCurrentTime(conn *net.UDPSerialConn) (time.Time, error) {
|
|||||||
} else if n == 0 {
|
} else if n == 0 {
|
||||||
continue // no packet received yet
|
continue // no packet received yet
|
||||||
} else if n != NTP_PACKET_SIZE {
|
} 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
|
return parseNTPpacket(), nil
|
||||||
}
|
}
|
||||||
return time.Time{}, errors.New("no packet received after 1 second")
|
return time.Time{}, errors.New("no packet received after 1 second")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build nano_rp2040
|
//go:build nano_rp2040
|
||||||
|
// +build nano_rp2040
|
||||||
|
|
||||||
// This examples shows how to control RGB LED connected to
|
// This examples shows how to control RGB LED connected to
|
||||||
// NINA-W102 chip on Arduino Nano RP2040 Connect board
|
// NINA-W102 chip on Arduino Nano RP2040 Connect board
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
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,4 +1,5 @@
|
|||||||
//go:build arduino
|
//go:build arduino
|
||||||
|
// +build arduino
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build digispark
|
//go:build digispark
|
||||||
|
// +build digispark
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !digispark && !arduino && !qtpy && !m5stamp_c3 && !thingplus_rp2040
|
//go:build !digispark && !arduino && !qtpy && !m5stamp_c3 && !thingplus_rp2040
|
||||||
|
// +build !digispark,!arduino,!qtpy,!m5stamp_c3,!thingplus_rp2040
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build qtpy || m5stamp_c3
|
//go:build qtpy || m5stamp_c3
|
||||||
|
// +build qtpy m5stamp_c3
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build qtpy
|
//go:build qtpy
|
||||||
|
// +build qtpy
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build thingplus_rp2040
|
//go:build thingplus_rp2040
|
||||||
|
// +build thingplus_rp2040
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build atsamd51
|
//go:build atsamd51
|
||||||
|
// +build atsamd51
|
||||||
|
|
||||||
package flash
|
package flash
|
||||||
|
|
||||||
|
|||||||
+4
-33
@@ -13,35 +13,8 @@ import (
|
|||||||
var (
|
var (
|
||||||
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
|
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
|
||||||
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
|
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
|
||||||
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
|
|
||||||
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
|
|
||||||
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
|
|
||||||
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
|
|
||||||
errInvalidGLLSentence = errors.New("invalid GLL NMEA sentence")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GPSError struct {
|
|
||||||
Err error
|
|
||||||
Info string
|
|
||||||
Sentence string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGPSError(err error, sentence string, info string) GPSError {
|
|
||||||
return GPSError{
|
|
||||||
Info: info,
|
|
||||||
Err: err,
|
|
||||||
Sentence: sentence,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ge GPSError) Error() string {
|
|
||||||
return ge.Err.Error() + " " + ge.Info + " " + ge.Sentence
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ge GPSError) Unwrap() error {
|
|
||||||
return ge.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Device wraps a connection to a GPS device.
|
// Device wraps a connection to a GPS device.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
buffer []byte
|
buffer []byte
|
||||||
@@ -154,18 +127,16 @@ func (gps *Device) WriteBytes(bytes []byte) {
|
|||||||
|
|
||||||
// validSentence checks if a sentence has been received uncorrupted
|
// validSentence checks if a sentence has been received uncorrupted
|
||||||
func validSentence(sentence string) error {
|
func validSentence(sentence string) error {
|
||||||
if len(sentence) < 7 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
|
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
|
||||||
|
|||||||
+20
-35
@@ -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,21 +51,18 @@ 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.Altitude = findAltitude(fields[9])
|
fix.Altitude = findAltitude(fields[9])
|
||||||
@@ -66,27 +71,11 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
|
|||||||
fix.Latitude = findLatitude(fields[2], fields[3])
|
fix.Latitude = findLatitude(fields[2], fields[3])
|
||||||
fix.Time = findTime(fields[1])
|
fix.Time = findTime(fields[1])
|
||||||
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
||||||
|
|
||||||
return fix, nil
|
|
||||||
case "GLL":
|
|
||||||
// https://docs.novatel.com/OEM7/Content/Logs/GPGLL.htm
|
|
||||||
fields := strings.Split(sentence, ",")
|
|
||||||
if len(fields) != 8 {
|
|
||||||
return fix, errInvalidGLLSentence
|
|
||||||
}
|
|
||||||
|
|
||||||
fix.Latitude = findLatitude(fields[2], fields[3])
|
|
||||||
fix.Longitude = findLongitude(fields[4], fields[5])
|
|
||||||
fix.Time = findTime(fields[6])
|
|
||||||
|
|
||||||
fix.Valid = (fields[7] == "A")
|
|
||||||
|
|
||||||
return fix, nil
|
|
||||||
case "RMC":
|
case "RMC":
|
||||||
// https://docs.novatel.com/OEM7/Content/Logs/GPRMC.htm
|
|
||||||
fields := strings.Split(sentence, ",")
|
fields := strings.Split(sentence, ",")
|
||||||
if len(fields) != 13 {
|
if len(fields) != 13 {
|
||||||
return fix, errInvalidRMCSentence
|
err = errInvalidRMCSentence
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fix.Longitude = findLongitude(fields[5], fields[6])
|
fix.Longitude = findLongitude(fields[5], fields[6])
|
||||||
@@ -94,12 +83,11 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
|
|||||||
fix.Time = findTime(fields[1])
|
fix.Time = findTime(fields[1])
|
||||||
fix.Speed = findSpeed(fields[7])
|
fix.Speed = findSpeed(fields[7])
|
||||||
fix.Heading = findHeading(fields[8])
|
fix.Heading = findHeading(fields[8])
|
||||||
fix.Valid = (len(fields[2]) > 0 && fields[2] == "A")
|
fix.Valid = (len(fields[2]) > 0 && fields[2][0:1] == "A")
|
||||||
|
default:
|
||||||
return fix, nil
|
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) == 10 {
|
|
||||||
ms, _ = strconv.ParseInt(val[7:10], 10, 16)
|
|
||||||
}
|
|
||||||
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
|
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !nano_33_ble
|
//go:build !nano_33_ble
|
||||||
|
// +build !nano_33_ble
|
||||||
|
|
||||||
package hts221
|
package hts221
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build nano_33_ble
|
//go:build nano_33_ble
|
||||||
|
// +build nano_33_ble
|
||||||
|
|
||||||
package hts221
|
package hts221
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build atsamd51 || atsame5x
|
//go:build atsamd51 || atsame5x
|
||||||
|
// +build atsamd51 atsame5x
|
||||||
|
|
||||||
package i2csoft
|
package i2csoft
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build esp32
|
//go:build esp32
|
||||||
|
// +build esp32
|
||||||
|
|
||||||
package i2csoft
|
package i2csoft
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build nrf52840
|
//go:build nrf52840
|
||||||
|
// +build nrf52840
|
||||||
|
|
||||||
package i2csoft
|
package i2csoft
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !esp32 && !atsamd51 && !atsame5x && !stm32f4 && !rp2040 && !nrf52840
|
//go:build !esp32 && !atsamd51 && !atsame5x && !stm32f4 && !rp2040 && !nrf52840
|
||||||
|
// +build !esp32,!atsamd51,!atsame5x,!stm32f4,!rp2040,!nrf52840
|
||||||
|
|
||||||
package i2csoft
|
package i2csoft
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build rp2040
|
//go:build rp2040
|
||||||
|
// +build rp2040
|
||||||
|
|
||||||
package i2csoft
|
package i2csoft
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build stm32f4
|
//go:build stm32f4
|
||||||
|
// +build stm32f4
|
||||||
|
|
||||||
package i2csoft
|
package i2csoft
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build atsamd51
|
//go:build atsamd51
|
||||||
|
// +build atsamd51
|
||||||
|
|
||||||
package ili9341
|
package ili9341
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !atsamd51 && !atsame5x && !atsamd21
|
//go:build !atsamd51 && !atsame5x && !atsamd21
|
||||||
|
// +build !atsamd51,!atsame5x,!atsamd21
|
||||||
|
|
||||||
package ili9341
|
package ili9341
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build atsamd21
|
//go:build atsamd21
|
||||||
|
// +build atsamd21
|
||||||
|
|
||||||
package ili9341
|
package ili9341
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build atsamd51 || atsame5x
|
//go:build atsamd51 || atsame5x
|
||||||
|
// +build atsamd51 atsame5x
|
||||||
|
|
||||||
package ili9341
|
package ili9341
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build ignore
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build gofuzz
|
//go:build gofuzz
|
||||||
|
// +build gofuzz
|
||||||
|
|
||||||
package png
|
package png
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +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
|
|
||||||
)
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -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,186 +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,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
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user