mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
lora/lorawan: implement minimal Join() function, and supporting functions
Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
+59
-3
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user