lora/lorawan: implement minimal Join() function, and supporting functions

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram
2023-01-08 19:42:19 +01:00
committed by Ron Evans
parent 36dfdf568e
commit a62fc01d81
4 changed files with 160 additions and 44 deletions
+59 -3
View File
@@ -1,8 +1,16 @@
package lorawan
import "tinygo.org/x/drivers/lora"
import (
"errors"
var ActiveRadio lora.Radio
"tinygo.org/x/drivers/lora"
)
const LORA_RXTX_TIMEOUT = 1000
var (
ActiveRadio lora.Radio
)
func UseRadio(r lora.Radio) {
if ActiveRadio != nil {
@@ -11,7 +19,55 @@ func UseRadio(r lora.Radio) {
ActiveRadio = r
}
func Join() error {
func Join(otaa *Otaa, session *Session) error {
var resp []uint8
if ActiveRadio == nil {
return errors.New("no LoRa radio attached")
}
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_RXTX_TIMEOUT)
if err != nil {
return err
}
// Wait for JoinAccept
//println("lorawan: Wait for JOINACCEPT for 10s")
ActiveRadio.SetIqMode(1) // IQ Inverted
for i := 0; i < 15; i++ {
resp, err = ActiveRadio.Rx(LORA_RXTX_TIMEOUT)
if err != nil {
return err
}
if resp != nil {
break
}
}
if resp == nil {
return errors.New("no JoinAccept packet received")
}
//println("lorawan: Received packet: len=", len(resp), "payload=", bytesToHexString(resp))
err = otaa.DecodeJoinAccept(resp, session)
if err != nil {
return err
}
// println("lorawan: Valid JOINACCEPT, now connected")
// println("lorawan: | DevAddr: ", bytesToHexString(r.Session.DevAddr[:]), " (LSB)")
// println("lorawan: | NetID : ", bytesToHexString(r.Otaa.NetID[:]))
// println("lorawan: | NwkSKey: ", bytesToHexString(r.Session.NwkSKey[:]))
// println("lorawan: | AppSKey: ", bytesToHexString(r.Session.AppSKey[:]))
return nil
}
+14 -28
View File
@@ -1,37 +1,23 @@
package lorawan
// revertByteArray inverts de order of a given byte slice
func revertByteArray(s []byte) []byte {
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 {
s[i], s[j] = s[j], s[i]
result[i], result[j] = s[j], s[i]
}
return s
return result
}
// byteToHex return string hex representation of byte
func byteToHex(b byte) string {
bb := (b >> 4) & 0x0F
ret := ""
if bb < 10 {
ret += string(rune('0' + bb))
} else {
ret += string(rune('A' + (bb - 10)))
// GetRand16 returns 2 random bytes
func GetRand16() ([2]uint8, error) {
randomBytes := make([]byte, 2)
_, err := rand.Read(randomBytes)
if err != nil {
return [2]uint8{}, err
}
bb = (b) & 0xF
if bb < 10 {
ret += string(rune('0' + bb))
} else {
ret += string(rune('A' + (bb - 10)))
}
return ret
}
// BytesToHexString converts byte slice to hex string representation
func bytesToHexString(data []byte) string {
s := ""
for i := 0; i < len(data); i++ {
s += byteToHex(data[i])
}
return s
return [2]uint8{randomBytes[0], randomBytes[1]}, nil
}
+71 -12
View File
@@ -3,6 +3,7 @@ package lorawan
import (
"bytes"
"crypto/aes"
"encoding/hex"
"errors"
)
@@ -14,25 +15,83 @@ type Otaa struct {
DevNonce [2]uint8
AppNonce [3]uint8
NetID [3]uint8
buf []uint8
}
// Initialize DevNonce
func (o *Otaa) Init() {
o.buf = make([]uint8, 0)
// TODO: handle error
rnd, _ := GetRand16()
o.DevNonce[0] = rnd[0]
o.DevNonce[1] = rnd[1]
}
// Set configures the Otaa AppEUI, DevEUI, AppKey for the device
func (o *Otaa) Set(appEUI [8]uint8, devEUI [8]uint8, appKey [16]uint8) {
o.AppEUI = appEUI
o.DevEUI = devEUI
o.AppKey = appKey
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 errors.New("invalid length")
}
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 errors.New("invalid length")
}
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 errors.New("invalid length")
}
copy(o.AppKey[:], appKey)
return nil
}
func (o *Otaa) GetAppKey() string {
return hex.EncodeToString(o.AppKey[:])
}
// GenerateJoinRequest Generates a LoraWAN Join request
func (o *Otaa) GenerateJoinRequest(buf []uint8) error {
func (o *Otaa) GenerateJoinRequest() ([]uint8, error) {
// TODO: Add checks
buf = append(buf, 0x00)
buf = append(buf, revertByteArray(o.AppEUI[:])...)
buf = append(buf, revertByteArray(o.DevEUI[:])...)
buf = append(buf, revertByteArray(o.DevNonce[:])...)
mic := genPayloadMIC(buf, o.AppKey)
buf = append(buf, mic[:]...)
return nil
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, reverseBytes(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
+16 -1
View File
@@ -3,6 +3,7 @@ package lorawan
import (
"crypto/aes"
"encoding/binary"
"encoding/hex"
"errors"
"math"
)
@@ -19,6 +20,21 @@ type Session struct {
DLSettings uint8
}
// SetDevAddr configures the Session DevAddr
func (s *Session) SetDevAddr(devAddr []uint8) error {
if len(devAddr) != 4 {
return errors.New("invalid length")
}
copy(s.DevAddr[:], devAddr)
return nil
}
func (s *Session) GetDevAddr() string {
return hex.EncodeToString(s.DevAddr[:])
}
// GenMessage Forge an uplink message
func (s *Session) GenMessage(dir uint8, payload []uint8) ([]uint8, error) {
var buf []uint8
@@ -87,4 +103,3 @@ func (s *Session) genFRMPayload(dir uint8, fCnt uint32, payload []byte, isFOpts
}
return encrypted[:len(payload)], nil
}