From 74043ba8695f6931685555efd887c147c3bb7b70 Mon Sep 17 00:00:00 2001 From: Francois Riotte <31351194+rgot-org@users.noreply.github.com> Date: Wed, 24 Apr 2024 16:12:36 +0200 Subject: [PATCH] first release --- README.md | 221 + .../ttn-otaa-BLE-provisioning.ino | 93 + examples/ttn-otaa/LoRaWan_config.h | 3 + examples/ttn-otaa/board_config.h | 77 + examples/ttn-otaa/ttn-otaa.ino | 61 + examples/ttn_abp/ttn_abp.ino | 51 + library.properties | 10 + src/BasicMAC/aes/aes-common.c | 145 + src/BasicMAC/aes/aes-ideetron.c | 340 ++ src/BasicMAC/aes/aes-original.c | 382 ++ src/BasicMAC/basicmac.h | 37 + src/BasicMAC/hal/hal.cpp | 495 +++ src/BasicMAC/hal/hal.h | 72 + src/BasicMAC/lmic/aes.h | 39 + src/BasicMAC/lmic/board.h | 29 + src/BasicMAC/lmic/debug.c | 272 ++ src/BasicMAC/lmic/debug.h | 65 + src/BasicMAC/lmic/hal.h | 188 + src/BasicMAC/lmic/hw.h | 35 + src/BasicMAC/lmic/lce.c | 182 + src/BasicMAC/lmic/lce.h | 66 + src/BasicMAC/lmic/lmic.c | 3570 +++++++++++++++++ src/BasicMAC/lmic/lmic.h | 614 +++ src/BasicMAC/lmic/lorabase.h | 337 ++ src/BasicMAC/lmic/oslmic.c | 247 ++ src/BasicMAC/lmic/oslmic.h | 378 ++ src/BasicMAC/lmic/peripherals.h | 121 + src/BasicMAC/lmic/radio-sx126x.c | 906 +++++ src/BasicMAC/lmic/radio-sx127x.c | 1314 ++++++ src/BasicMAC/lmic/radio.c | 209 + src/BasicMAC/lmic/region.h | 147 + src/ByteArrayUtils.h | 100 + src/EzLoRaWAN.cpp | 920 +++++ src/EzLoRaWAN.h | 469 +++ src/EzLoRaWAN_BLE.cpp.org | 138 + src/EzLoRaWAN_BLE.h.org | 48 + src/EzLoRaWAN_CayenneLPP.cpp | 240 ++ src/EzLoRaWAN_CayenneLPP.h | 74 + src/helper.h | 17 + src/oslmic_types.h | 54 + src/target-config.h | 180 + 41 files changed, 12946 insertions(+) create mode 100644 README.md create mode 100644 examples/ttn-otaa-BLE-provisioning/ttn-otaa-BLE-provisioning.ino create mode 100644 examples/ttn-otaa/LoRaWan_config.h create mode 100644 examples/ttn-otaa/board_config.h create mode 100644 examples/ttn-otaa/ttn-otaa.ino create mode 100644 examples/ttn_abp/ttn_abp.ino create mode 100644 library.properties create mode 100644 src/BasicMAC/aes/aes-common.c create mode 100644 src/BasicMAC/aes/aes-ideetron.c create mode 100644 src/BasicMAC/aes/aes-original.c create mode 100644 src/BasicMAC/basicmac.h create mode 100644 src/BasicMAC/hal/hal.cpp create mode 100644 src/BasicMAC/hal/hal.h create mode 100644 src/BasicMAC/lmic/aes.h create mode 100644 src/BasicMAC/lmic/board.h create mode 100644 src/BasicMAC/lmic/debug.c create mode 100644 src/BasicMAC/lmic/debug.h create mode 100644 src/BasicMAC/lmic/hal.h create mode 100644 src/BasicMAC/lmic/hw.h create mode 100644 src/BasicMAC/lmic/lce.c create mode 100644 src/BasicMAC/lmic/lce.h create mode 100644 src/BasicMAC/lmic/lmic.c create mode 100644 src/BasicMAC/lmic/lmic.h create mode 100644 src/BasicMAC/lmic/lorabase.h create mode 100644 src/BasicMAC/lmic/oslmic.c create mode 100644 src/BasicMAC/lmic/oslmic.h create mode 100644 src/BasicMAC/lmic/peripherals.h create mode 100644 src/BasicMAC/lmic/radio-sx126x.c create mode 100644 src/BasicMAC/lmic/radio-sx127x.c create mode 100644 src/BasicMAC/lmic/radio.c create mode 100644 src/BasicMAC/lmic/region.h create mode 100644 src/ByteArrayUtils.h create mode 100644 src/EzLoRaWAN.cpp create mode 100644 src/EzLoRaWAN.h create mode 100644 src/EzLoRaWAN_BLE.cpp.org create mode 100644 src/EzLoRaWAN_BLE.h.org create mode 100644 src/EzLoRaWAN_CayenneLPP.cpp create mode 100644 src/EzLoRaWAN_CayenneLPP.h create mode 100644 src/helper.h create mode 100644 src/oslmic_types.h create mode 100644 src/target-config.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f7eb2a --- /dev/null +++ b/README.md @@ -0,0 +1,221 @@ +# API Reference + +The `TTN_esp32` class enables ESP32 devices with supported LoRaWAN modules to communicate via The Things Network. This library is a wrapper for MCCI-Catena/arduino-lmic library. the pre-integrated boards are the same as mcci-catena library plus : + + - HELTEC Wifi Lora 32 (V1 & V2) + - HELTEC Wireless Stick +# Configuration +Like MCCI-Catena/arduino-lmic library the configuration is setup by editing `project_config/lmic_project_config.h`. See https://github.com/mcci-catena/arduino-lmic#configuration for more informations + +## Class: `TTN_esp32` + +Include and instantiate the TTN_esp32 class. The constructor initialize the library with the Streams it should communicate with. + +```c +#include + +TTN_esp32 ttn; +``` +## Method: `begin` + +Start the LMIC stack with Pre-integrated boards (see https://github.com/mcci-catena/arduino-lmic#pre-integrated-boards) + +Following boards are tested : + - HELTEC WIRELESS STICK + - HELTEC WIFI LORA 32 V1 + - HELTEC WIFI LORA 32 V2 + + +```c +void begin(); +``` +Initialize the stack with pointer to pin mapping (see https://github.com/mcci-catena/arduino-lmic#pin-mapping) +```c +bool begin(const TTN_esp32_LMIC::HalPinmap_t* pPinmap); +``` +Initialize the LMIC stack with pinout as arguments +```c +void begin(uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1, uint8_t dio2); +``` +Example for HELTEC WIRELESS STICK +```c +// wireless stick pinout +#define UNUSED_PIN 0xFF +#define SS 18 +#define RST_LoRa 14 +#define DIO0 26 +#define DIO1 35 +#define DIO2 34 +... +ttn.begin(SS, UNUSED_PIN, RST_LoRa, DIO0,DIO1,DIO2); +``` +## Method: `getAppEui` + +Gets the provisioned AppEUI. The AppEUI is set using `provision()` or `join()`. + +```c +size_t getAppEui(char *buffer, size_t size); +``` +return AppEui as an array of char +```c +String getAppEui(); +``` +return AppEui as String +## Method: `getDevEui` +Gets the provisioned DevEUI. The DevEUI is set using `provision()` or `join()`. +```c +size_t getDevEui(char *buffer, size_t size, bool hardwareEUI=false); +``` +return DevEUI as array of char +```c +String getDevEui(bool hardwareEui=false); +``` +return DevEUI as String + +- `bool hardwareEui=false`: if true get DevEUI from Mac Address. + +## Method: `isJoined` +Check whether we have joined TTN +```c + bool isJoined(); +``` +return `true` if joined to TTN, `false` if not. +## Method: `showStatus` + +Writes information about the device and LoRa module to `Serial` . + +```c +void showStatus(); +``` + +Will write something like: + +```bash +---------------Status-------------- +Device EUI: 004D22F44E5DCE53 +Application EUI: 70B3D57ED0015575 +netid: 13 +devaddr: 26011B24 +NwkSKey: 6A2D3C24AD3C0D17614D7325BCAA976 +AppSKey: 9E68DCBEBF8AE9D891252FB7E6054 +data rate: 5 +tx power: 14dB +freq: 867100000Hz +----------------------------------- +``` + +## Method: `onMessage` + +Sets a function which will be called to process incoming messages. You'll want to do this in your `setup()` function and then define a `void (*cb)(const byte* payload, size_t length, port_t port)` function somewhere else in your sketch. + +```c +void onMessage(void (*cb)(const uint8_t *payload, size_t size, int rssi)); +``` + +- `const uint8_t* payload`: Bytes received. +- `size_t size`: Number of bytes. +- `int rssi`: the rssi in dB. + +See the [ttn-otaa](https://github.com/rgot-org/TheThingsNetwork_esp32/blob/master/examples/ttn-otaa/ttn-otaa.ino) example. + +## Method: `join` + +Activate the device via OTAA (default). + +```c +bool join(); +bool join(const char *app_eui, const char *app_key, int8_t retries = -1, uint32_t retryDelay = 10000); +bool join(const char *dev_eui, const char *app_eui, const char *app_key, int8_t retries = -1, uint32_t retryDelay = 10000); +``` + +- `const char *app_eui`: Application EUI the device is registered to. +- `const char *app_key`: Application Key assigned to the device. +- `const char *dev_eui`: Device EUI +- `int8_t retries = -1`: Number of times to retry after failed or unconfirmed join. Defaults to `-1` which means infinite. +- `uint32_t retryDelay = 10000`: Delay in ms between attempts. Defaults to 10 seconds. + +Returns `true` or `false` depending on whether it received confirmation that the activation was successful before the maximum number of attempts. + +Call the method without the first two arguments if the device's LoRa module is provisioned or comes with NVS stored values. See `provision`, `saveKeys` and `restoreKeys` + +## Method: `personalize` + +Activate the device via ABP. + +```c +bool personalize(const char *devAddr, const char *nwkSKey, const char *appSKey); +bool personalize(); +``` + +- `const char *devAddr`: Device Address assigned to the device. +- `const char *nwkSKey`: Network Session Key assigned to the device for identification. +- `const char *appSKey`: Application Session Key assigned to the device for encryption. + +Returns `true` or `false` depending on whether the activation was successful. + +Call the method with no arguments if the device's LoRa module is provisioned or comes with NVS stored values. See `provisionABP`, `saveKeys` and `restoreKeys` + +See the [ttn_abp](https://github.com/rgot-org/TheThingsNetwork_esp32/tree/master/examples/ttn_abp) example. + +## Method: `sendBytes` + +Send a message to the application using raw bytes. + +```c +bool sendBytes(uint8_t *payload, size_t length, uint8_t port = 1, uint8_t confirm = 0); +``` + +- `uint8_t *payload `: Bytes to send. +- `size_t length`: The number of bytes. Use `sizeof(payload)` to get it. +- `uint8_t port = 1`: The port to address. Defaults to `1`. +- `uint8_t confirm = false`: Whether to ask for confirmation. Defaults to `false` + +## Method: `poll` + +Calls `sendBytes()` with `{ 0x00 }` as payload to poll for incoming messages. + +```c +int8_t poll(uint8_t port = 1, uint8_t confirm = 0); +``` + +- `uint8_t port = 1`: The port to address. Defaults to `1`. +- `uint8_t confirm = 0`: Whether to ask for confirmation. + +Returns the result of `sendBytes()`. + + +## Method: `provision` + +Sets the informations needed to activate the device via OTAA, without actually activating. Call join() without the first 2 arguments to activate. + +```c +bool provision(const char *appEui, const char *appKey); +bool provision(const char *devEui, const char *appEui, const char *appKey); +``` + +- `const char *appEui`: Application Identifier for the device. +- `const char *appKey`: Application Key assigned to the device. +- `const char *devEui`: Device EUI. +## Method: `provisionABP` + +Sets the informations needed to activate the device via ABP, without actually activating. call `personalize()` without arguments to activate. +```c +bool provisionABP(const char *devAddr, const char *nwkSKey, const char *appSKey); +``` +- `const char *devAddr`: Device Address. +- `const char *nwkSKey`: Network Session Key. +- `const char *appSKey`: Application Session Key. +## Method: `saveKeys` +Save the provisioning keys (OTAA and ABP) in Non Volatile Storage (NVS). +```c +bool saveKeys(); +``` +## Method: `restoreKeys` +Restore the keys from NVS and provisioning the informations for OTAA or ABP connection. Call `join()` or `Personalize()` after this method to activate the device. +```c +boobool restoreKeys(bool silent=true); +``` +- `bool silent=true`: silent mode (no log) + + + diff --git a/examples/ttn-otaa-BLE-provisioning/ttn-otaa-BLE-provisioning.ino b/examples/ttn-otaa-BLE-provisioning/ttn-otaa-BLE-provisioning.ino new file mode 100644 index 0000000..531a0e0 --- /dev/null +++ b/examples/ttn-otaa-BLE-provisioning/ttn-otaa-BLE-provisioning.ino @@ -0,0 +1,93 @@ +/*************************************************************************************** + To use this sketch you must install the TTN ESP32 BLE Provisioning APP from gGoogle Play + https://play.google.com/store/apps/details?id=org.rgot.BLE_TEST + this program was tested with heltech boards (Heltec Wifi Lora 32 & Heltec Wireless Stick) + + When running press the user button (named PROG on board) the on board Led Light + and the Bluetooth Low Energy (BLE) is power on. + On the Android application you can see a new device named "RGOT_xxx" where xxx is the hardware devEui. + + When you tap on this device, you can complete a form with devEUI, AppEui and AppKey. + On the keyboard press Save (enter) to save the parameter. + When finish press the back arrow, then the esp32 board reboot and take your new parameters. + +*/ +#include "LoRaWan_BLE_esp32.h" +#include +#include "LoRaWan_CayenneLPP.h" + +// wireless stick pinout + +#define INTERVAL 30000 +LoRaWan_esp32 ttn; +LoRaWan_BLE_esp32 ble; +LoRaWan_CayenneLPP lpp; + +void setup() { + + Serial.begin(115200); + Serial.println("Starting"); + ttn.begin(); + if (ttn.restoreKeys()) {// provisioning with restored key from NVS + Serial.println("devEui : "+ ttn.getDevEui()); + Serial.println("appEui : "+ ttn.getAppEui()); + ttn.join(); + Serial.println("joining"); + } + else + { + Serial.println("No key are provisioned, please press the User Button\n\ +then launch the \'TTN ESP32 Prosioning\' Android App \n\ +Select the RGOT_... device and provisioning the keys\n\ +Quit the android App, then the esp32 restart... "); + } + pinMode(KEY_BUILTIN, INPUT); + pinMode(LED, OUTPUT); + digitalWrite(LED, LOW); +} + +void loop() { + static bool jonction = false; + static float nb = 0.0; + static unsigned previoumillis = 0; + if (!digitalRead(KEY_BUILTIN) && !ble.getInitialized()) + { + ttn.stop(); + ble.begin(); + digitalWrite(LED, HIGH); + while (!digitalRead(KEY_BUILTIN)); + + } + + if (ttn.isJoined()) + { + if (!jonction) + { + ttn.showStatus(); + jonction = true; + } + if (millis() - previoumillis > INTERVAL) + { + if (ttn.isRunning()) + { + nb += 0.1; + lpp.reset(); + lpp.addTemperature(1, nb); + if (ttn.sendBytes(lpp.getBuffer(), lpp.getSize(), 1, 1)) { + Serial.printf("envoi reussi : %d %x %02X%02X\n", lpp.getBuffer()[0], lpp.getBuffer()[1], lpp.getBuffer()[2], lpp.getBuffer()[3]); + } + } + + previoumillis = millis(); + } + } + else { + if (ttn.isRunning()) + { + Serial.print("."); + delay(500); + } + + } + + } diff --git a/examples/ttn-otaa/LoRaWan_config.h b/examples/ttn-otaa/LoRaWan_config.h new file mode 100644 index 0000000..7a7fe46 --- /dev/null +++ b/examples/ttn-otaa/LoRaWan_config.h @@ -0,0 +1,3 @@ +const char* config_devEui = "CHANGE_ME"; // Change to TTN Device EUI +const char* config_appEui = "CHANGE_ME"; // Change to TTN Application EUI +const char* config_appKey = "CHANGE_ME"; // Chaneg to TTN Application Key \ No newline at end of file diff --git a/examples/ttn-otaa/board_config.h b/examples/ttn-otaa/board_config.h new file mode 100644 index 0000000..49b244e --- /dev/null +++ b/examples/ttn-otaa/board_config.h @@ -0,0 +1,77 @@ +#pragma once +/* +Example for LyLigo T3S3 +Arduino IDE options : +T3-S3 Value +Board ESP32S3 Dev Module +Port Your port +USB CDC On Boot Enable +CPU Frequency 240MHZ(WiFi) +Core Debug Level None +USB DFU On Boot Disable +Erase All Flash Disable +Events Run On Core1 +Flash Mode QIO 80MHZ +Flash Size 4MB(32Mb) +Arduino Runs On Core1 +USB FW MSC On Boot Disable +Partition Scheme Default 4M Flash with spiffs(1.2M APP/1.5MB SPIFFS) +PSRAM QSPI PSRAM +Upload Mode UART0/Hardware CDC +Upload Speed 921600 +USB Mode CDC and JTAG +Programmer Esptool +*/ +// T3S3 with sx1262 +#define BRD_sx1262_radio 1 +#define RADIO_SCLK_PIN 5 +#define RADIO_MISO_PIN 3 +#define RADIO_MOSI_PIN 6 +#define RADIO_CS_PIN 7 +#define RADIO_DIO1_PIN 33 //SX1280 DIO1 = IO9 +#define RADIO_BUSY_PIN 34 //SX1280 BUSY = IO36 +#define RADIO_RST_PIN 8 + // T3S3 with other LoRaWAN chip see : https://github.com/Xinyuan-LilyGO/LilyGo-LoRa-Series/blob/master/examples/T3S3Factory/utilities.h + +const lmic_pinmap lmic_pins = { + // NSS input pin for SPI communication (required) + .nss = RADIO_CS_PIN, + // If needed, these pins control the RX/TX antenna switch (active + // high outputs). When you have both, the antenna switch can + // powerdown when unused. If you just have a RXTX pin it should + // usually be assigned to .tx, reverting to RX mode when idle). + // + // The SX127x has an RXTX pin that can automatically control the + // antenna switch (if internally connected on the transceiver + // board). This pin is always active, so no configuration is needed + // for that here. + // On SX126x, the DIO2 can be used for the same thing, but this is + // disabled by default. To enable this, set .tx to + // LMIC_CONTROLLED_BY_DIO2 below (this seems to be common and + // enabling it when not needed is probably harmless, unless DIO2 is + // connected to GND or VCC directly inside the transceiver board). + .tx = LMIC_CONTROLLED_BY_DIO2, // this constant is defined automaticaly when BRD_sx1262_radio is defined + .rx = LMIC_UNUSED_PIN, + // Radio reset output pin (active high for SX1276, active low for + // others). When omitted, reset is skipped which might cause problems. + .rst = RADIO_RST_PIN, + // DIO input pins. + // For SX127x, LoRa needs DIO0 and DIO1, FSK needs DIO0, DIO1 and DIO2 + // For SX126x, Only DIO1 is needed (so leave DIO0 and DIO2 as LMIC_UNUSED_PIN) + .dio = {/* DIO0 */ LMIC_UNUSED_PIN, /* DIO1 */ RADIO_DIO1_PIN, /* DIO2 */ LMIC_UNUSED_PIN}, + // Busy input pin (SX126x only). When omitted, a delay is used which might + // cause problems. + .busy = RADIO_BUSY_PIN, + // TCXO oscillator enable output pin (active high). + // + // For SX127x this should be an I/O pin that controls the TCXO, or + // LMIC_UNUSED_PIN when a crystal is used instead of a TCXO. + // + // For SX126x this should be LMIC_CONTROLLED_BY_DIO3 when a TCXO is + // directly connected to the transceiver DIO3 to let the transceiver + // start and stop the TCXO, or LMIC_UNUSED_PIN when a crystal is + // used instead of a TCXO. Controlling the TCXO from the MCU is not + // supported. + .tcxo = LMIC_CONTROLLED_BY_DIO3, //this constant is defined automaticaly when BRD_sx1262_radio is defined +}; + diff --git a/examples/ttn-otaa/ttn-otaa.ino b/examples/ttn-otaa/ttn-otaa.ino new file mode 100644 index 0000000..17d2c24 --- /dev/null +++ b/examples/ttn-otaa/ttn-otaa.ino @@ -0,0 +1,61 @@ +#include +#include +#include "LoRaWan_config.h" +/*************************************************************************** + * Go to your TTN console register a device then the copy fields + * and replace the CHANGE_ME strings below + ****************************************************************************/ +const char* devEui = config_devEui; // Change to TTN Device EUI +const char* appEui = config_appEui; // Change to TTN Application EUI +const char* appKey = config_appKey; // Chaneg to TTN Application Key + +LoRaWan_esp32 ttn; +LoRaWan_CayenneLPP lpp; +#ifndef AUTO_PIN_MAP // AUTO_PIN_MAP is set if board is defined in the file target-config.h +#include "board_config.h" +#endif // !AUTO_PINS +void message(const uint8_t* payload, size_t size, uint8_t port, int rssi) +{ + Serial.println("-- MESSAGE"); + Serial.printf("Received %d bytes on port %d (RSSI=%ddB) :", size, port, rssi); + for (int i = 0; i < size; i++) + { + Serial.printf(" %02X", payload[i]); + } + Serial.println(); +} + +void setup() +{ + Serial.begin(115200); + Serial.println("Starting"); +#ifndef AUTO_PIN_MAP + SPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN); +#endif // !AUTO_PIN_MAP + ttn.begin(); + ttn.onMessage(message); // Declare callback function for handling downlink + // messages from server + ttn.join(devEui, appEui, appKey); + Serial.print("Joining TTN "); + while (!ttn.isJoined()) + { + Serial.print("."); + delay(500); + } + Serial.println("\njoined !"); + ttn.showStatus(); +} + +void loop() +{ + static float nb = 18.2; + nb += 0.1; + lpp.reset(); + lpp.addTemperature(1, nb); + if (ttn.sendBytes(lpp.getBuffer(), lpp.getSize())) + { + Serial.printf("Temp: %f TTN_CayenneLPP: %d %x %02X%02X\n", nb, lpp.getBuffer()[0], lpp.getBuffer()[1], + lpp.getBuffer()[2], lpp.getBuffer()[3]); + } + delay(100000); +} \ No newline at end of file diff --git a/examples/ttn_abp/ttn_abp.ino b/examples/ttn_abp/ttn_abp.ino new file mode 100644 index 0000000..73bec21 --- /dev/null +++ b/examples/ttn_abp/ttn_abp.ino @@ -0,0 +1,51 @@ +#include + +#include "LoRaWan_CayenneLPP.h" +/*************************************************************************** + * Go to your TTN console register a device then the copy fields + * and replace the CHANGE_ME strings below + ****************************************************************************/ +const char* devAddr = "CHANGE_ME"; // Change to TTN Device Address +const char* nwkSKey = "CHANGE_ME"; // Change to TTN Network Session Key +const char* appSKey = "CHANGE_ME"; // Change to TTN Application Session Key + +LoRaWan_esp32 ttn ; +LoRaWan_CayenneLPP lpp; + +void message(const uint8_t* payload, size_t size, int rssi) +{ + Serial.println("-- MESSAGE"); + Serial.print("Received " + String(size) + " bytes RSSI= " + String(rssi) + "dB"); + + for (int i = 0; i < size; i++) + { + Serial.print(" " + String(payload[i])); + // Serial.write(payload[i]); + } + + Serial.println(); +} + +void setup() +{ + Serial.begin(115200); + Serial.println("Starting"); + ttn.begin(); + ttn.onMessage(message); // declare callback function when is downlink from server + ttn.personalize(devAddr, nwkSKey, appSKey); + ttn.showStatus(); +} + +void loop() +{ + static float nb = 18.2; + nb += 0.1; + lpp.reset(); + lpp.addTemperature(1, nb); + if (ttn.sendBytes(lpp.getBuffer(), lpp.getSize())) + { + Serial.printf("Temp: %f LoRaWan_CayenneLPP: %d %x %02X%02X\n", nb, lpp.getBuffer()[0], lpp.getBuffer()[1], + lpp.getBuffer()[2], lpp.getBuffer()[3]); + } + delay(10000); +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..01d0a13 --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +name=EzLoRaWAN +version=0.1.1 +author=Francois Riotte +maintainer=Francois Riotte +sentence=ESP 32 port of the Arduino TheThingsNetwork library. +paragraph=Supports esp32 boards with SX127x or SX126X LoRa chips +category=Communication +url=https://github.com/rgot-org/EzLoRaWan +architectures=esp32 +includes=EzLoRaWAN.h,EzLoRaWAN_CayenneLPP.h diff --git a/src/BasicMAC/aes/aes-common.c b/src/BasicMAC/aes/aes-common.c new file mode 100644 index 0000000..5704c83 --- /dev/null +++ b/src/BasicMAC/aes/aes-common.c @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2016 Matthijs Kooijman + * + * LICENSE + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and + * redistribution. + * + * NO WARRANTY OF ANY KIND IS PROVIDED. + *******************************************************************************/ + +/* + * The original LMIC AES implementation integrates raw AES encryption + * with CMAC and AES-CTR in a single piece of code. Most other AES + * implementations (only) offer raw single block AES encryption, so this + * file contains an implementation of CMAC and AES-CTR, and offers the + * same API through the os_aes() function as the original AES + * implementation. This file assumes that there is an encryption + * function available with this signature: + * + * extern "C" void lmic_aes_encrypt(uint8_t *data, uint8_t *key); + * + * That takes a single 16-byte buffer and encrypts it wit the given + * 16-byte key. + */ + +#include "../lmic/aes.h" + +#if !defined(USE_ORIGINAL_AES) + +// This should be defined elsewhere +void lmic_aes_encrypt(uint8_t *data, uint8_t *key); + +// global area for passing parameters (aux, key) and for storing round keys +u32_t AESAUX[16/sizeof(u32_t)]; +u32_t AESKEY[11*16/sizeof(u32_t)]; + +// Shift the given buffer left one bit +static void shift_left(uint8_t *buf, uint8_t len) { + while (len--) { + uint8_t next = len ? buf[1] : 0; + + uint8_t val = (*buf << 1); + if (next & 0x80) + val |= 1; + *buf++ = val; + } +} + +// Apply RFC4493 CMAC, using AESKEY as the key. If prepend_aux is true, +// AESAUX is prepended to the message. AESAUX is used as working memory +// in any case. The CMAC result is returned in AESAUX as well. +static void os_aes_cmac(uint8_t *buf, u16_t len, uint8_t prepend_aux) { + if (prepend_aux) + lmic_aes_encrypt(AESaux, AESkey); + else + memset (AESaux, 0, 16); + + while (len > 0) { + uint8_t need_padding = 0; + for (uint8_t i = 0; i < 16; ++i, ++buf, --len) { + if (len == 0) { + // The message is padded with 0x80 and then zeroes. + // Since zeroes are no-op for xor, we can just skip them + // and leave AESAUX unchanged for them. + AESaux[i] ^= 0x80; + need_padding = 1; + break; + } + AESaux[i] ^= *buf; + } + + if (len == 0) { + // Final block, xor with K1 or K2. K1 and K2 are calculated + // by encrypting the all-zeroes block and then applying some + // shifts and xor on that. + uint8_t final_key[16]; + memset(final_key, 0, sizeof(final_key)); + lmic_aes_encrypt(final_key, AESkey); + + // Calculate K1 + uint8_t msb = final_key[0] & 0x80; + shift_left(final_key, sizeof(final_key)); + if (msb) + final_key[sizeof(final_key)-1] ^= 0x87; + + // If the final block was not complete, calculate K2 from K1 + if (need_padding) { + msb = final_key[0] & 0x80; + shift_left(final_key, sizeof(final_key)); + if (msb) + final_key[sizeof(final_key)-1] ^= 0x87; + } + + // Xor with K1 or K2 + for (uint8_t i = 0; i < sizeof(final_key); ++i) + AESaux[i] ^= final_key[i]; + } + + lmic_aes_encrypt(AESaux, AESkey); + } +} + +// Run AES-CTR using the key in AESKEY and using AESAUX as the +// counter block. The last byte of the counter block will be incremented +// for every block. The given buffer will be encrypted in place. +static void os_aes_ctr (uint8_t *buf, u16_t len) { + uint8_t ctr[16]; + while (len) { + // Encrypt the counter block with the selected key + memcpy(ctr, AESaux, sizeof(ctr)); + lmic_aes_encrypt(ctr, AESkey); + + // Xor the payload with the resulting ciphertext + for (uint8_t i = 0; i < 16 && len > 0; i++, len--, buf++) + *buf ^= ctr[i]; + + // Increment the block index byte + AESaux[15]++; + } +} + +u32_t os_aes (uint8_t mode, uint8_t *buf, u16_t len) { + switch (mode & ~AES_MICNOAUX) { + case AES_MIC: + os_aes_cmac(buf, len, /* prepend_aux */ !(mode & AES_MICNOAUX)); + return os_rmsbf4(AESaux); + + case AES_ENC: + // TODO: Check / handle when len is not a multiple of 16 + for (uint8_t i = 0; i < len; i += 16) + lmic_aes_encrypt(buf+i, AESkey); + break; + + case AES_CTR: + os_aes_ctr(buf, len); + break; + } + return 0; +} + +#endif // !defined(USE_ORIGINAL_AES) diff --git a/src/BasicMAC/aes/aes-ideetron.c b/src/BasicMAC/aes/aes-ideetron.c new file mode 100644 index 0000000..1f51a60 --- /dev/null +++ b/src/BasicMAC/aes/aes-ideetron.c @@ -0,0 +1,340 @@ +/****************************************************************************************** +#if defined(USE_IDEETRON_AES) +* Copyright 2015, 2016 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/****************************************************************************************** +* +* File: AES-128_V10.cpp +* Author: Gerben den Hartog +* Compagny: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +******************************************************************************************/ +/**************************************************************************************** +* +* Created on: 20-10-2015 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +* +* Firmware Version 1.0 +* First version +****************************************************************************************/ + +// This file was taken from +// https://github.com/Ideetron/RFM95W_Nexus/tree/master/LoRaWAN_V31 for +// use with LMIC. It was only cosmetically modified: +// - AES_Encrypt was renamed to lmic_aes_encrypt. +// - All other functions and variables were made static +// - Tabs were converted to 2 spaces +// - An #include and #if guard was added + +#include "../lmic/oslmic.h" + +#if defined(USE_IDEETRON_AES) + +/* +******************************************************************************************** +* Global Variables +******************************************************************************************** +*/ + +static unsigned char State[4][4]; + +static unsigned char S_Table[16][16] = { + {0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76}, + {0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0}, + {0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15}, + {0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75}, + {0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84}, + {0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF}, + {0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8}, + {0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2}, + {0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73}, + {0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB}, + {0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79}, + {0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08}, + {0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A}, + {0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E}, + {0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF}, + {0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16} +}; + +void lmic_aes_encrypt(unsigned char *Data, unsigned char *Key); +static void AES_Add_Round_Key(unsigned char *Round_Key); +static unsigned char AES_Sub_Byte(unsigned char Byte); +static void AES_Shift_Rows(); +static void AES_Mix_Collums(); +static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key); + +/* +***************************************************************************************** +* Description : Function for encrypting data using AES-128 +* +* Arguments : *Data Data to encrypt is a 16 byte long arry +* *Key Key to encrypt data with is a 16 byte long arry +***************************************************************************************** +*/ +void lmic_aes_encrypt(unsigned char *Data, unsigned char *Key) +{ + unsigned char i; + unsigned char Row,Collum; + unsigned char Round = 0x00; + unsigned char Round_Key[16]; + + //Copy input to State arry + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = Data[Row + (4*Collum)]; + } + } + + //Copy key to round key + for(i = 0; i < 16; i++) + { + Round_Key[i] = Key[i]; + } + + //Add round key + AES_Add_Round_Key(Round_Key); + + //Preform 9 full rounds + for(Round = 1; Round < 10; Round++) + { + //Preform Byte substitution with S table + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = AES_Sub_Byte(State[Row][Collum]); + } + } + + //Preform Row Shift + AES_Shift_Rows(); + + //Mix Collums + AES_Mix_Collums(); + + //Calculate new round key + AES_Calculate_Round_Key(Round,Round_Key); + + //Add round key + AES_Add_Round_Key(Round_Key); + } + + //Last round whitout mix collums + //Preform Byte substitution with S table + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = AES_Sub_Byte(State[Row][Collum]); + } + } + + //Shift rows + AES_Shift_Rows(); + + //Calculate new round key + AES_Calculate_Round_Key(Round,Round_Key); + + //Add round Key + AES_Add_Round_Key(Round_Key); + + //Copy the State into the data array + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + Data[Row + (4*Collum)] = State[Row][Collum]; + } + } + +} + +/* +***************************************************************************************** +* Description : Function that add's the round key for the current round +* +* Arguments : *Round_Key 16 byte long array holding the Round Key +***************************************************************************************** +*/ +static void AES_Add_Round_Key(unsigned char *Round_Key) +{ + unsigned char Row,Collum; + + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = State[Row][Collum] ^ Round_Key[Row + (4*Collum)]; + } + } +} + +/* +***************************************************************************************** +* Description : Function that substitutes a byte with a byte from the S_Table +* +* Arguments : Byte The byte that will be substituted +* +* Return : The return is the found byte in the S_Table +***************************************************************************************** +*/ +static unsigned char AES_Sub_Byte(unsigned char Byte) +{ + unsigned char S_Row,S_Collum; + unsigned char S_Byte; + + //Split byte up in Row and Collum + S_Row = ((Byte >> 4) & 0x0F); + S_Collum = (Byte & 0x0F); + + //Find the correct byte in the S_Table + S_Byte = S_Table[S_Row][S_Collum]; + + return S_Byte; +} + +/* +***************************************************************************************** +* Description : Function that preforms the shift row operation described in the AES standard +***************************************************************************************** +*/ +static void AES_Shift_Rows() +{ + unsigned char Buffer; + + //Row 0 doesn't change + + //Shift Row 1 one left + //Store firt byte in buffer + Buffer = State[1][0]; + //Shift all bytes + State[1][0] = State[1][1]; + State[1][1] = State[1][2]; + State[1][2] = State[1][3]; + State[1][3] = Buffer; + + //Shift row 2 two left + Buffer = State[2][0]; + State[2][0] = State[2][2]; + State[2][2] = Buffer; + Buffer = State[2][1]; + State[2][1] = State[2][3]; + State[2][3] = Buffer; + + //Shift row 3 three left + Buffer = State[3][3]; + State[3][3] = State[3][2]; + State[3][2] = State[3][1]; + State[3][1] = State[3][0]; + State[3][0] = Buffer; +} + +/* +***************************************************************************************** +* Description : Function that preforms the Mix Collums operation described in the AES standard +***************************************************************************************** +*/ +static void AES_Mix_Collums() +{ + unsigned char Row,Collum; + unsigned char a[4], b[4]; + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + a[Row] = State[Row][Collum]; + b[Row] = (State[Row][Collum] << 1); + + if((State[Row][Collum] & 0x80) == 0x80) + { + b[Row] = b[Row] ^ 0x1B; + } + } + State[0][Collum] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; + State[1][Collum] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; + State[2][Collum] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; + State[3][Collum] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; + } +} + +/* +***************************************************************************************** +* Description : Function that calculaties the round key for the current round +* +* Arguments : Round Number of current Round +* *Round_Key 16 byte long array holding the Round Key +***************************************************************************************** +*/ +static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key) +{ + unsigned char i,j; + unsigned char b; + unsigned char Temp[4]; + unsigned char Buffer; + unsigned char Rcon; + + //Calculate first Temp + //Copy laste byte from previous key + for(i = 0; i < 4; i++) + { + Temp[i] = Round_Key[i+12]; + } + + //Rotate Temp + Buffer = Temp[0]; + Temp[0] = Temp[1]; + Temp[1] = Temp[2]; + Temp[2] = Temp[3]; + Temp[3] = Buffer; + + //Substitute Temp + for(i = 0; i < 4; i++) + { + Temp[i] = AES_Sub_Byte(Temp[i]); + } + + //Calculate Rcon + Rcon = 0x01; + while(Round != 1) + { + b = Rcon & 0x80; + Rcon = Rcon << 1; + if(b == 0x80) + { + Rcon = Rcon ^ 0x1b; + } + Round--; + } + + //XOR Rcon + Temp[0] = Temp[0] ^ Rcon; + + //Calculate new key + for(i = 0; i < 4; i++) + { + for(j = 0; j < 4; j++) + { + Round_Key[j + (4*i)] = Round_Key[j + (4*i)] ^ Temp[j]; + Temp[j] = Round_Key[j + (4*i)]; + } + } +} + +#endif // defined(USE_IDEETRON_AES) diff --git a/src/BasicMAC/aes/aes-original.c b/src/BasicMAC/aes/aes-original.c new file mode 100644 index 0000000..938f0af --- /dev/null +++ b/src/BasicMAC/aes/aes-original.c @@ -0,0 +1,382 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#if defined(USE_ORIGINAL_AES) + +#include "../lmic/aes.h" + + +// global area for passing parameters (aux, key) and for storing round keys +u32_t AESAUX[16/sizeof(u32_t)]; +u32_t AESKEY[11*16/sizeof(u32_t)]; + + +#if defined(CFG_bootloader) && defined(CFG_bootloader_aes) + +#include "boot/bootloader.h" + +u32_t os_aes (uint8_t mode, uint8_t* buf, u16_t len) { + return HAL_boottab->aes(mode, buf, len, AESKEY, AESAUX); +} + +#else + +#define AES_MICSUB 0x30 // internal use only + +static const u32_t AES_RCON[10] = { + 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, + 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000 +}; + +static const uint8_t AES_S[256] = { + 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, +}; + +static const u32_t AES_E1[256] = { + 0xC66363A5, 0xF87C7C84, 0xEE777799, 0xF67B7B8D, 0xFFF2F20D, 0xD66B6BBD, 0xDE6F6FB1, 0x91C5C554, + 0x60303050, 0x02010103, 0xCE6767A9, 0x562B2B7D, 0xE7FEFE19, 0xB5D7D762, 0x4DABABE6, 0xEC76769A, + 0x8FCACA45, 0x1F82829D, 0x89C9C940, 0xFA7D7D87, 0xEFFAFA15, 0xB25959EB, 0x8E4747C9, 0xFBF0F00B, + 0x41ADADEC, 0xB3D4D467, 0x5FA2A2FD, 0x45AFAFEA, 0x239C9CBF, 0x53A4A4F7, 0xE4727296, 0x9BC0C05B, + 0x75B7B7C2, 0xE1FDFD1C, 0x3D9393AE, 0x4C26266A, 0x6C36365A, 0x7E3F3F41, 0xF5F7F702, 0x83CCCC4F, + 0x6834345C, 0x51A5A5F4, 0xD1E5E534, 0xF9F1F108, 0xE2717193, 0xABD8D873, 0x62313153, 0x2A15153F, + 0x0804040C, 0x95C7C752, 0x46232365, 0x9DC3C35E, 0x30181828, 0x379696A1, 0x0A05050F, 0x2F9A9AB5, + 0x0E070709, 0x24121236, 0x1B80809B, 0xDFE2E23D, 0xCDEBEB26, 0x4E272769, 0x7FB2B2CD, 0xEA75759F, + 0x1209091B, 0x1D83839E, 0x582C2C74, 0x341A1A2E, 0x361B1B2D, 0xDC6E6EB2, 0xB45A5AEE, 0x5BA0A0FB, + 0xA45252F6, 0x763B3B4D, 0xB7D6D661, 0x7DB3B3CE, 0x5229297B, 0xDDE3E33E, 0x5E2F2F71, 0x13848497, + 0xA65353F5, 0xB9D1D168, 0x00000000, 0xC1EDED2C, 0x40202060, 0xE3FCFC1F, 0x79B1B1C8, 0xB65B5BED, + 0xD46A6ABE, 0x8DCBCB46, 0x67BEBED9, 0x7239394B, 0x944A4ADE, 0x984C4CD4, 0xB05858E8, 0x85CFCF4A, + 0xBBD0D06B, 0xC5EFEF2A, 0x4FAAAAE5, 0xEDFBFB16, 0x864343C5, 0x9A4D4DD7, 0x66333355, 0x11858594, + 0x8A4545CF, 0xE9F9F910, 0x04020206, 0xFE7F7F81, 0xA05050F0, 0x783C3C44, 0x259F9FBA, 0x4BA8A8E3, + 0xA25151F3, 0x5DA3A3FE, 0x804040C0, 0x058F8F8A, 0x3F9292AD, 0x219D9DBC, 0x70383848, 0xF1F5F504, + 0x63BCBCDF, 0x77B6B6C1, 0xAFDADA75, 0x42212163, 0x20101030, 0xE5FFFF1A, 0xFDF3F30E, 0xBFD2D26D, + 0x81CDCD4C, 0x180C0C14, 0x26131335, 0xC3ECEC2F, 0xBE5F5FE1, 0x359797A2, 0x884444CC, 0x2E171739, + 0x93C4C457, 0x55A7A7F2, 0xFC7E7E82, 0x7A3D3D47, 0xC86464AC, 0xBA5D5DE7, 0x3219192B, 0xE6737395, + 0xC06060A0, 0x19818198, 0x9E4F4FD1, 0xA3DCDC7F, 0x44222266, 0x542A2A7E, 0x3B9090AB, 0x0B888883, + 0x8C4646CA, 0xC7EEEE29, 0x6BB8B8D3, 0x2814143C, 0xA7DEDE79, 0xBC5E5EE2, 0x160B0B1D, 0xADDBDB76, + 0xDBE0E03B, 0x64323256, 0x743A3A4E, 0x140A0A1E, 0x924949DB, 0x0C06060A, 0x4824246C, 0xB85C5CE4, + 0x9FC2C25D, 0xBDD3D36E, 0x43ACACEF, 0xC46262A6, 0x399191A8, 0x319595A4, 0xD3E4E437, 0xF279798B, + 0xD5E7E732, 0x8BC8C843, 0x6E373759, 0xDA6D6DB7, 0x018D8D8C, 0xB1D5D564, 0x9C4E4ED2, 0x49A9A9E0, + 0xD86C6CB4, 0xAC5656FA, 0xF3F4F407, 0xCFEAEA25, 0xCA6565AF, 0xF47A7A8E, 0x47AEAEE9, 0x10080818, + 0x6FBABAD5, 0xF0787888, 0x4A25256F, 0x5C2E2E72, 0x381C1C24, 0x57A6A6F1, 0x73B4B4C7, 0x97C6C651, + 0xCBE8E823, 0xA1DDDD7C, 0xE874749C, 0x3E1F1F21, 0x964B4BDD, 0x61BDBDDC, 0x0D8B8B86, 0x0F8A8A85, + 0xE0707090, 0x7C3E3E42, 0x71B5B5C4, 0xCC6666AA, 0x904848D8, 0x06030305, 0xF7F6F601, 0x1C0E0E12, + 0xC26161A3, 0x6A35355F, 0xAE5757F9, 0x69B9B9D0, 0x17868691, 0x99C1C158, 0x3A1D1D27, 0x279E9EB9, + 0xD9E1E138, 0xEBF8F813, 0x2B9898B3, 0x22111133, 0xD26969BB, 0xA9D9D970, 0x078E8E89, 0x339494A7, + 0x2D9B9BB6, 0x3C1E1E22, 0x15878792, 0xC9E9E920, 0x87CECE49, 0xAA5555FF, 0x50282878, 0xA5DFDF7A, + 0x038C8C8F, 0x59A1A1F8, 0x09898980, 0x1A0D0D17, 0x65BFBFDA, 0xD7E6E631, 0x844242C6, 0xD06868B8, + 0x824141C3, 0x299999B0, 0x5A2D2D77, 0x1E0F0F11, 0x7BB0B0CB, 0xA85454FC, 0x6DBBBBD6, 0x2C16163A, +}; + +static const u32_t AES_E2[256] = { + 0xA5C66363, 0x84F87C7C, 0x99EE7777, 0x8DF67B7B, 0x0DFFF2F2, 0xBDD66B6B, 0xB1DE6F6F, 0x5491C5C5, + 0x50603030, 0x03020101, 0xA9CE6767, 0x7D562B2B, 0x19E7FEFE, 0x62B5D7D7, 0xE64DABAB, 0x9AEC7676, + 0x458FCACA, 0x9D1F8282, 0x4089C9C9, 0x87FA7D7D, 0x15EFFAFA, 0xEBB25959, 0xC98E4747, 0x0BFBF0F0, + 0xEC41ADAD, 0x67B3D4D4, 0xFD5FA2A2, 0xEA45AFAF, 0xBF239C9C, 0xF753A4A4, 0x96E47272, 0x5B9BC0C0, + 0xC275B7B7, 0x1CE1FDFD, 0xAE3D9393, 0x6A4C2626, 0x5A6C3636, 0x417E3F3F, 0x02F5F7F7, 0x4F83CCCC, + 0x5C683434, 0xF451A5A5, 0x34D1E5E5, 0x08F9F1F1, 0x93E27171, 0x73ABD8D8, 0x53623131, 0x3F2A1515, + 0x0C080404, 0x5295C7C7, 0x65462323, 0x5E9DC3C3, 0x28301818, 0xA1379696, 0x0F0A0505, 0xB52F9A9A, + 0x090E0707, 0x36241212, 0x9B1B8080, 0x3DDFE2E2, 0x26CDEBEB, 0x694E2727, 0xCD7FB2B2, 0x9FEA7575, + 0x1B120909, 0x9E1D8383, 0x74582C2C, 0x2E341A1A, 0x2D361B1B, 0xB2DC6E6E, 0xEEB45A5A, 0xFB5BA0A0, + 0xF6A45252, 0x4D763B3B, 0x61B7D6D6, 0xCE7DB3B3, 0x7B522929, 0x3EDDE3E3, 0x715E2F2F, 0x97138484, + 0xF5A65353, 0x68B9D1D1, 0x00000000, 0x2CC1EDED, 0x60402020, 0x1FE3FCFC, 0xC879B1B1, 0xEDB65B5B, + 0xBED46A6A, 0x468DCBCB, 0xD967BEBE, 0x4B723939, 0xDE944A4A, 0xD4984C4C, 0xE8B05858, 0x4A85CFCF, + 0x6BBBD0D0, 0x2AC5EFEF, 0xE54FAAAA, 0x16EDFBFB, 0xC5864343, 0xD79A4D4D, 0x55663333, 0x94118585, + 0xCF8A4545, 0x10E9F9F9, 0x06040202, 0x81FE7F7F, 0xF0A05050, 0x44783C3C, 0xBA259F9F, 0xE34BA8A8, + 0xF3A25151, 0xFE5DA3A3, 0xC0804040, 0x8A058F8F, 0xAD3F9292, 0xBC219D9D, 0x48703838, 0x04F1F5F5, + 0xDF63BCBC, 0xC177B6B6, 0x75AFDADA, 0x63422121, 0x30201010, 0x1AE5FFFF, 0x0EFDF3F3, 0x6DBFD2D2, + 0x4C81CDCD, 0x14180C0C, 0x35261313, 0x2FC3ECEC, 0xE1BE5F5F, 0xA2359797, 0xCC884444, 0x392E1717, + 0x5793C4C4, 0xF255A7A7, 0x82FC7E7E, 0x477A3D3D, 0xACC86464, 0xE7BA5D5D, 0x2B321919, 0x95E67373, + 0xA0C06060, 0x98198181, 0xD19E4F4F, 0x7FA3DCDC, 0x66442222, 0x7E542A2A, 0xAB3B9090, 0x830B8888, + 0xCA8C4646, 0x29C7EEEE, 0xD36BB8B8, 0x3C281414, 0x79A7DEDE, 0xE2BC5E5E, 0x1D160B0B, 0x76ADDBDB, + 0x3BDBE0E0, 0x56643232, 0x4E743A3A, 0x1E140A0A, 0xDB924949, 0x0A0C0606, 0x6C482424, 0xE4B85C5C, + 0x5D9FC2C2, 0x6EBDD3D3, 0xEF43ACAC, 0xA6C46262, 0xA8399191, 0xA4319595, 0x37D3E4E4, 0x8BF27979, + 0x32D5E7E7, 0x438BC8C8, 0x596E3737, 0xB7DA6D6D, 0x8C018D8D, 0x64B1D5D5, 0xD29C4E4E, 0xE049A9A9, + 0xB4D86C6C, 0xFAAC5656, 0x07F3F4F4, 0x25CFEAEA, 0xAFCA6565, 0x8EF47A7A, 0xE947AEAE, 0x18100808, + 0xD56FBABA, 0x88F07878, 0x6F4A2525, 0x725C2E2E, 0x24381C1C, 0xF157A6A6, 0xC773B4B4, 0x5197C6C6, + 0x23CBE8E8, 0x7CA1DDDD, 0x9CE87474, 0x213E1F1F, 0xDD964B4B, 0xDC61BDBD, 0x860D8B8B, 0x850F8A8A, + 0x90E07070, 0x427C3E3E, 0xC471B5B5, 0xAACC6666, 0xD8904848, 0x05060303, 0x01F7F6F6, 0x121C0E0E, + 0xA3C26161, 0x5F6A3535, 0xF9AE5757, 0xD069B9B9, 0x91178686, 0x5899C1C1, 0x273A1D1D, 0xB9279E9E, + 0x38D9E1E1, 0x13EBF8F8, 0xB32B9898, 0x33221111, 0xBBD26969, 0x70A9D9D9, 0x89078E8E, 0xA7339494, + 0xB62D9B9B, 0x223C1E1E, 0x92158787, 0x20C9E9E9, 0x4987CECE, 0xFFAA5555, 0x78502828, 0x7AA5DFDF, + 0x8F038C8C, 0xF859A1A1, 0x80098989, 0x171A0D0D, 0xDA65BFBF, 0x31D7E6E6, 0xC6844242, 0xB8D06868, + 0xC3824141, 0xB0299999, 0x775A2D2D, 0x111E0F0F, 0xCB7BB0B0, 0xFCA85454, 0xD66DBBBB, 0x3A2C1616, +}; + +static const u32_t AES_E3[256] = { + 0x63A5C663, 0x7C84F87C, 0x7799EE77, 0x7B8DF67B, 0xF20DFFF2, 0x6BBDD66B, 0x6FB1DE6F, 0xC55491C5, + 0x30506030, 0x01030201, 0x67A9CE67, 0x2B7D562B, 0xFE19E7FE, 0xD762B5D7, 0xABE64DAB, 0x769AEC76, + 0xCA458FCA, 0x829D1F82, 0xC94089C9, 0x7D87FA7D, 0xFA15EFFA, 0x59EBB259, 0x47C98E47, 0xF00BFBF0, + 0xADEC41AD, 0xD467B3D4, 0xA2FD5FA2, 0xAFEA45AF, 0x9CBF239C, 0xA4F753A4, 0x7296E472, 0xC05B9BC0, + 0xB7C275B7, 0xFD1CE1FD, 0x93AE3D93, 0x266A4C26, 0x365A6C36, 0x3F417E3F, 0xF702F5F7, 0xCC4F83CC, + 0x345C6834, 0xA5F451A5, 0xE534D1E5, 0xF108F9F1, 0x7193E271, 0xD873ABD8, 0x31536231, 0x153F2A15, + 0x040C0804, 0xC75295C7, 0x23654623, 0xC35E9DC3, 0x18283018, 0x96A13796, 0x050F0A05, 0x9AB52F9A, + 0x07090E07, 0x12362412, 0x809B1B80, 0xE23DDFE2, 0xEB26CDEB, 0x27694E27, 0xB2CD7FB2, 0x759FEA75, + 0x091B1209, 0x839E1D83, 0x2C74582C, 0x1A2E341A, 0x1B2D361B, 0x6EB2DC6E, 0x5AEEB45A, 0xA0FB5BA0, + 0x52F6A452, 0x3B4D763B, 0xD661B7D6, 0xB3CE7DB3, 0x297B5229, 0xE33EDDE3, 0x2F715E2F, 0x84971384, + 0x53F5A653, 0xD168B9D1, 0x00000000, 0xED2CC1ED, 0x20604020, 0xFC1FE3FC, 0xB1C879B1, 0x5BEDB65B, + 0x6ABED46A, 0xCB468DCB, 0xBED967BE, 0x394B7239, 0x4ADE944A, 0x4CD4984C, 0x58E8B058, 0xCF4A85CF, + 0xD06BBBD0, 0xEF2AC5EF, 0xAAE54FAA, 0xFB16EDFB, 0x43C58643, 0x4DD79A4D, 0x33556633, 0x85941185, + 0x45CF8A45, 0xF910E9F9, 0x02060402, 0x7F81FE7F, 0x50F0A050, 0x3C44783C, 0x9FBA259F, 0xA8E34BA8, + 0x51F3A251, 0xA3FE5DA3, 0x40C08040, 0x8F8A058F, 0x92AD3F92, 0x9DBC219D, 0x38487038, 0xF504F1F5, + 0xBCDF63BC, 0xB6C177B6, 0xDA75AFDA, 0x21634221, 0x10302010, 0xFF1AE5FF, 0xF30EFDF3, 0xD26DBFD2, + 0xCD4C81CD, 0x0C14180C, 0x13352613, 0xEC2FC3EC, 0x5FE1BE5F, 0x97A23597, 0x44CC8844, 0x17392E17, + 0xC45793C4, 0xA7F255A7, 0x7E82FC7E, 0x3D477A3D, 0x64ACC864, 0x5DE7BA5D, 0x192B3219, 0x7395E673, + 0x60A0C060, 0x81981981, 0x4FD19E4F, 0xDC7FA3DC, 0x22664422, 0x2A7E542A, 0x90AB3B90, 0x88830B88, + 0x46CA8C46, 0xEE29C7EE, 0xB8D36BB8, 0x143C2814, 0xDE79A7DE, 0x5EE2BC5E, 0x0B1D160B, 0xDB76ADDB, + 0xE03BDBE0, 0x32566432, 0x3A4E743A, 0x0A1E140A, 0x49DB9249, 0x060A0C06, 0x246C4824, 0x5CE4B85C, + 0xC25D9FC2, 0xD36EBDD3, 0xACEF43AC, 0x62A6C462, 0x91A83991, 0x95A43195, 0xE437D3E4, 0x798BF279, + 0xE732D5E7, 0xC8438BC8, 0x37596E37, 0x6DB7DA6D, 0x8D8C018D, 0xD564B1D5, 0x4ED29C4E, 0xA9E049A9, + 0x6CB4D86C, 0x56FAAC56, 0xF407F3F4, 0xEA25CFEA, 0x65AFCA65, 0x7A8EF47A, 0xAEE947AE, 0x08181008, + 0xBAD56FBA, 0x7888F078, 0x256F4A25, 0x2E725C2E, 0x1C24381C, 0xA6F157A6, 0xB4C773B4, 0xC65197C6, + 0xE823CBE8, 0xDD7CA1DD, 0x749CE874, 0x1F213E1F, 0x4BDD964B, 0xBDDC61BD, 0x8B860D8B, 0x8A850F8A, + 0x7090E070, 0x3E427C3E, 0xB5C471B5, 0x66AACC66, 0x48D89048, 0x03050603, 0xF601F7F6, 0x0E121C0E, + 0x61A3C261, 0x355F6A35, 0x57F9AE57, 0xB9D069B9, 0x86911786, 0xC15899C1, 0x1D273A1D, 0x9EB9279E, + 0xE138D9E1, 0xF813EBF8, 0x98B32B98, 0x11332211, 0x69BBD269, 0xD970A9D9, 0x8E89078E, 0x94A73394, + 0x9BB62D9B, 0x1E223C1E, 0x87921587, 0xE920C9E9, 0xCE4987CE, 0x55FFAA55, 0x28785028, 0xDF7AA5DF, + 0x8C8F038C, 0xA1F859A1, 0x89800989, 0x0D171A0D, 0xBFDA65BF, 0xE631D7E6, 0x42C68442, 0x68B8D068, + 0x41C38241, 0x99B02999, 0x2D775A2D, 0x0F111E0F, 0xB0CB7BB0, 0x54FCA854, 0xBBD66DBB, 0x163A2C16, +}; + +static const u32_t AES_E4[256] = { + 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491, + 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC, + 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB, + 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B, + 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83, + 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A, + 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F, + 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA, + 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B, + 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713, + 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6, + 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85, + 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411, + 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B, + 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1, + 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF, + 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E, + 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6, + 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B, + 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD, + 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8, + 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2, + 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049, + 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810, + 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197, + 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F, + 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C, + 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927, + 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733, + 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5, + 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0, + 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C, +}; + +#define msbf4_read(p) ((p)[0]<<24 | (p)[1]<<16 | (p)[2]<<8 | (p)[3]) +#define msbf4_write(p,v) (p)[0]=(v)>>24,(p)[1]=(v)>>16,(p)[2]=(v)>>8,(p)[3]=(v) +#define swapmsbf(x) ( (x&0xFF)<<24 | (x&0xFF00)<<8 | (x&0xFF0000)>>8 | (x>>24) ) + +#define u1(v) ((uint8_t)(v)) + +#define AES_key4(r1,r2,r3,r0,i) r1 = ki[i+1]; \ + r2 = ki[i+2]; \ + r3 = ki[i+3]; \ + r0 = ki[i] + +#define AES_expr4(r1,r2,r3,r0,i) r1 ^= AES_E4[u1(i)]; \ + r2 ^= AES_E3[u1(i>>8)]; \ + r3 ^= AES_E2[u1(i>>16)]; \ + r0 ^= AES_E1[ (i>>24)] + +#define AES_expr(a,r0,r1,r2,r3,i) a = ki[i]; \ + a ^= ((u32_t)AES_S[ r0>>24 ]<<24); \ + a ^= ((u32_t)AES_S[u1(r1>>16)]<<16); \ + a ^= ((u32_t)AES_S[u1(r2>> 8)]<< 8); \ + a ^= (u32_t)AES_S[u1(r3) ] + +// generate 1+10 roundkeys for encryption with 128-bit key +// read 128-bit key from AESKEY in MSBF, generate roundkey words in place +static void aesroundkeys () { + int i; + u32_t b; + + for( i=0; i<4; i++) { + AESKEY[i] = swapmsbf(AESKEY[i]); + } + + b = AESKEY[3]; + for( ; i<44; i++ ) { + if( i%4==0 ) { + // b = SubWord(RotWord(b)) xor Rcon[i/4] + b = ((u32_t)AES_S[u1(b >> 16)] << 24) ^ + ((u32_t)AES_S[u1(b >> 8)] << 16) ^ + ((u32_t)AES_S[u1(b) ] << 8) ^ + ((u32_t)AES_S[ b >> 24 ] ) ^ + AES_RCON[(i-4)/4]; + } + AESKEY[i] = b ^= AESKEY[i-4]; + } +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +u32_t os_aes (uint8_t mode, uint8_t* buf, u16_t len) { + + aesroundkeys(); + + if( mode & AES_MICNOAUX ) { + AESAUX[0] = AESAUX[1] = AESAUX[2] = AESAUX[3] = 0; + } else { + AESAUX[0] = swapmsbf(AESAUX[0]); + AESAUX[1] = swapmsbf(AESAUX[1]); + AESAUX[2] = swapmsbf(AESAUX[2]); + AESAUX[3] = swapmsbf(AESAUX[3]); + } + + while( (s16_t)len > 0 ) { + u32_t a0, a1, a2, a3; + u32_t t0, t1, t2, t3; + u32_t *ki, *ke; + + // load input block + if( (mode & AES_CTR) || ((mode & AES_MIC) && (mode & AES_MICNOAUX)==0) ) { // load CTR block or first MIC block + a0 = AESAUX[0]; + a1 = AESAUX[1]; + a2 = AESAUX[2]; + a3 = AESAUX[3]; + } + else if( (mode & AES_MIC) && len <= 16 ) { // last MIC block + a0 = a1 = a2 = a3 = 0; // load null block + mode |= ((len == 16) ? 1 : 2) << 4; // set MICSUB: CMAC subkey K1 or K2 + } else + LOADDATA: { // load data block (partially) + for(t0=0; t0<16; t0++) { + t1 = (t1<<8) | ((t0> 4) != 0 ) { // last block + do { + // compute CMAC subkey K1 and K2 + t0 = a0 >> 31; // save MSB + a0 = (a0 << 1) | (a1 >> 31); + a1 = (a1 << 1) | (a2 >> 31); + a2 = (a2 << 1) | (a3 >> 31); + a3 = (a3 << 1); + if( t0 ) a3 ^= 0x87; + } while( --t1 ); + + AESAUX[0] ^= a0; + AESAUX[1] ^= a1; + AESAUX[2] ^= a2; + AESAUX[3] ^= a3; + mode &= ~AES_MICSUB; + goto LOADDATA; + } else { + // save cipher block as new iv + AESAUX[0] = a0; + AESAUX[1] = a1; + AESAUX[2] = a2; + AESAUX[3] = a3; + } + } else { // CIPHER + if( mode & AES_CTR ) { // xor block (partially) + t0 = (len > 16) ? 16: len; + for(t1=0; t1>24); + a0 <<= 8; + if((t1&3)==3) { + a0 = a1; + a1 = a2; + a2 = a3; + } + } + // update counter + AESAUX[3]++; + } else { // ECB + // store block + msbf4_write(buf+0, a0); + msbf4_write(buf+4, a1); + msbf4_write(buf+8, a2); + msbf4_write(buf+12, a3); + } + } + + // update block state + if( (mode & AES_MIC)==0 || (mode & AES_MICNOAUX) ) { + buf += 16; + len -= 16; + } + mode |= AES_MICNOAUX; + } + return AESAUX[0]; +} +#pragma GCC diagnostic pop + +#endif + +#endif // defined(USE_ORIGINAL_AES) diff --git a/src/BasicMAC/basicmac.h b/src/BasicMAC/basicmac.h new file mode 100644 index 0000000..c8a2d73 --- /dev/null +++ b/src/BasicMAC/basicmac.h @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL SEMTECH BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *******************************************************************************/ + +#ifdef __cplusplus +extern "C"{ +#endif +//#definehal_init_ex lmic_hal_init +#include "lmic/lmic.h" + +#ifdef __cplusplus +} +#endif diff --git a/src/BasicMAC/hal/hal.cpp b/src/BasicMAC/hal/hal.cpp new file mode 100644 index 0000000..316f719 --- /dev/null +++ b/src/BasicMAC/hal/hal.cpp @@ -0,0 +1,495 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL SEMTECH BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This the HAL to run LMIC on top of the Arduino environment. + *******************************************************************************/ + +#define _GNU_SOURCE 1 // For fopencookie +// Must be first, otherwise it might have already been included without _GNU_SOURCE +#include +#undef _GNU_SOURCE +// Prevent warning on samd where samd21g18a.h from CMSIS defines this +#undef LITTLE_ENDIAN +#include +#include +#include "../basicmac.h" +#include "hal.h" + +// Datasheet defins typical times until busy goes low. Most are < 200us, +// except when waking up from sleep, which typically takes 3500us. Since +// we cannot know here if we are in sleep, we'll have to assume we are. +// Since 3500 is typical, not maximum, wait a bit more than that. +static unsigned long MAX_BUSY_TIME = 5000; + +// ----------------------------------------------------------------------------- +// I/O + +static void hal_io_init () { + uint8_t i; + // Checks below assume that all special pin values are >= LMIC_UNUSED_PIN, so check that. + #if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + LMIC_STATIC_ASSERT(LMIC_UNUSED_PIN < LMIC_CONTROLLED_BY_DIO3, "Unexpected constant values"); + LMIC_STATIC_ASSERT(LMIC_UNUSED_PIN < LMIC_CONTROLLED_BY_DIO2, "Unexpected constant values"); + #endif // defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + + ASSERT(lmic_pins.nss < LMIC_UNUSED_PIN); + ASSERT(lmic_pins.rst <= LMIC_UNUSED_PIN); + ASSERT(lmic_pins.rx <= LMIC_UNUSED_PIN); + +#if defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) + //DIO0 is required, DIO1 is required for LoRa, DIO2 for FSK + ASSERT(lmic_pins.dio[0] < LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio[1] < LMIC_UNUSED_PIN || lmic_pins.dio[2] < LMIC_UNUSED_PIN); + + ASSERT(lmic_pins.busy == LMIC_UNUSED_PIN); + ASSERT(lmic_pins.tcxo == LMIC_UNUSED_PIN); + ASSERT(lmic_pins.tx <= LMIC_UNUSED_PIN); +#elif defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + // Only DIO1 should be specified + ASSERT(lmic_pins.dio[0] == LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio[1] < LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio[2] == LMIC_UNUSED_PIN); + + ASSERT(lmic_pins.busy <= LMIC_UNUSED_PIN); + ASSERT(lmic_pins.tcxo == LMIC_UNUSED_PIN || lmic_pins.tcxo == LMIC_CONTROLLED_BY_DIO3); + ASSERT(lmic_pins.tx <= LMIC_UNUSED_PIN || lmic_pins.tx == LMIC_CONTROLLED_BY_DIO2); +#else + #error "Unknown radio type?" +#endif + + // Write HIGH to deselect (NSS is active low). Do this before + // setting output, to prevent a moment of OUTPUT LOW on e.g. AVR. + digitalWrite(lmic_pins.nss, HIGH); + pinMode(lmic_pins.nss, OUTPUT); + // Write HIGH again after setting output, for architectures that + // reset to LOW when setting OUTPUT (e.g. arduino-STM32L4). + digitalWrite(lmic_pins.nss, HIGH); + + if (lmic_pins.tx < LMIC_UNUSED_PIN) + pinMode(lmic_pins.tx, OUTPUT); + if (lmic_pins.rx < LMIC_UNUSED_PIN) + pinMode(lmic_pins.rx, OUTPUT); + if (lmic_pins.rst < LMIC_UNUSED_PIN) + pinMode(lmic_pins.rst, OUTPUT); + if (lmic_pins.busy < LMIC_UNUSED_PIN) + pinMode(lmic_pins.busy, INPUT); + if (lmic_pins.tcxo < LMIC_UNUSED_PIN) + pinMode(lmic_pins.tcxo, OUTPUT); + + for (i = 0; i < NUM_DIO; ++i) { + if (lmic_pins.dio[i] < LMIC_UNUSED_PIN) + pinMode(lmic_pins.dio[i], INPUT); + } +} + +// rx = 0, tx = 1, off = -1 +void hal_ant_switch (uint8_t val) { + // TODO: Support separate pin for TX2 (PA_BOOST output) + if (lmic_pins.tx < LMIC_UNUSED_PIN) + digitalWrite(lmic_pins.tx, val == HAL_ANTSW_TX || val == HAL_ANTSW_TX2); + if (lmic_pins.rx < LMIC_UNUSED_PIN) + digitalWrite(lmic_pins.rx, val == HAL_ANTSW_RX); +} + +// set radio RST pin to given value (or keep floating!) +bool hal_pin_rst (uint8_t val) { + if (lmic_pins.rst >= LMIC_UNUSED_PIN) + return false; + + if(val == 0 || val == 1) { // drive pin + pinMode(lmic_pins.rst, OUTPUT); + digitalWrite(lmic_pins.rst, val); + } else { // keep pin floating + pinMode(lmic_pins.rst, INPUT); + } + return true; +} + +void hal_irqmask_set (int /* mask */) { + // Not implemented +} + +static bool dio_states[NUM_DIO] = {0}; + +static void hal_io_check() { + uint8_t i; + for (i = 0; i < NUM_DIO; ++i) { + if (lmic_pins.dio[i] >= LMIC_UNUSED_PIN) + continue; + + if (dio_states[i] != digitalRead(lmic_pins.dio[i])) { + dio_states[i] = !dio_states[i]; + if (dio_states[i]) + radio_irq_handler(i, hal_ticks()); + } + } +} + +#if defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) +bool hal_pin_tcxo (uint8_t val) { + if (lmic_pins.tcxo >= LMIC_UNUSED_PIN) + return false; + + digitalWrite(lmic_pins.tcxo, val); + return true; +} +#endif // defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) + +void hal_pin_busy_wait (void) { + if (lmic_pins.busy >= LMIC_UNUSED_PIN) { + // TODO: We could probably keep some state so we know the chip + // is in sleep, since otherwise the delay can be much shorter. + // Also, all delays after commands (rather than waking up from + // sleep) are measured from the *end* of the previous SPI + // transaction, so we could wait shorter if we remember when + // that was. + delayMicroseconds(MAX_BUSY_TIME); + } else { + unsigned long start = micros(); + + while((micros() - start) < MAX_BUSY_TIME && digitalRead(lmic_pins.busy)) /* wait */; + } +} + +#if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) +bool hal_dio3_controls_tcxo (void) { + return lmic_pins.tcxo == LMIC_CONTROLLED_BY_DIO3; +} +bool hal_dio2_controls_rxtx (void) { + return lmic_pins.tx == LMIC_CONTROLLED_BY_DIO2; +} +#endif // defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + +// ----------------------------------------------------------------------------- +// SPI + +static const SPISettings settings(10E6, MSBFIRST, SPI_MODE0); + +static void hal_spi_init () { + SPI.begin(); +} + +void hal_spi_select (int on) { + if (on) + SPI.beginTransaction(settings); + else + SPI.endTransaction(); + + //Serial.println(val?">>":"<<"); + digitalWrite(lmic_pins.nss, !on); +} + +// perform SPI transaction with radio +uint8_t hal_spi (uint8_t out) { + uint8_t res = SPI.transfer(out); +/* + Serial.print(">"); + Serial.print(out, HEX); + Serial.print("<"); + Serial.println(res, HEX); + */ + return res; +} + +// ----------------------------------------------------------------------------- +// TIME + +static void hal_time_init () { + // Nothing to do +} + +u32_t hal_ticks () { + // Because micros() is scaled down in this function, micros() will + // overflow before the tick timer should, causing the tick timer to + // miss a significant part of its values if not corrected. To fix + // this, the "overflow" serves as an overflow area for the micros() + // counter. It consists of three parts: + // - The US_PER_OSTICK upper bits are effectively an extension for + // the micros() counter and are added to the result of this + // function. + // - The next bit overlaps with the most significant bit of + // micros(). This is used to detect micros() overflows. + // - The remaining bits are always zero. + // + // By comparing the overlapping bit with the corresponding bit in + // the micros() return value, overflows can be detected and the + // upper bits are incremented. This is done using some clever + // bitwise operations, to remove the need for comparisons and a + // jumps, which should result in efficient code. By avoiding shifts + // other than by multiples of 8 as much as possible, this is also + // efficient on AVR (which only has 1-bit shifts). + static uint8_t overflow = 0; + + // Scaled down timestamp. The top US_PER_OSTICK_EXPONENT bits are 0, + // the others will be the lower bits of our return value. + uint32_t scaled = micros() >> US_PER_OSTICK_EXPONENT; + // Most significant byte of scaled + uint8_t msb = scaled >> 24; + // Mask pointing to the overlapping bit in msb and overflow. + const uint8_t mask = (1 << (7 - US_PER_OSTICK_EXPONENT)); + // Update overflow. If the overlapping bit is different + // between overflow and msb, it is added to the stored value, + // so the overlapping bit becomes equal again and, if it changed + // from 1 to 0, the upper bits are incremented. + overflow += (msb ^ overflow) & mask; + + // Return the scaled value with the upper bits of stored added. The + // overlapping bit will be equal and the lower bits will be 0, so + // bitwise or is a no-op for them. + return scaled | ((uint32_t)overflow << 24); + + // 0 leads to correct, but overly complex code (it could just return + // micros() unmodified), 8 leaves no room for the overlapping bit. + static_assert(US_PER_OSTICK_EXPONENT > 0 && US_PER_OSTICK_EXPONENT < 8, "Invalid US_PER_OSTICK_EXPONENT value"); +} + +u64_t hal_xticks (void) { + // TODO + return hal_ticks(); +} +/* Not actually used now +s16_t hal_subticks (void) { + // TODO + return 0; +} +*/ + +// Returns the number of ticks until time. Negative values indicate that +// time has already passed. +static s32_t delta_time(u32_t time) { + return (s32_t)(time - hal_ticks()); +} + +void hal_waitUntil (u32_t time) { + s32_t delta = delta_time(time); + // From delayMicroseconds docs: Currently, the largest value that + // will produce an accurate delay is 16383. + while (delta > (16000 / US_PER_OSTICK)) { + delay(16); + delta -= (16000 / US_PER_OSTICK); + } + if (delta > 0) + delayMicroseconds(delta * US_PER_OSTICK); +} + +// check and rewind for target time +uint8_t hal_checkTimer (u32_t time) { + // No need to schedule wakeup, since we're not sleeping + return delta_time(time) <= 0; +} + +static uint8_t irqlevel = 0; + +void hal_disableIRQs () { + noInterrupts(); + irqlevel++; +} + +void hal_enableIRQs () { + if(--irqlevel == 0) { + interrupts(); + + // Instead of using proper interrupts (which are a bit tricky + // and/or not available on all pins on AVR), just poll the pin + // values. Since os_runloop disables and re-enables interrupts, + // putting this here makes sure we check at least once every + // loop. + // + // As an additional bonus, this prevents the can of worms that + // we would otherwise get for running SPI transfers inside ISRs + hal_io_check(); + } +} + +uint8_t hal_sleep (uint8_t type, u32_t targettime) { + // Actual sleeping not implemented, but jobs are only run when this + // function returns 0, so make sure we only do that when the + // targettime is close. When asked to sleep forever (until woken up + // by an interrupt), just return immediately to keep polling. + if (type == HAL_SLEEP_FOREVER) + return 0; + + // TODO: What value should we use for "close"? + return delta_time(targettime) < 10 ? 0 : 1; +} + +void hal_watchcount (int /* cnt */) { + // Not implemented +} + +// ----------------------------------------------------------------------------- +// CFG_DEBUG + +#ifdef CFG_DEBUG +static void hal_debug_init() { + #ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); + #endif +} + +#if !defined(CFG_DEBUG_STREAM) +#error "CFG_DEBUG needs CFG_DEBUG_STREAM defined in target-config.h" +#endif + +void hal_debug_str (const char* str) { + CFG_DEBUG_STREAM.print(str); +} + +void hal_debug_led (int val) { + #ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, val); + #endif +} +#endif // CFG_DEBUG + +// ----------------------------------------------------------------------------- + +#if defined(LMIC_PRINTF_TO) +#if defined(__AVR__) +// On AVR, use the AVR-specific fdev_setup_stream to redirect stdout +static int uart_putchar (char c, FILE *) +{ + LMIC_PRINTF_TO.write(c) ; + return 0 ; +} + +void hal_printf_init() { + // create a FILE structure to reference our UART output function + static FILE uartout; + memset(&uartout, 0, sizeof(uartout)); + + // fill in the UART file descriptor with pointer to writer. + fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE); + + // The uart is the standard output device STDOUT. + stdout = &uartout ; +} +#else +// On non-AVR platforms, use the somewhat more complex "cookie"-based +// approach to custom streams. This is a GNU-specific extension to libc. +static ssize_t uart_putchar (void *, const char *buf, size_t len) { + auto res = LMIC_PRINTF_TO.write(buf, len); + // Since the C interface has no meaningful way to flush (fflush() is a + // no-op on AVR since stdio does not introduce any buffering), just flush + // every byte. + LMIC_PRINTF_TO.flush(); + return res; +} + +static cookie_io_functions_t functions = { + .read = NULL, + .write = uart_putchar, + .seek = NULL, + .close = NULL +}; + +void hal_printf_init() { + stdout = fopencookie(NULL, "w", functions); + // Disable buffering, so the callbacks get called right away + setbuf(stdout, nullptr); +} +#endif // !defined(__AVR__) +#endif // defined(LMIC_PRINTF_TO) + +void hal_init_ex (void * /* bootarg */) { + // configure radio I/O and interrupt handler + hal_io_init(); + // configure radio SPI + hal_spi_init(); + // configure timer and interrupt handler + hal_time_init(); +#if defined(LMIC_PRINTF_TO) + // printf support + hal_printf_init(); +#endif +#ifdef CFG_DEBUG + hal_debug_init(); +#endif +} + +void hal_failed () { +#ifdef CFG_DEBUG + CFG_DEBUG_STREAM.flush(); +#endif + // keep IRQs enabled, to allow e.g. USB to continue to run and allow + // firmware uploads on boards with native USB. + while(1); +} + +void hal_reboot (void) { + // TODO + hal_failed(); +} + +uint8_t hal_getBattLevel (void) { + // Not implemented + return 0; +} + +void hal_setBattLevel (uint8_t /* level */) { + // Not implemented +} + +void hal_fwinfo (hal_fwi* /* fwi */) { + // Not implemented +} + +uint8_t* hal_joineui (void) { + return nullptr; +} + +uint8_t* hal_deveui (void) { + return nullptr; +} + +uint8_t* hal_nwkkey (void) { + return nullptr; +} + +uint8_t* hal_appkey (void) { + return nullptr; +} + +uint8_t* hal_serial (void) { + return nullptr; +} + +u32_t hal_region (void) { + return 0; +} + +u32_t hal_hwid (void) { + return 0; +} + +u32_t hal_unique (void) { + return 0; +} + +u32_t hal_dnonce_next (void) { + return os_getRndU2(); +} diff --git a/src/BasicMAC/hal/hal.h b/src/BasicMAC/hal/hal.h new file mode 100644 index 0000000..3ff312a --- /dev/null +++ b/src/BasicMAC/hal/hal.h @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL SEMTECH BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This the HAL to run LMIC on top of the Arduino environment. + *******************************************************************************/ +#ifndef _hal_hal_h_ +#define _hal_hal_h_ + +static const int NUM_DIO = 3; + +struct lmic_pinmap { + uint8_t nss; + // Written HIGH in TX mode, LOW otherwise. + // Typically used with a single RXTX switch pin. + uint8_t tx; + // Written HIGH in RX mode, LOW otherwise. + // Typicaly used with separate RX/TX pins, to allow switching off + // the antenna switch completely. + uint8_t rx; + uint8_t rst; + uint8_t dio[NUM_DIO]; + uint8_t busy; + uint8_t tcxo; +}; + +// Use this for any unused pins. +const uint8_t LMIC_UNUSED_PIN = 0xfd; +#if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) +// Used for lmic_pinmap.tcxo only +const u8_t LMIC_CONTROLLED_BY_DIO3 = 0xff; +const u8_t LMIC_CONTROLLED_BY_DIO2 = 0xfe; +#endif // defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) +#ifdef AUTO_PIN_MAP +// Declared here, to be defined an initialized by the application +const lmic_pinmap lmic_pins = { + .nss = RADIO_CS_PIN, + .tx = RADIO_TX_PIN, + .rx = RADIO_RX_PIN, + .rst = RADIO_RST_PIN, + .dio = {RADIO_DIO0_PIN , RADIO_DIO1_PIN, RADIO_DIO2_PIN}, + .busy = RADIO_BUSY_PIN, + .tcxo = RADIO_TCXO_PIN, +}; +#else + +extern const lmic_pinmap lmic_pins ; +#endif +#endif // _hal_hal_h_ diff --git a/src/BasicMAC/lmic/aes.h b/src/BasicMAC/lmic/aes.h new file mode 100644 index 0000000..9a4ee48 --- /dev/null +++ b/src/BasicMAC/lmic/aes.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#ifndef _aes_h_ +#define _aes_h_ + +#include "oslmic.h" + +#ifdef __cplusplus +extern "C"{ +#endif + +// ====================================================================== +// AES support +// !!Keep in sync with lorabase.hpp!! +// !!Keep in sync with bootloader/aes.c!! + +#ifndef AES_ENC // if AES_ENC is defined as macro all other values must be too +#define AES_ENC 0x00 +#define AES_DEC 0x01 +#define AES_MIC 0x02 +#define AES_CTR 0x04 +#define AES_MICNOAUX 0x08 +#endif +#ifndef AESkey // if AESkey is defined as macro all other values must be too +extern u8_t* AESkey; +extern u8_t* AESaux; +#endif +#ifndef os_aes +u32_t os_aes (u8_t mode, u8_t* buf, u16_t len); +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _aes_h_ diff --git a/src/BasicMAC/lmic/board.h b/src/BasicMAC/lmic/board.h new file mode 100644 index 0000000..9d8cd0d --- /dev/null +++ b/src/BasicMAC/lmic/board.h @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL SEMTECH BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *******************************************************************************/ + +#include "../../target-config.h" diff --git a/src/BasicMAC/lmic/debug.c b/src/BasicMAC/lmic/debug.c new file mode 100644 index 0000000..af90059 --- /dev/null +++ b/src/BasicMAC/lmic/debug.c @@ -0,0 +1,272 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#include "lmic.h" + +#ifdef CFG_DEBUG + +#include + +void debug_led (int val) { + hal_debug_led(val); +} + +void debug_str (const char* str) { + hal_debug_str(str); +} + +static int debug_itoa (char* buf, u32_t val, int base, int mindigits, int exp, int prec, char sign) { + char num[33], *p = num, *b = buf; + if (sign) { + if ((s32_t) val < 0) { + val = -val; + *b++ = '-'; + } else if (sign != '-') { + *b++ = sign; // space or plus + } + } + if (mindigits > 32) { + mindigits = 32; + } + do { + int m = val % base; + *p++ = (m <= 9) ? m + '0' : m - 10 + 'A'; + if (p - num == exp) *p++ = '.'; + } while ( (val /= base) || p - num < mindigits ); + do { + *b++ = *--p; + } while (p > num + exp - prec); + *b = 0; + return b - buf; +} + +static int strpad (char *buf, int size, const char *str, int len, int width, int leftalign, char pad) { + if (len > width) { + width = len; + } + if (width > size) { + width = size; + } + for (int i = 0, npad = width - len; i < width; i++) { + buf[i] = (leftalign) ? ((i < len) ? str[i] : pad) : ((i < npad) ? pad : str[i - npad]); + } + return width; +} + +static const char* const evnames[] = { + [EV_SCAN_TIMEOUT] = "SCAN_TIMEOUT", + [EV_BEACON_FOUND] = "BEACON_FOUND", + [EV_BEACON_MISSED] = "BEACON_MISSED", + [EV_BEACON_TRACKED] = "BEACON_TRACKED", + [EV_JOINING] = "JOINING", + [EV_JOINED] = "JOINED", + [EV_RFU1] = "RFU1", + [EV_JOIN_FAILED] = "JOIN_FAILED", + [EV_REJOIN_FAILED] = "REJOIN_FAILED", + [EV_TXCOMPLETE] = "TXCOMPLETE", + [EV_LOST_TSYNC] = "LOST_TSYNC", + [EV_RESET] = "RESET", + [EV_RXCOMPLETE] = "RXCOMPLETE", + [EV_LINK_DEAD] = "LINK_DEAD", + [EV_LINK_ALIVE] = "LINK_ALIVE", + [EV_SCAN_FOUND] = "SCAN_FOUND", + [EV_TXSTART] = "TXSTART", + [EV_TXDONE] = "TXDONE", + [EV_DATARATE] = "DATARATE", + [EV_START_SCAN] = "START_SCAN", + [EV_ADR_BACKOFF] = "ADR_BACKOFF", +}; + +static int debug_vsnprintf(char *str, int size, const char *format, va_list arg) { + char c, *dst = str, *end = str + size - 1; + int width, left, base, zero, space, plus, prec, sign, longint; + + while ( (c = *format++) && dst < end ) { + if (c != '%') { + *dst++ = c; + } else { + // flags + width = prec = left = zero = sign = space = plus = longint = 0; + while ( (c = *format++) ) { + if (c == '-') left = 1; + else if (c == ' ') space = 1; + else if (c == '+') plus = 1; + else if (c == '0') zero = 1; + else break; + } + // width + if (c == '*') { + width = va_arg(arg, int); + c = *format++; + } else { + while (c >= '0' && c <= '9') { + width = width * 10 + c - '0'; + c = *format++; + } + } + // precision + if (c == '.') { + c = *format++; + if (c == '*') { + prec = va_arg(arg, int); + c = *format++; + } else { + while (c >= '0' && c <= '9') { + prec = prec * 10 + c - '0'; + c = *format++; + } + } + } + // length + if(c == 'l') { + c = *format++; + longint = 1; + } + // conversion specifiers + switch (c) { + case 'c': // character + c = va_arg(arg, int); + // fallthrough + case '%': // percent literal + *dst++ = c; + break; + case 's': { // nul-terminated string + char *s = va_arg(arg, char *); + int l = strlen(s); + if(prec && l > prec) { + l = prec; + } + dst += strpad(dst, end - dst, s, l, width, left, ' '); + break; + } + case 'd': // signed integer as decimal + sign = (plus) ? '+' : (space) ? ' ' : '-'; + // fallthrough + case 'u': // unsigned integer as decimal + base = 10; + goto numeric; + case 'x': + case 'X': // unsigned integer as hex + base = 16; + goto numeric; + case 'b': // unsigned integer as binary + base = 2; + numeric: { + char num[33], pad = ' '; + if (zero && left == 0 && prec == 0) { + prec = width - 1; // have debug_itoa() do the leading zero-padding for correct placement of sign + pad = '0'; + } + u32_t val = longint ? va_arg(arg, long) : va_arg(arg, int); + int len = debug_itoa(num, val, base, prec, 0, 0, sign); + dst += strpad(dst, end - dst, num, len, width, left, pad); + break; + } + case 'F': { // signed integer and exponent as fixed-point decimal + char num[33], pad = (zero && left == 0) ? '0' : ' '; + u32_t val = va_arg(arg, u32_t); + int exp = va_arg(arg, int); + int len = debug_itoa(num, val, 10, exp + 2, exp, (prec) ? prec : exp, (plus) ? '+' : (space) ? ' ' : '-'); + dst += strpad(dst, end - dst, num, len, width, left, pad); + break; + } + case 'e': { // LMIC event name + unsigned ev = va_arg(arg, unsigned); + const char *evn = (ev < sizeof(evnames) / sizeof(evnames[0]) && evnames[ev]) ? evnames[ev] : "UNKNOWN"; + dst += strpad(dst, end - dst, evn, strlen(evn), width, left, ' '); + break; + } + case 'E': { // EUI64, lsbf (xx-xx-xx-xx-xx-xx-xx-xx) + char buf[23], *p = buf; + unsigned char *eui = va_arg(arg, unsigned char *); + for (int i = 7; i >= 0; i--) { + p += debug_itoa(p, eui[i], 16, 2, 0, 0, 0); + if (i) *p++ = '-'; + } + dst += strpad(dst, end - dst, buf, 23, width, left, ' '); + break; + } + case 't': // ostime_t (hh:mm:ss.mmm) + case 'T': { // osxtime_t (ddd.hh:mm:ss) + #if defined(CFG_DEBUG_RAW_TIMESTAMPS) + if (c == 't') { + longint = 1; + base = 10; + goto numeric; + } + #endif + char buf[12], *p = buf; + uint64_t t = ((c == 'T') ? va_arg(arg, uint64_t) : va_arg(arg, uint32_t)) * 1000 / OSTICKS_PER_SEC; + int ms = t % 1000; + t /= 1000; + int sec = t % 60; + t /= 60; + int min = t % 60; + t /= 60; + int hr = t % 24; + t /= 24; + int day = t; + if (c == 'T') { + p += debug_itoa(p, day, 10, 3, 0, 0, 0); + *p++ = '.'; + } + p += debug_itoa(p, hr, 10, 2, 0, 0, 0); + *p++ = ':'; + p += debug_itoa(p, min, 10, 2, 0, 0, 0); + *p++ = ':'; + p += debug_itoa(p, sec, 10, 2, 0, 0, 0); + if (c == 't') { + *p++ = '.'; + p += debug_itoa(p, ms, 10, 3, 0, 0, 0); + } + dst += strpad(dst, end - dst, buf, 12, width, left, ' '); + break; + } + case 'h': { // buffer+length as hex dump (no field padding, but precision/maxsize truncation) + unsigned char *buf = va_arg(arg, unsigned char *); + int len = va_arg(arg, int); + char *top = (prec == 0 || dst + prec > end) ? end : dst + prec; + while (len--) { + if ((len == 0 && top - dst >= 2) || top - dst >= 2 + space + 2) { + dst += debug_itoa(dst, *buf++, 16, 2, 0, 0, 0); + if(space && len && dst < top) *dst++ = ' '; + } else { + while (dst < top) *dst++ = '.'; + } + } + break; + } + default: // (also catch '\0') + goto stop; + } + } + } + stop: + *dst++ = 0; + return dst - str - 1; +} + +int debug_snprintf (char *str, int size, const char *format, ...) { + va_list arg; + int length; + + va_start(arg, format); + length = debug_vsnprintf(str, size, format, arg); + va_end(arg); + return length; +} + +void debug_printf_real (char const *format, ...) { + char buf[256]; + va_list arg; + + va_start(arg, format); + debug_vsnprintf(buf, sizeof(buf), format, arg); + va_end(arg); + debug_str(buf); +} + +#endif diff --git a/src/BasicMAC/lmic/debug.h b/src/BasicMAC/lmic/debug.h new file mode 100644 index 0000000..205064e --- /dev/null +++ b/src/BasicMAC/lmic/debug.h @@ -0,0 +1,65 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#ifndef _debug_h_ +#define _debug_h_ + +#ifndef CFG_DEBUG + +#define debug_snprintf(s,n,f,...) do { } while (0) +#define debug_printf(f,...) do { } while (0) +#define debug_printf_continue(f,...) do { } while (0) +#define debug_str(s) do { } while (0) +#define debug_led(val) do { } while (0) +#define debug_verbose_printf(f,...) do { } while (0) + +#ifdef CFG_DEBUG_VERBOSE +#error CFG_DEBUG_VERBOSE requires CFG_DEBUG +#endif + +#else + +#include + +// When printing 16-bit values, the code uses %d and friends (which +// accept an `int`-sized argument). This works, because these arguments +// are integer-promoted automatically (provided int is at least 16-bits, +// but the C language requires this +// When printing 32-bit values, the code uses %ld (which accepts a +// `long`-sized argument). This only works when `long` is actually +// 32-bits. This is the case on at least ARM and AVR, but to be sure, +// check at compiletime. Since there is no portable LONG_WIDTH, we use +// LONG_MAX instead. +#if LONG_MAX != ((1 << 31) - 1) +#error "long is not exactly 32 bits, printing will fail" +#endif + +// write formatted string to buffer +int debug_snprintf (char *str, int size, const char *format, ...); + +// write formatted string to USART +void debug_printf_real (char const *format, ...); +#define debug_printf(format, ...) debug_printf_real("%10t: " format, os_getTime(), ## __VA_ARGS__) +// To continue a line, omit the timestamp +#define debug_printf_continue(format, ...) debug_printf_real(format, ## __VA_ARGS__) + +// write nul-terminated string to USART +void debug_str (const char* str); + +// set LED state +void debug_led (int val); + +#ifndef CFG_DEBUG_VERBOSE +#define debug_verbose_printf(f,...) do { } while (0) +#define debug_verbose_printf_continue(f,...) do { } while (0) +#else +#define debug_verbose_printf debug_printf +#define debug_verbose_printf_continue debug_printf_continue +#endif + +#endif + +#endif diff --git a/src/BasicMAC/lmic/hal.h b/src/BasicMAC/lmic/hal.h new file mode 100644 index 0000000..3f98b4c --- /dev/null +++ b/src/BasicMAC/lmic/hal.h @@ -0,0 +1,188 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#ifndef _hal_hpp_ +#define _hal_hpp_ + +#ifdef __cplusplus +extern "C"{ +#endif + +#ifdef HAL_IMPL_INC +#include HAL_IMPL_INC +#endif + +/* + * initialize hardware (IO, SPI, TIMER, IRQ). + */ +void hal_init_ex (void* bootarg); + +/* + * set watchdog counter (in 2s units) + */ +void hal_watchcount (int cnt); + +/* + * drive antenna switch (and account power consumption) + */ +#define HAL_ANTSW_OFF 0 +#define HAL_ANTSW_RX 1 +#define HAL_ANTSW_TX 2 +#define HAL_ANTSW_TX2 3 +void hal_ant_switch (u8_t val); + +#if defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) +/* + * control radio TCXO power (0=off, 1=on) + * (return if TCXO is present and in use) + */ +bool hal_pin_tcxo (u8_t val); +#endif // defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) + +/* + * control radio RST pin (0=low, 1=high, 2=floating) + * (return true if reset pin is connected) + */ +bool hal_pin_rst (u8_t val); + +/* + * wait until radio BUSY pin is low + */ +void hal_pin_busy_wait (void); + +/* + * set DIO0/1/2/3 interrupt mask + */ +#define HAL_IRQMASK_DIO0 (1<<0) +#define HAL_IRQMASK_DIO1 (1<<1) +#define HAL_IRQMASK_DIO2 (1<<2) +#define HAL_IRQMASK_DIO3 (1<<3) +void hal_irqmask_set (int mask); + +#if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) +/* + * Returns true if DIO3 should control the TCXO. + * TODO: Reconsider this HAL function, maybe integrate with hal_pin_tcxo + * somehow? + */ +bool hal_dio3_controls_tcxo (void); + +/* + * Returns true if DIO2 should control the RXTX switch. + * TODO: Reconsider this HAL function, maybe integrate with + * hal_ant_switch somehow? + */ +bool hal_dio2_controls_rxtx (void); +#endif // defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + +/* + * drive radio NSS pin (on=low, off=high). + */ +void hal_spi_select (int on); + +/* + * perform 8-bit SPI transaction with radio. + * - write given byte 'outval' + * - read byte and return value + */ +u8_t hal_spi (u8_t outval); + +/* + * disable all CPU interrupts. + * - might be invoked nested + * - will be followed by matching call to hal_enableIRQs() + */ +void hal_disableIRQs (void); + +/* + * enable CPU interrupts. + */ +void hal_enableIRQs (void); + +/* + * put system and CPU in low-power mode, sleep until target time / interrupt. + * - return 0 if target time is close + * - otherwise sleep until target time / interrupt and return non-zero + */ +#define HAL_SLEEP_EXACT 0 +#define HAL_SLEEP_APPROX 1 +#define HAL_SLEEP_FOREVER 2 +u8_t hal_sleep (u8_t type, u32_t targettime); + +/* + * return 32-bit system time in ticks. + */ +u32_t hal_ticks (void); + +/* + * return 64-bit system time in ticks. + */ +u64_t hal_xticks (void); + +/* + * return subticks (1/1024th tick) + */ +s16_t hal_subticks (void); + +/* + * busy-wait until specified timestamp (in ticks) is reached. + */ +void hal_waitUntil (u32_t time); + +/* + * get current battery level + */ +u8_t hal_getBattLevel (void); + +/* + * set current battery level + */ +void hal_setBattLevel (u8_t level); + +/* + * perform fatal failure action. + * - called by assertions + * - action could be HALT or reboot + */ +void hal_failed (void); + +#ifdef CFG_DEBUG + +void hal_debug_str (const char* str); +void hal_debug_led (int val); + +#endif + +typedef struct { + uint32_t blversion; + uint32_t version; + uint32_t crc; + uint32_t flashsz; +} hal_fwi; + +void hal_fwinfo (hal_fwi* fwi); + +u8_t* hal_joineui (void); +u8_t* hal_deveui (void); +u8_t* hal_nwkkey (void); +u8_t* hal_appkey (void); +u8_t* hal_serial (void); +u32_t hal_region (void); +u32_t hal_hwid (void); +u32_t hal_unique (void); + +u32_t hal_dnonce_next (void); + +void hal_reboot (void); +bool hal_set_update (void* ptr); + +void hal_logEv (uint8_t evcat, uint8_t evid, uint32_t evparam); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _hal_hpp_ diff --git a/src/BasicMAC/lmic/hw.h b/src/BasicMAC/lmic/hw.h new file mode 100644 index 0000000..a4a149e --- /dev/null +++ b/src/BasicMAC/lmic/hw.h @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL SEMTECH BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *******************************************************************************/ + +#ifndef _hw_h_ +#define _hw_h_ + +// Dummy file, not used by this HAL (but must be present, since other +// files include it). + +#endif diff --git a/src/BasicMAC/lmic/lce.c b/src/BasicMAC/lmic/lce.c new file mode 100644 index 0000000..9fb867f --- /dev/null +++ b/src/BasicMAC/lmic/lce.c @@ -0,0 +1,182 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#include "oslmic.h" +#include "aes.h" +#include "lce.h" +#include "lmic.h" + + +bool lce_processJoinAccept (u8_t* jacc, u8_t jacclen, u16_t devnonce) { + if( (jacc[0] & HDR_FTYPE) != HDR_FTYPE_JACC || (jacclen != LEN_JA && jacclen != LEN_JAEXT) ) { + return 0; + } + os_getNwkKey(AESkey); + os_aes(AES_ENC, jacc+1, jacclen-1); + + jacclen -= 4; + u32_t mic1 = os_rmsbf4(jacc+jacclen); +#if defined(CFG_lorawan11) + u8_t optneg = jacc[OFF_JA_DLSET] & JA_DLS_OPTNEG; + if( optneg ) { + os_moveMem(jacc+OFF_JA_JOINNONCE+2, jacc+OFF_JA_JOINNONCE, jacclen-OFF_JA_JOINNONCE); + os_wlsbf2(jacc+OFF_JA_JOINNONCE, devnonce); + jacclen += 2; + } +#endif + os_getNwkKey(AESkey); + u32_t mic2 = os_aes(AES_MIC|AES_MICNOAUX, jacc, jacclen); +#if defined(CFG_lorawan11) + if( optneg ) { // Restore orig frame + jacclen -= 2; + os_moveMem(jacc+OFF_JA_JOINNONCE, jacc+OFF_JA_JOINNONCE+2, jacclen-OFF_JA_JOINNONCE); + os_wlsbf4(jacc+jacclen, mic1); + } +#endif + if( mic1 != mic2 ) { + return 0; + } + u8_t* nwkskey = LMIC.lceCtx.nwkSKey; + os_clearMem(nwkskey, 16); + nwkskey[0] = 0x01; + os_copyMem(nwkskey+1, &jacc[OFF_JA_JOINNONCE], LEN_JOINNONCE+LEN_NETID); + os_wlsbf2(nwkskey+1+LEN_JOINNONCE+LEN_NETID, devnonce); + os_copyMem(LMIC.lceCtx.appSKey, nwkskey, 16); + LMIC.lceCtx.appSKey[0] = 0x02; +#if defined(CFG_lorawan11) + os_copyMem(LMIC.lceCtx.nwkSKeyDn, nwkskey, 16); + LMIC.lceCtx.nwkSKeyDn[0] = 0x03; +#endif + + os_getNwkKey(AESkey); + os_aes(AES_ENC, nwkskey, 16); +#if defined(CFG_lorawan11) + if( optneg ) { + os_getNwkKey(AESkey); + os_aes(AES_ENC, LMIC.lceCtx.nwkSKeyDn, 16); + os_getAppKey(AESkey); + } else { + os_copyMem(LMIC.lceCtx.nwkSKeyDn, nwkskey, 16); + os_getNwkKey(AESkey); + } +#else + os_getNwkKey(AESkey); +#endif + os_aes(AES_ENC, LMIC.lceCtx.appSKey, 16); + return 1; +} + + +void lce_addMicJoinReq (u8_t* pdu, int len) { + os_getNwkKey(AESkey); + os_wmsbf4(pdu+len, os_aes(AES_MIC|AES_MICNOAUX, pdu, len)); // MSB because of internal structure of AES +} + +void lce_encKey0 (u8_t* buf) { + os_clearMem(AESkey,16); + os_aes(AES_ENC,buf,16); +} + +static void micB0 (u32_t devaddr, u32_t seqno, u8_t cat, int len) { + os_clearMem(AESaux,16); + AESaux[0] = 0x49; + AESaux[5] = cat; + AESaux[15] = len; + os_wlsbf4(AESaux+ 6,devaddr); + os_wlsbf4(AESaux+10,seqno); +} + +bool lce_verifyMic (s8_t keyid, u32_t devaddr, u32_t seqno, u8_t* pdu, int len) { + micB0(devaddr, seqno, 1, len); + const u8_t* key; + if( keyid == LCE_NWKSKEY ) { +#if defined(CFG_lorawan11) + key = LMIC.lceCtx.nwkSKeyDn; +#else + key = LMIC.lceCtx.nwkSKey; +#endif + } + else if( keyid >= LCE_MCGRP_0 && keyid < LCE_MCGRP_0+LCE_MCGRP_MAX ) { + key = LMIC.lceCtx.mcgroup[keyid - LCE_MCGRP_0].nwkSKeyDn; + } + else { + // Illegal key index + return 0; + } + os_copyMem(AESkey,key,16); + return os_aes(AES_MIC, pdu, len) == os_rmsbf4(pdu+len); +} + +void lce_addMic (s8_t keyid, u32_t devaddr, u32_t seqno, u8_t* pdu, int len) { + if( keyid != LCE_NWKSKEY ) { + return; // Illegal key index + } + micB0(devaddr, seqno, 0, len); + const u8_t* key = LMIC.lceCtx.nwkSKey; + os_copyMem(AESkey,key,16); + // MSB because of internal structure of AES + os_wmsbf4(pdu+len, os_aes(AES_MIC, pdu, len)); +} + +u32_t lce_micKey0 (u32_t devaddr, u32_t seqno, u8_t* pdu, int len) { + micB0(devaddr, seqno, 0, len); + os_clearMem(AESkey,16); + // MSB because of internal structure of AES + u8_t mic[4]; + os_wmsbf4(mic, os_aes(AES_MIC, pdu, len)); + return os_rlsbf4(mic); +} + +void lce_cipher (s8_t keyid, u32_t devaddr, u32_t seqno, int cat, u8_t* payload, int len) { + if(len <= 0 || (cat==LCE_SCC_UP && (LMIC.opmode & OP_NOCRYPT)) ) { + return; + } + const u8_t* key; + if( keyid == LCE_NWKSKEY ) { +#if defined(CFG_lorawan11) + key = cat==LCE_SCC_DN ? LMIC.lceCtx.nwkSKeyDn : LMIC.lceCtx.nwkSKey; +#else + key = LMIC.lceCtx.nwkSKey; +#endif + } + else if( keyid == LCE_APPSKEY ) { + key = LMIC.lceCtx.appSKey; + } + else if( keyid >= LCE_MCGRP_0 && keyid < LCE_MCGRP_0+LCE_MCGRP_MAX ) { + key = LMIC.lceCtx.mcgroup[keyid - LCE_MCGRP_0].appSKey; + cat = LCE_SCC_DN; + } + else { + // Illegal key index + os_clearMem(payload,len); + return; + } + micB0(devaddr, seqno, cat, 1); + AESaux[0] = 0x01; + os_copyMem(AESkey,key,16); + os_aes(AES_CTR, payload, len); +} + + +#if defined(CFG_lorawan11) +void lce_loadSessionKeys (const u8_t* nwkSKey, const u8_t* nwkSKeyDn, const u8_t* appSKey) +#else +void lce_loadSessionKeys (const u8_t* nwkSKey, const u8_t* appSKey) +#endif +{ + if( nwkSKey != (u8_t*)0 ) + os_copyMem(LMIC.lceCtx.nwkSKey, nwkSKey, 16); +#if defined(CFG_lorawan11) + if( nwkSKeyDn != (u8_t*)0 ) + os_copyMem(LMIC.lceCtx.nwkSKeyDn, nwkSKeyDn, 16); +#endif + if( appSKey != (u8_t*)0 ) + os_copyMem(LMIC.lceCtx.appSKey, appSKey, 16); +} + + +void lce_init (void) { + os_clearMem(&LMIC.lceCtx, sizeof(LMIC.lceCtx)); +} diff --git a/src/BasicMAC/lmic/lce.h b/src/BasicMAC/lmic/lce.h new file mode 100644 index 0000000..78b70db --- /dev/null +++ b/src/BasicMAC/lmic/lce.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#ifndef _lce_h_ +#define _lce_h_ + +#include "oslmic.h" + +#ifdef __cplusplus +extern "C"{ +#endif + +// Some keyids: +#define LCE_APPSKEY (-2) +#define LCE_NWKSKEY (-1) +#define LCE_MCGRP_0 ( 0) +#define LCE_MCGRP_MAX ( 2) + +// Stream cipher categories (lce_cipher(..,cat,..): +// Distinct use of the AppSKey must use different key classes +// or plain text will leak: +enum { + LCE_SCC_UP = 0, // std LoRaWAN uplink frame + LCE_SCC_DN = 1, // std LoRaWAN downlink frame + LCE_SCC_FUP = 0x40, // file upload + LCE_SCC_DSE = 0x41, // data streaming engine + LCE_SCC_ROSE = 0x42, // reliable octet streaming engine +}; + +void lce_encKey0 (u8_t* buf); +u32_t lce_micKey0 (u32_t devaddr, u32_t seqno, u8_t* pdu, int len); +bool lce_processJoinAccept (u8_t* jacc, u8_t jacclen, u16_t devnonce); +void lce_addMicJoinReq (u8_t* pdu, int len); +bool lce_verifyMic (s8_t keyid, u32_t devaddr, u32_t seqno, u8_t* pdu, int len); +void lce_addMic (s8_t keyid, u32_t devaddr, u32_t seqno, u8_t* pdu, int len); +void lce_cipher (s8_t keyid, u32_t devaddr, u32_t seqno, int cat, u8_t* payload, int len); +#if defined(CFG_lorawan11) +void lce_loadSessionKeys (const u8_t* nwkSKey, const u8_t* nwkSKeyDn, const u8_t* appSKey); +#else +void lce_loadSessionKeys (const u8_t* nwkSKey, const u8_t* appSKey); +#endif +void lce_init (void); + + +typedef struct lce_ctx_mcgrp { + u8_t nwkSKeyDn[16]; // network session key for down-link + u8_t appSKey[16]; // application session key +} lce_ctx_mcgrp_t; + +typedef struct lce_ctx { + u8_t nwkSKey[16]; // network session key (LoRaWAN1.1: up-link only) +#if defined(CFG_lorawan11) + u8_t nwkSKeyDn[16]; // network session key for down-link +#endif + u8_t appSKey[16]; // application session key + lce_ctx_mcgrp_t mcgroup[LCE_MCGRP_MAX]; +} lce_ctx_t; + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _lce_h_ diff --git a/src/BasicMAC/lmic/lmic.c b/src/BasicMAC/lmic/lmic.c new file mode 100644 index 0000000..124941f --- /dev/null +++ b/src/BasicMAC/lmic/lmic.c @@ -0,0 +1,3570 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +//! \file +#include "lmic.h" + +#if !defined(MINRX_SYMS) +#define MINRX_SYMS 7 // (see bugfix_rxtime() in radio-rx127x.c) +#endif // !defined(MINRX_SYMS) +#define PAMBL_SYMS_BCN BCN_PREAMBLE_LEN +#define PAMBL_SYMS STD_PREAMBLE_LEN +#define PAMBL_FSK 5 +#define PRERX_FSK 2 +#define RXLEN_FSK (PRERX_FSK+5+3) // rx preamble and sync word +#define BCN_100PPM_ms 13 // 13ms = 128sec*100ppm which is roughly +/-100ppm + +#define BCN_INTV_osticks sec2osticks(BCN_INTV_sec) +#define TXRX_GUARD_osticks ms2osticks(TXRX_GUARD_ms) +#define JOIN_GUARD_osticks ms2osticks(JOIN_GUARD_ms) +#define DELAY_DNW1_osticks sec2osticks(DELAY_DNW1) +#define DELAY_DNW2_osticks sec2osticks(DELAY_DNW2) +#define DELAY_JACC1_osticks sec2osticks(DELAY_JACC1) +#define DELAY_JACC2_osticks sec2osticks(DELAY_JACC2) +#define DELAY_EXTDNW2_osticks sec2osticks(DELAY_EXTDNW2) +#define BCN_RESERVE_osticks ms2osticks(BCN_RESERVE_ms) +#define BCN_GUARD_osticks ms2osticks(BCN_GUARD_ms) +#define BCN_WINDOW_osticks ms2osticks(BCN_WINDOW_ms) +#define AIRTIME_BCN_osticks us2osticksRound(AIRTIME_BCN) + +#define DNW2_SAFETY_ZONE_ETSI ms2osticks(3000) +#define DNW2_SAFETY_ZONE_FCC ms2osticks(750) + +// Special APIs - for development or testing +#if defined(CFG_extapi) +#define isTESTMODE() (LMIC.opmode & OP_TESTMODE) +#else +#define isTESTMODE() 0 +#endif + +DEFINE_LMIC; + +// Fwd decls. +static void engineUpdate(void); + + +// ================================================================================ +// BEG OS - default implementations for certain OS suport functions + +#if !defined(HAS_os_calls) + +#if !defined(os_rlsbf2) +u16_t os_rlsbf2 (const u8_t* buf) { + return (u16_t)((u16_t)buf[0] | ((u16_t)buf[1]<<8)); +} +#endif + +#if !defined(os_rmsbf2) +u16_t os_rmsbf2 (const u8_t* buf) { + return (u16_t)((buf[0]<<8) | buf[1]); +} +#endif + +#if !defined(os_rlsbf4) +u32_t os_rlsbf4 (const u8_t* buf) { + return (u32_t)((u32_t)buf[0] | ((u32_t)buf[1]<<8) | ((u32_t)buf[2]<<16) | ((u32_t)buf[3]<<24)); +} +#endif + + +#if !defined(os_rmsbf4) +u32_t os_rmsbf4 (const u8_t* buf) { + return (u32_t)((u32_t)buf[3] | ((u32_t)buf[2]<<8) | ((u32_t)buf[1]<<16) | ((u32_t)buf[0]<<24)); +} +#endif + + +#if !defined(os_wlsbf2) +void os_wlsbf2 (u8_t* buf, u16_t v) { + buf[0] = v; + buf[1] = v>>8; +} +#endif + +#if !defined(os_wmsbf2) +void os_wmsbf2 (u8_t* buf, u16_t v) { + buf[0] = v>>8; + buf[1] = v; +} +#endif + +#if !defined(os_wlsbf3) +void os_wlsbf3 (u8_t* buf, u32_t v) { + buf[0] = v; + buf[1] = v>>8; + buf[2] = v>>16; +} +#endif + +#if !defined(os_wlsbf4) +void os_wlsbf4 (u8_t* buf, u32_t v) { + buf[0] = v; + buf[1] = v>>8; + buf[2] = v>>16; + buf[3] = v>>24; +} +#endif + +#if !defined(os_wmsbf4) +void os_wmsbf4 (u8_t* buf, u32_t v) { + buf[3] = v; + buf[2] = v>>8; + buf[1] = v>>16; + buf[0] = v>>24; +} +#endif + +#if !defined(os_crc16) +// New CRC-16 CCITT(XMODEM) checksum for beacons: +u16_t os_crc16 (u8_t* data, uint len) { + u16_t remainder = 0; + u16_t polynomial = 0x1021; + for( uint i = 0; i < len; i++ ) { + remainder ^= data[i] << 8; + for( u8_t bit = 8; bit > 0; bit--) { + if( (remainder & 0x8000) ) + remainder = (remainder << 1) ^ polynomial; + else + remainder <<= 1; + } + } + return remainder; +} +#endif + +#endif // !HAS_os_calls + + +// END OS - default implementations for certain OS suport functions +// ================================================================================ + +// ================================================================================ +// BEG NEW REGION STUFF + +#define LORA_UP_RPS(sf,bw) (MAKE_LORA_RPS((sf),(bw),CR_4_5,0,0)) +#define LORA_DN_RPS(sf,bw) (MAKE_LORA_RPS((sf),(bw),CR_4_5,0,1)) +#define FSK_UP_RPS() (MAKE_FSK_RPS(0)) + +// data rate tables +#ifdef REG_DRTABLE_EU +static const u8_t DR2RPS_EU[16] = { + LORA_UP_RPS(SF12, BW125), LORA_UP_RPS(SF11, BW125), LORA_UP_RPS(SF10, BW125), LORA_UP_RPS(SF9, BW125), + LORA_UP_RPS(SF8, BW125), LORA_UP_RPS(SF7, BW125), LORA_UP_RPS(SF7, BW250), FSK_UP_RPS(), + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, +}; +#endif +#ifdef REG_DRTABLE_125kHz +static const u8_t DR2RPS_125kHz[16] = { + LORA_UP_RPS(SF12, BW125), LORA_UP_RPS(SF11, BW125), LORA_UP_RPS(SF10, BW125), LORA_UP_RPS(SF9, BW125), + LORA_UP_RPS(SF8, BW125), LORA_UP_RPS(SF7, BW125), LORA_UP_RPS(SF7, BW250), ILLEGAL_RPS, + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, +}; +#endif +#ifdef REG_DRTABLE_US +static const u8_t DR2RPS_US[16] = { + LORA_UP_RPS(SF10, BW125), LORA_UP_RPS(SF9, BW125), LORA_UP_RPS(SF8, BW125), LORA_UP_RPS(SF7, BW125), + LORA_UP_RPS(SF8, BW500), ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + LORA_DN_RPS(SF12, BW500), LORA_DN_RPS(SF11, BW500), LORA_DN_RPS(SF10, BW500), LORA_DN_RPS(SF9, BW500), + LORA_DN_RPS(SF8, BW500), LORA_DN_RPS(SF7, BW500), ILLEGAL_RPS, ILLEGAL_RPS, +}; +#endif +#ifdef REG_DRTABLE_AU +static const u8_t DR2RPS_AU[16] = { + LORA_UP_RPS(SF12, BW125), LORA_UP_RPS(SF11, BW125), LORA_UP_RPS(SF10, BW125), LORA_UP_RPS(SF9, BW125), + LORA_UP_RPS(SF8, BW125), LORA_UP_RPS(SF7, BW125), LORA_UP_RPS(SF8, BW500), ILLEGAL_RPS, + LORA_DN_RPS(SF12, BW500), LORA_DN_RPS(SF11, BW500), LORA_DN_RPS(SF10, BW500), LORA_DN_RPS(SF9, BW500), + LORA_DN_RPS(SF8, BW500), LORA_DN_RPS(SF7, BW500), ILLEGAL_RPS, ILLEGAL_RPS, +}; +#endif +#ifdef REG_DRTABLE_IN +static const u8_t DR2RPS_IN[16] = { + LORA_UP_RPS(SF12, BW125), LORA_UP_RPS(SF11, BW125), LORA_UP_RPS(SF10, BW125), LORA_UP_RPS(SF9, BW125), + LORA_UP_RPS(SF8, BW125), LORA_UP_RPS(SF7, BW125), ILLEGAL_RPS, FSK_UP_RPS(), + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, + ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, ILLEGAL_RPS, +}; +#endif +#ifdef REG_DYN +static const rfuncs_t RFUNCS_DYN; // fwd decl +#endif +#ifdef REG_FIX +static const rfuncs_t RFUNCS_FIX; // fwd decl +#endif +#define ILLEGAL_RX1DRoff (-128) +#define __RX1DRval(dnoff,di) ((di)==ILLEGAL_RX1DRoff ? ILLEGAL_RX1DRoff : (dnoff)-(di)) +#define RX1DR_OFFSETS(dnoff,d0,d1,d2,d3,d4,d5,d6,d7) { \ + __RX1DRval(dnoff,d0), __RX1DRval(dnoff,d1), \ + __RX1DRval(dnoff,d2), __RX1DRval(dnoff,d3), \ + __RX1DRval(dnoff,d4), __RX1DRval(dnoff,d5), \ + __RX1DRval(dnoff,d6), __RX1DRval(dnoff,d7) } + +static const LMIC_REGION_t REGIONS[REGIONS_COUNT] = { +#ifdef CFG_eu868 + [LMIC_REGION_EU868] = { + .regcode = REGCODE_EU868, + .flags = 0, + .minFreq = 863000000, + .maxFreq = 870000000, + .defaultCh = { 868100000, 868300000, 868500000 }, +#if 0 + .flags = REG_PSA, + .chTxCap = 36, // PSA: 100s/1h max cumulative on time + .ccaThreshold = (-80 + RSSI_OFF), // -80dBm XXX + .ccaTime = us2osticks(160), +#endif + .bands = { + { 869400000, 869650000, CAP_DECI, 29 }, // h1.7 + { 865000000, 868000000, CAP_CENTI, 16 }, // h1.4 + { 868000000, 868600000, CAP_CENTI, 16 }, // h1.5 + { 869700000, 870000000, CAP_CENTI, 16 }, // h1.9 + { 862000000, 863000000, CAP_MILLI, 16 }, // h0, max 350kHz BW (not enforced) + { 863000000, 865000000, CAP_MILLI, 16 }, // h1.3 + { 868700000, 869200000, CAP_MILLI, 16 }, // h1.6 + }, + .beaconFreq = 869525000, + .rx2Freq = 869525000, + .pingFreq = 869525000, + .pingDr = 0, + .rx2Dr = 0, + .beaconDr = 3, + .beaconOffInfo = 8, + .beaconLen = 17, + .beaconAirtime = us2osticksRound(152576), + .maxEirp = 16, + .rx1DrOff = RX1DR_OFFSETS(0, 0, 1, 2, 3, 4, 5, + ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff), + .dr2rps = DR2RPS_EU, + .dr2maxAppPload = {51, 51, 51, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0}, + .rfuncs = &RFUNCS_DYN, + + }, +#endif +#ifdef CFG_as923 + [LMIC_REGION_AS923] = { + .regcode = REGCODE_AS923, + .flags = 0, + .minFreq = 920000000, + .maxFreq = 928000000, + .defaultCh = { 923200000, 923400000 }, + .chTxCap = 10, // 10% + .bands = { + { 0, 0, CAP_NONE, 16 }, // ==> no bands (XXX:bit of a hack) + }, + .beaconFreq = 923400000, + .rx2Freq = 923200000, + .pingFreq = 923400000, + .pingDr = 2, + .rx2Dr = 2, + .beaconDr = 3, + .beaconOffInfo = 8, + .beaconLen = 17, + .beaconAirtime = us2osticksRound(152576), + .maxEirp = 16, + .rx1DrOff = RX1DR_OFFSETS(0, 0, 1, 2, 3, 4, 5, -1, -2), + .dr2rps = DR2RPS_EU, + .dr2maxAppPload = { 0, 0, 11, 53, 125, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0}, + .rfuncs = &RFUNCS_DYN, + }, +#endif +#ifdef CFG_us915 + [LMIC_REGION_US915] = { + .regcode = REGCODE_US915, + .flags = REG_FIXED, + .minFreq = 902000000, + .maxFreq = 928000000, + .baseFreq125 = 902300000, + .baseFreqFix = 903000000, + .baseFreqDn = 923300000, + .numChBlocks = 8, + .numChDnBlocks = 1, + .rx2Freq = 923300000, + .pingDr = 8, + .rx2Dr = 8, + .beaconDr = 8, + .beaconOffInfo = 11, + .beaconLen = 23, + .beaconAirtime = us2osticksRound(305152), + .maxEirp = 30, + .fixDr = 4, + .rx1DrOff = RX1DR_OFFSETS(10, 0, 1, 2, 3, + ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff, + ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff), + .dr2rps = DR2RPS_US, + .dr2maxAppPload = { 11, 53, 125, 242, 242, 0, 0, 0, 53, 129, 242, 242, 242, 242, 0, 0}, + .rfuncs = &RFUNCS_FIX, + }, +#endif +#ifdef CFG_au915 + [LMIC_REGION_AU915] = { + .regcode = REGCODE_AU915, + .flags = REG_FIXED, + .minFreq = 915000000, + .maxFreq = 928000000, + .baseFreq125 = 915200000, + .baseFreqFix = 915900000, + .baseFreqDn = 923300000, + .numChBlocks = 8, + .numChDnBlocks = 1, + .rx2Freq = 923300000, + .pingDr = 10, + .rx2Dr = 8, + .beaconDr = 10, + .beaconOffInfo = 9, + .beaconLen = 19, + .beaconAirtime = us2osticksRound(76288), + .maxEirp = 30, + .joinDr = 2, + .fixDr = 6, + .rx1DrOff = RX1DR_OFFSETS(8, 0, 1, 2, 3, 4, 5, + ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff), + .dr2rps = DR2RPS_AU, + .dr2maxAppPload = {0, 0, 11, 53, 125, 242, 242, 0, 53, 129, 242, 242, 242, 242, 0, 0}, + .rfuncs = &RFUNCS_FIX, + }, +#endif +#ifdef CFG_cn470 + [LMIC_REGION_CN470] = { + .regcode = REGCODE_CN470, + .flags = REG_FIXED, + .minFreq = 470000000, + .maxFreq = 510000000, + .baseFreq125 = 470200000, + .baseFreqFix = 0, + .baseFreqDn = 500300000, + .numChBlocks = 12, + .numChDnBlocks = 6, + .rx2Freq = 505300000, + .pingDr = 0, + .rx2Dr = 0, + .beaconDr = 2, + .beaconOffInfo = 9, + .beaconLen = 19, + .beaconAirtime = us2osticksRound(305152), + .maxEirp = 30, + .fixDr = ILLEGAL_DR, + .rx1DrOff = RX1DR_OFFSETS(0, 0, 1, 2, 3, 4, + ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff, ILLEGAL_RX1DRoff), + .dr2rps = DR2RPS_125kHz, + .dr2maxAppPload = {51, 51, 51, 115, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .rfuncs = &RFUNCS_FIX, + }, +#endif +#ifdef CFG_in865 + [LMIC_REGION_IN865] = { + .regcode = REGCODE_IN865, + .flags = 0, + .minFreq = 865000000, + .maxFreq = 867000000, + .defaultCh = { 865062500, 865402500, 865985000 }, + .bands = { + { 865000000, 867000000, CAP_NONE, 30 } + }, + .beaconFreq = 866550000, + .rx2Freq = 866550000, + .pingFreq = 866550000, + .pingDr = 4, + .rx2Dr = 2, + .beaconDr = 4, + .beaconOffInfo = 7, + .beaconLen = 19, + .beaconAirtime = us2osticksRound(92672), + .maxEirp = 30, + .rx1DrOff = RX1DR_OFFSETS(0, 0, 1, 2, 3, 4, 5, -1, -2), + .dr2rps = DR2RPS_IN, + .dr2maxAppPload = {51, 51, 51, 115, 222, 222, 222, 222, 0, 0, 0, 0, 0, 0, 0, 0}, + .rfuncs = &RFUNCS_DYN, + }, +#endif +}; + +// Workaround for Lacuna LS200 core defining this on the gcc commandline +#undef REGION +#define REGION (*LMIC.region) +#define isREGION(reg) (®ION == ®IONS[LMIC_REGION_##reg]) + +#if defined(REG_FIX) && defined(REG_DYN) +#define REG_IS_FIX() ((REGION.flags & REG_FIXED) != 0) +#elif defined(REG_FIX) +#define REG_IS_FIX() (1) +#else +#define REG_IS_FIX() (0) +#endif + +#define _call_rfunc(fn,...) (REGION.rfuncs->fn(__VA_ARGS__)) +#define disableChannel(...) _call_rfunc( disableChannel, __VA_ARGS__) +#define initDefaultChannels() _call_rfunc( initDefaultChannels) +#define prepareDn() _call_rfunc( prepareDn) +#define applyChannelMap(...) _call_rfunc( applyChannelMap, __VA_ARGS__) +#define checkChannelMap(...) _call_rfunc( checkChannelMap, __VA_ARGS__) +#define syncDatarate() _call_rfunc( syncDatarate) +#define updateTx(...) _call_rfunc( updateTx, __VA_ARGS__) +#define nextTx(...) _call_rfunc( nextTx, __VA_ARGS__) +#define setBcnRxParams() _call_rfunc( setBcnRxParams) + + +static osxtime_t getAvail (avail_t avail) { + return LMIC.baseAvail + sec2osticks(avail); +} + +static void adjAvail (avail_t* pavail, osxtime_t base) { + osxtime_t t = getAvail(*pavail); + *pavail = (t > base) ? osticks2secCeil(t - base) : 0; +} + +static void setAvail (avail_t* pavail, osxtime_t t) { + osxtime_t base = LMIC.baseAvail; + u32_t v; + if( base > t ) { + t = base; // make sure t is not in the past + } + if( (v = osticks2secCeil(t - base)) > 0xffff ) { + // need to fix up baseAvail + base = os_getXTime(); + v = osticks2secCeil(t - base); + ASSERT(v <= 0xffff); + adjAvail(&LMIC.globalAvail, base); +#ifdef REG_DYN + if( !REG_IS_FIX() ) { + for( int i = 0; i < MAX_DYN_CHNLS; i++ ) { + adjAvail(&LMIC.dyn.chAvail[i], base); + } + for( int i=0; i < MAX_BANDS && REGION.bands[i].lo; i++ ) { + adjAvail(&LMIC.dyn.bandAvail[i], base); + } + } +#endif + LMIC.baseAvail = base; + } + *pavail = v; +} + +static dr_t lowerDR (dr_t dr, u8_t n) { + if (dr == CUSTOM_DR) + return dr; + return dr <= n ? 0 : dr-n; +} + +static dr_t fastest125 () { + dr_t dr = 1; // assuming DR=0 is always 125kHz + for(; dr < 16; dr++ ) { + rps_t rps = REGION.dr2rps[dr]; + if( rps == ILLEGAL_RPS || getBw(rps) != BW125 || getNocrc(rps) ) // DN only DR + break; + } + return dr-1; +} + +static inline bit_t validDR (dr_t dr) { + return REGION.dr2rps[dr] != ILLEGAL_RPS; +} + +static rps_t updr2rps (dr_t dr) { + if (dr == CUSTOM_DR) + return LMIC.custom_rps; + return REGION.dr2rps[dr]; +} + +static rps_t dndr2rps (dr_t dr) { + return setNocrc(updr2rps(dr), 1); +} + +#if !defined(DISABLE_CLASSB) +static u8_t numBcnChannels() { + return REG_IS_FIX() ? 8 : 1; +} +#endif // !defined(DISABLE_CLASSB) + +static dr_t rps2dndr (rps_t rps) { + for( dr_t dr = 15; dr != (dr_t)-1; dr-- ) { + if( setNocrc(REGION.dr2rps[dr], 1) == rps ) { + return dr; + } + } + return (dr_t) -1; +} + + +// END NEW REGION STUFF +// ================================================================================ + + +// ================================================================================ +// BEG LORA + +static const u8_t SENSITIVITY[7][3] = { + // ------------bw---------- + // 125kHz 250kHz 500kHz + { 141-109, 141-109, 141-109 }, // FSK + { 141-127, 141-124, 141-121 }, // SF7 + { 141-129, 141-126, 141-123 }, // SF8 + { 141-132, 141-129, 141-126 }, // SF9 + { 141-135, 141-132, 141-129 }, // SF10 + { 141-138, 141-135, 141-132 }, // SF11 + { 141-141, 141-138, 141-135 } // SF12 +}; + +int getSensitivity (rps_t rps) { + return -141 + SENSITIVITY[getSf(rps)][getBw(rps)]; +} + +ostime_t calcAirTime (rps_t rps, u8_t plen) { + if( isFsk(rps) ) { + return (plen+/*preamble*/5+/*syncword*/3+/*len*/1+/*crc*/2) * /*bits/byte*/8 + * (s32_t)OSTICKS_PER_SEC / /*kbit/s*/50000; + } + u8_t bw = getBw(rps); // 0,1,2 = 125,250,500kHz + u8_t sf = getSf(rps); + u8_t sfx = 4*(sf+(7-SF7)); + u8_t q = sfx - 8*enDro(rps); + int tmp = 8*plen - sfx + 28 + (getNocrc(rps)?0:16) - (getIh(rps)?20:0); + if( tmp > 0 ) { + tmp = (tmp + q - 1) / q; + tmp *= getCr(rps)+5; + tmp += 8; + } else { + tmp = 8; + } + tmp = (tmp<<2) + /*preamble*/49 /* 4 * (8 + 4.25) */; + // bw = 125000 = 15625 * 2^3 + // 250000 = 15625 * 2^4 + // 500000 = 15625 * 2^5 + // sf = 7..12 + // + // osticks = tmp * OSTICKS_PER_SEC * 1< counter reduced divisor 125000/8 => 15625 + // 2 => counter 2 shift on tmp + sfx = sf+(7-SF7) - (3+2) - bw; + int div = 15625; + if( sfx > 4 ) { + // prevent 32bit signed int overflow in last step + div >>= sfx-4; + sfx = 4; + } + // Need 32bit arithmetic for this last step + return (((ostime_t)tmp << sfx) * OSTICKS_PER_SEC + div/2) / div; +} + +extern inline rps_t makeLoraRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc); +extern inline rps_t makeFskRps (int nocrc); +extern inline sf_t getSf (rps_t params); +extern inline rps_t setSf (rps_t params, sf_t sf); +extern inline bw_t getBw (rps_t params); +extern inline rps_t setBw (rps_t params, bw_t cr); +extern inline cr_t getCr (rps_t params); +extern inline rps_t setCr (rps_t params, cr_t cr); +extern inline int getNocrc (rps_t params); +extern inline rps_t setNocrc (rps_t params, int nocrc); +extern inline int getIh (rps_t params); +extern inline rps_t setIh (rps_t params, int ih); +extern inline sf_t isLora (rps_t params); +extern inline sf_t isFsk (rps_t params); +extern inline int enDro (rps_t params); + + +// END LORA +// ================================================================================ + + +#if defined(CFG_lorawan11) +static void incPollcnt (void) { + u8_t c = LMIC.pollcnt; + if( c < 0xFF ) + LMIC.pollcnt = c+1; +} + +static void decPollcnt (void) { + u8_t c = LMIC.pollcnt; + if( c > 0 ) + LMIC.pollcnt = c-1; +} +#endif + +// Table below defines the size of one symbol as +// symtime = 2 ^ T(sf,bw) us +// SF: +// BW: |__7___8___9__10__11__12 +// 125kHz | 10 11 12 13 14 15 +// 250kHz | 9 10 11 12 13 14 +// 500kHz | 8 9 10 11 12 13 +// +static ostime_t dr2hsym (dr_t dr, s8_t num) { + rps_t rps = updr2rps(dr); + u8_t sf = getSf(rps); + u8_t bw = getBw(rps); + LMIC_STATIC_ASSERT(BW125 == 0, "BW125 assumed to be 0"); + ASSERT(sf >= SF7 && sf <= SF12 && bw <= BW500); + s32_t us = num << (9 + sf - SF7 - bw); // 10 => 9 half symbol time + return us2osticks(us); +} + +#if !defined(DISABLE_CLASSB) +static ostime_t calcRxWindow (u8_t secs, dr_t dr) { + ostime_t rxoff, err; + + // assume max wobble for missed bcn periods + err = (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns; + if( secs==0 ) { + // aka 128 secs (next becaon) + rxoff = dr2hsym(dr, PAMBL_SYMS_BCN); + rxoff += LMIC.drift; + err += LMIC.lastDriftDiff; + } else { + // scheduled RX window within secs into current beacon period + rxoff = dr2hsym(dr, PAMBL_SYMS); + rxoff += (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp; + err += (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp; + } + // std RX window, enlarged by drift wobble + ostime_t hsym = dr2hsym(dr,1); // 1 symbol in ticks + u32_t rxsyms = MINRX_SYMS + (err+hsym-1) / hsym; // ceil syms + // rxoff is the center of the beacon preamble adjusted by drift + // rxsyms is the width of the rx window + // limit for dr2hsym/rxsym: s8_t + return rxoff - dr2hsym(dr, rxsyms>127 ? 127 : rxsyms); +} + + +// Setup beacon RX parameters assuming we need a tolerance of 'ms' (aka +/-ms) +static void calcBcnRxWindowFromMillis (u8_t ms, bit_t ini) { + if( ini ) { + LMIC.drift = 0; + LMIC.maxDriftDiff = 0; + LMIC.missedBcns = 0; + LMIC.bcninfo.flags |= BCN_NODRIFT|BCN_NODDIFF; + } + ostime_t hsym = dr2hsym(REGION.beaconDr, 1); + ostime_t cpre = dr2hsym(REGION.beaconDr, + PAMBL_SYMS_BCN); // offset: center preamble + int wsyms = (ms2osticksCeil(ms) + hsym - 1) / hsym; // len RX span (2*ms) in syms (ceil) + if( wsyms < MINRX_SYMS ) wsyms = MINRX_SYMS; // no smaller than min + ostime_t whspan = dr2hsym(REGION.beaconDr, wsyms); // half RX span in osticks + LMIC.bcnRxsyms = wsyms; + LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks + cpre - whspan; +} +#endif + +static void iniRxdErr () { + // Avg(rxdErrs) == 0 + // Min(rxdErrs) == -RXDERR_INI/2 + // Min(rxdErrs) == +RXDERR_INI/2 + LMIC.rxdErrs[0] = 0; + for( u8_t u=RXDERR_NUM&0; u>20)+1) > 1 ) // overflow? + return; + LMIC.rxdErrs[LMIC.rxdErrIdx] = err; + LMIC.rxdErrIdx = (LMIC.rxdErrIdx + 1) % RXDERR_NUM; +} + +#if defined(CFG_testpin) || defined(CFG_extapi) +static s32_t evalRxdErr (u32_t* span) { + s32_t min = 0x7FFFFFFF, min2=0x7FFFFFFF; + s32_t max = 0x80000000, max2=0x80000000; + s32_t sum = 0; + for( u8_t u=0; u= max ) { max2=max; max=v; } + else if( v >= max2) { max2=v; } + sum += v; + } + //*span = max-min; + //return (sum + (RXDERR_NUM/2)) / RXDERR_NUM; + *span = max2-min2; + return (sum - max - min + ((RXDERR_NUM-2)/2)) / (RXDERR_NUM-2); +} +#endif + +static void adjustByRxdErr (u8_t rxdelay, u8_t dr) { +#ifdef CFG_testpin + // XXX: for now only in HW regr tests - not yet ready for prime time + u32_t span; + s32_t skew = evalRxdErr(&span); + LMIC.rxtime += (skew * rxdelay + (1<<(RXDERR_SHIFT-1))) >> RXDERR_SHIFT; + ostime_t hsym = dr2hsym(dr,1); + span /= dr2hsym(dr,1); // additional half symbols + LMIC.rxsyms += (span + 1) >> 1; + LMIC.rxtime -= span*hsym; +#else // CFG_testpin + (void)rxdelay; (void)dr; // unused +#endif // CFG_testpin +} + + +#if !defined(DISABLE_CLASSB) +// Setup scheduled RX window (ping/multicast slot) +static void rxschedInit (rxsched_t* rxsched) { + // Relates to the standard in the following way: + // pingNb = 2^(7-intvExp) + // pingOffset = Rand % pingPeriod + // pingPeriod = 2^12 / pingNb = 2^12 / 2^(7-intvExp) = 2^5/2^-intvExp = 32<intvExp <= 7); + u8_t intvExp = rxsched->intvExp; + os_clearMem(LMIC.frame+8,8); + os_wlsbf4(LMIC.frame, LMIC.bcninfo.time); + os_wlsbf4(LMIC.frame+4, LMIC.devaddr); + lce_encKey0(LMIC.frame); + ostime_t off = os_rlsbf2(LMIC.frame) & ((32<rxbase = (LMIC.bcninfo.txtime + + BCN_RESERVE_osticks + + ms2osticks(BCN_SLOT_SPAN_ms * off)); // random offset osticks + rxsched->slot = 0; + rxsched->rxtime = rxsched->rxbase + calcRxWindow(/*secs BCN_RESERVE*/2+(1<dr); + rxsched->rxsyms = LMIC.rxsyms; +} + + +static bit_t rxschedNext (rxsched_t* rxsched, ostime_t cando) { + again: + if( rxsched->rxtime - cando >= 0 ) + return 1; + u8_t slot; + if( (slot=rxsched->slot) >= 128 ) + return 0; + u8_t intv = 1<<(rxsched->intvExp & 0x7); + if( (rxsched->slot = (slot += (intv))) >= 128 ) + return 0; + rxsched->rxtime = rxsched->rxbase + + ((BCN_WINDOW_osticks * (ostime_t)slot) >> BCN_INTV_exp) + + calcRxWindow(/*secs BCN_RESERVE*/2+slot+intv,rxsched->dr); + rxsched->rxsyms = LMIC.rxsyms; + goto again; +} +#endif + + +static ostime_t rndDelay (u8_t secSpan) { + u16_t r = os_getRndU2(); + ostime_t delay = r; + if( delay > OSTICKS_PER_SEC ) + delay = r % (u16_t)OSTICKS_PER_SEC; + if( secSpan > 0 ) + delay += ((u8_t)r % secSpan) * OSTICKS_PER_SEC; + return delay; +} + + +static void txDelay (ostime_t reftime, u8_t secSpan) { + reftime += rndDelay(secSpan); + if( LMIC.globalDutyRate == 0 || (reftime - LMIC.globalDutyAvail) > 0 ) { + LMIC.globalDutyAvail = reftime; + LMIC.opmode |= OP_RNDTX; + } +} + + +static void setDrJoin (u8_t reason, dr_t dr) { + (void)reason; // unused + LMIC.datarate = dr; +} + + +static void setDrTxpow (u8_t reason, dr_t dr, s8_t powadj) { + (void)reason; // unused + if( powadj != KEEP_TXPOWADJ ) + LMIC.txPowAdj = powadj; + if( LMIC.datarate != dr ) { + LMIC.datarate = dr; + LMIC.opmode |= OP_NEXTCHNL; + } +} + + +#if !defined(DISABLE_CLASSB) +void LMIC_stopPingable (void) { + LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI); +} + + +u8_t LMIC_setPingable (u8_t intvExp) { + ASSERT(intvExp <= 7); + // Change setting + if( LMIC.ping.intvExp == intvExp ) { + LMIC.opmode |= OP_PINGABLE; + return 0; // no change + } + // Change of interval requires to disable class B until we got this ACKed + LMIC.ping.intvExp = 0x80 | intvExp; + LMIC.opmode &= ~OP_PINGABLE; + if( (LMIC.opmode & OP_TRACK) != 0 ) + return 1; // already tracking a beacon - communicating change to NWKS + // Start tracking a beacon) + LMIC_enableTracking(3); + return 2; +} +#endif + + +static freq_t rdFreq (u8_t* p) { + freq_t freq = (((freq_t)p[2] << 16) | ((freq_t)p[1] << 8) | (freq_t)p[0]) * 100; + if( freq != 0 && (freq < REGION.minFreq || freq > REGION.maxFreq) ) + return -1; + return freq; +} + + +// Shard between REG_DYN/REG_FIX +static dr_t prepareDnDr (dr_t updr) { + ASSERT(updr != CUSTOM_DR); + + s8_t dndr = (s8_t) updr + REGION.rx1DrOff[LMIC.dn1DrOffIdx]; + dr_t dr8 = REGION.dr2rps[8]; + s8_t mindr = (dr8 != ILLEGAL_RPS && getNocrc(dr8)) ? 8 : 0; + if( dndr < mindr ) + dndr = mindr; + else if( dndr >= 16 ) + dndr = 15; + while( REGION.dr2rps[dndr] == ILLEGAL_RPS ) + dndr -= 1; + return dndr; +} + +// ================================================================================ +// BEG DYNAMIC CHANNEL PLAN REGIONS + +#ifdef REG_DYN + +static void prepareDn_dyn () { + // Check reconfigured DN link freq + freq_t dnfreq = LMIC.dyn.chDnFreq[LMIC.txChnl]; + if( dnfreq ) { + LMIC.freq = dnfreq; + } +} + +static void disableChannel_dyn (u8_t chidx) { + LMIC.dyn.chUpFreq[chidx] = 0; + LMIC.dyn.chDnFreq[chidx] = 0; + LMIC.dyn.chDrMap [chidx] = 0; + LMIC.dyn.channelMap &= ~(1 << chidx); + if (LMIC.dyn.channelMap == 0) { + LMIC.dyn.channelMap = (1 << MIN_DYN_CHNLS) - 1; // safety net + // XXX - won't the default channels have 0 as their freqency + // if they have been disabled in the past? + // I suspect the safety net is not needed anymore... + } +} + +static drmap_t all125up () { + drmap_t map = 0; + for( u8_t dr=0; dr < 16; dr++ ) { + rps_t rps = REGION.dr2rps[dr]; + if( rps != ILLEGAL_RPS && isLora(rps) + && getBw(rps) == BW125 && !getNocrc(rps) ) // not DN only DR + map |= 1<= MAX_DYN_CHNLS) { + return 0; + } + if (freq == 0) { + disableChannel_dyn(chidx); + return 1; + } + // clear lowest bits for band index + freq &= ~BAND_MASK; + if( REGION.bands[0].lo ) { + // Region has bands - freq must fall within one (currently EU868 only) + for (u8_t i = 0; i < MAX_BANDS; i++) { + const band_t* b = ®ION.bands[i]; + // XXX:TODO: take bandwidth into account when checking frequencies + if (freq >= b->lo && freq <= b->hi) { + freq |= i; + goto ok; + } + } + debug_printf("Channel %d frequency (%F) not within any known band, ignoring\r\n", chidx, freq, 6); + return 0; + } + ok: + LMIC.dyn.chUpFreq[chidx] = freq; + LMIC.dyn.chDnFreq[chidx] = 0; // reset DN freq if channel is setup/modified + LMIC.dyn.chDrMap[chidx] = drmap ?: all125up(); + setAvail(&LMIC.dyn.chAvail[chidx], 0); // available right away + LMIC.dyn.channelMap |= 1 << chidx; // enabled right away + return 1; +} + +static void initDefaultChannels_dyn (void) { + os_clearMem(&LMIC.dyn, sizeof(LMIC.dyn)); + drmap_t defaultDrMap = all125up(); + for (u8_t ch = 0; ch < MIN_DYN_CHNLS && REGION.defaultCh[ch]; ch++) { + setupChannel_dyn(ch, REGION.defaultCh[ch], defaultDrMap); + } +} + +static u8_t applyChannelMap_dyn (u8_t chpage, u16_t chmap, u16_t* dest) { + if( chpage == MCMD_LADR_CHP_ALLON ) { + chmap = 0; + for( u8_t ci=0; ci> n) & endrs) { + dr = LMIC.datarate - n; + break; + } + if ((drbit << n) & endrs) { + dr = LMIC.datarate + n; + break; + } + n += 1; + } + debug_printf("Current datarate (%d) not enabled in any channel, using closest instead (%d)\r\n", LMIC.datarate, dr); + setDrTxpow(DRCHG_SET, dr, KEEP_TXPOWADJ); + } +} + +static void updateTx_dyn (ostime_t txbeg) { + ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen); + freq_t freq = LMIC.dyn.chUpFreq[LMIC.txChnl]; + u8_t b = freq & BAND_MASK; + // set frequency/power + LMIC.freq = freq & ~BAND_MASK; + LMIC.txpow = os_min(LMIC.txPowAdj + REGION.maxEirp, REGION.bands[b].txpow); + // Update band duty cycle stats + osxtime_t xnow = os_getXTime(); + //XXX:TBD: osxtime_t xtxbeg = os_time2XTime(txbeg, os_getXTime()); + setAvail(&LMIC.dyn.bandAvail[b], os_time2XTime(txbeg + + airtime * REGION.bands[b].txcap, + xnow)); + // Update channel duty cycle stats + setAvail(&LMIC.dyn.chAvail[LMIC.txChnl], os_time2XTime(txbeg + + airtime * REGION.chTxCap, + xnow)); + // Update global duty cycle stats + if (LMIC.globalDutyRate != 0) { + LMIC.globalDutyAvail = txbeg + (airtime << LMIC.globalDutyRate); + } + debug_verbose_printf("Updating info for TX at %t, airtime will be %t, frequency %.2F. Setting available time for band %u to %u\r\n", txbeg, airtime, LMIC.freq, 6, b, LMIC.dyn.bandAvail[b]); + if( LMIC.globalDutyRate != 0 ) + debug_verbose_printf("Updating global duty avail to %t\r\n", LMIC.globalDutyAvail); +} + +static u8_t selectRandomChnl (u16_t map, u8_t nbits) { + u8_t k; + again: + // Note: we have a small negligible bias of 2^16 % nbits (nbits <= 16 => bias < 0.025%) + k = os_getRndU2() % nbits; + for( u8_t chnl=0; chnl<16; chnl++ ) { + if( (map & (1< 1 ) + goto again; // don't use same channel twice + LMIC.refChnl = chnl; + return chnl; + } + k--; + } + ASSERT(0); + return 0; +} + +// select channel, perform LBT if required +// returns now if ready to send, or time +// when to try again (no free channel or DC). +// will block while doing LBT +static ostime_t nextTx_dyn (ostime_t now) { + drmap_t drbit = 1 << LMIC.datarate; + osxtime_t xnow = os_time2XTime(now, os_getXTime()); + osxtime_t txavail = OSXTIME_MAX; + u8_t cccnt = 0; // number of candidate channels + u16_t ccmap = 0; // candidate channel mask + u16_t pcmap = 0; // probe channel mask +again: + for (u8_t chnl = 0; chnl < MAX_DYN_CHNLS; chnl++) { + u16_t chnlbit = 1 << chnl; + if ((LMIC.dyn.channelMap & chnlbit) == 0 || // channel disabled + (LMIC.dyn.chDrMap[chnl] & drbit) == 0) { // or not enabled for current datarate + continue; + } + // check channel DC availability + osxtime_t avail = getAvail(LMIC.dyn.chAvail[chnl]); + // check band DC availability + osxtime_t bavail = getAvail(LMIC.dyn.bandAvail[LMIC.dyn.chUpFreq[chnl] & BAND_MASK]); + debug_verbose_printf("Considering channel %u, available at %t (in band %u available at %t)\r\n", chnl, (ostime_t)avail, LMIC.dyn.chUpFreq[chnl] & BAND_MASK, (ostime_t)bavail); + if( LMIC.noDC ) + goto addch; + if (REGION.flags & REG_PSA ) { + // PSA: channel can be used if band DC (unconditional) + // or channel DC (PSA) are available + if( bavail <= xnow ) { + avail = bavail; // just use the channel without probe + } else { + pcmap |= chnlbit; // if PSA DC permits then probe this channel + } + } else { + // do not use channel unless both band+channel DC are available + if (bavail > avail) { + avail = bavail; + } + } + if( avail <= xnow ) { + addch: + cccnt += 1; + ccmap |= chnlbit; + } + if( txavail > avail ) { + txavail = avail; + } + } + if (txavail == OSXTIME_MAX) { + debug_verbose_printf("No suitable channel found, trying different datarate\r\n"); + // No suitable channel found - maybe there's no channel which includes current datarate + syncDatarate(); + drmap_t drbit2 = 1 << LMIC.datarate; + ASSERT(drbit != drbit2); + drbit = drbit2; + goto again; + } + + if (cccnt) { + debug_verbose_printf("%u channels are available now\r\n", cccnt); + while( cccnt ) { + u8_t chnl = selectRandomChnl(ccmap, cccnt); + + if (REGION.ccaThreshold + && (((REGION.flags & REG_PSA) == 0) || pcmap & (1 << chnl))) { + // perform CCA + LMIC.rps = updr2rps(LMIC.datarate); + LMIC.freq = LMIC.dyn.chUpFreq[chnl] & ~BAND_MASK; + LMIC.rxtime = REGION.ccaTime; + LMIC.rssi = REGION.ccaThreshold; + os_radio(RADIO_CCA); + if (LMIC.rssi >= REGION.ccaThreshold) { // channel is not available + debug_verbose_printf("Channel %u not available due to CCA\r\n", chnl); + goto unavailable; + } + } else { + // channel decision is stable (i.e. won't change even if not used immediately) + LMIC.opmode &= ~OP_NEXTCHNL; // XXX - not sure if that's necessary, since we only consider channels that can be used NOW + } + debug_verbose_printf("Selected channel %u (%.2F)\r\n", chnl, LMIC.dyn.chUpFreq[chnl] & ~BAND_MASK, 6); + // good to go! + LMIC.txChnl = chnl; + return now; + unavailable: + ccmap &= ~(1 << chnl); + cccnt -= 1; + } + // Avoid being bombarded... + txavail = os_getXTime() + ms2osticks(100); + } + // Earliest duty cycle expiry or earliest time a channel might be tested again + debug_verbose_printf("Channel(s) will become available at %t\r\n", (ostime_t)txavail); + return (ostime_t) txavail; +} + +#if !defined(DISABLE_CLASSB) +static void setBcnRxParams_dyn (void) { + LMIC.dataLen = 0; + LMIC.freq = LMIC.bcnFreq ? LMIC.bcnFreq : REGION.beaconFreq; + LMIC.rps = setIh(setNocrc(dndr2rps(REGION.beaconDr), 1), REGION.beaconLen); +} +#endif + +#endif // REG_DYN + +// END DYNAMIC CHANNEL PLAN REGIONS +// ================================================================================ + + + +static void initJoinLoop (void) { + initDefaultChannels(); + if( REG_IS_FIX() ) { +#ifdef REG_FIX + LMIC.refChnl = 0; + LMIC.txChnl = LMIC.fix.hoplist[LMIC.refChnl]; + setDrJoin(DRCHG_SET, REGION.joinDr); +#endif + } else { + LMIC.txChnl = 0; // XXX - join should use nextTx! + setDrJoin(DRCHG_SET, fastest125()); + } + LMIC.txPowAdj = 0; + LMIC.nbTrans = 0; + ASSERT((LMIC.opmode & OP_NEXTCHNL) == 0); + LMIC.txend = os_getTime() + rndDelay(8); // random delay before first join req +} + +static ostime_t nextJoinState (void) { + u8_t failed = 0; + ostime_t delay; + + if( REG_IS_FIX() ) { +#ifdef REG_FIX + // NOTE: this is incorrect for CN470, but CN470 is a moving target anyway... + if( LMIC.datarate == REGION.fixDr ) { + // switch back to var-DR channels + goto nextblock; + } + LMIC.refChnl += 1; + if( (LMIC.refChnl % REGION.numChBlocks) == 0 ) { + // went through all channels blocks + if( REGION.baseFreqFix ) { + // try fix-DR channel + LMIC.txChnl = (REGION.numChBlocks * 8) + (LMIC.refChnl / REGION.numChBlocks) - 1; + setDrJoin(DRCHG_SET, REGION.fixDr); + goto done; + } else { +nextblock: + if( LMIC.refChnl == (REGION.numChBlocks * 8) ) { + LMIC.refChnl = 0; + failed = 1; + } + } + } + setDrJoin(DRCHG_SET, REGION.joinDr); + LMIC.txChnl = LMIC.fix.hoplist[LMIC.refChnl]; +done: + LMIC.opmode &= ~OP_NEXTCHNL; + delay = rndDelay(32); +#endif + } else { +#ifdef REG_DYN + // use next channel // XXX - join should use nextTx! + if( ++LMIC.txChnl == MIN_DYN_CHNLS ) { + LMIC.txChnl = 0; + } + // Clear NEXTCHNL because join state engine controls channel hopping + LMIC.opmode &= ~OP_NEXTCHNL; + // lower DR every 2nd try + if( (++LMIC.txCnt & 1) == 0 ) { + if( LMIC.datarate == 0 ) { + failed = 1; // we have tried all DR - signal EV_JOIN_FAILED + } + else { + setDrJoin(DRCHG_NOJACC, lowerDR(LMIC.datarate, 1)); + } + } + delay = rndDelay(255 >> LMIC.datarate); +#endif + } + if (failed) + debug_verbose_printf("Join failed\r\n"); + else + debug_verbose_printf("Scheduling next join at %t\r\n", os_getTime() + delay); + + // 1 - triggers EV_JOIN_FAILED event + return (delay & ~1) | failed; +} + + +// ================================================================================ +// BEG FIXED CHANNEL PLAN REGIONS + +#ifdef REG_FIX + +// get next pseudo-random byte +u8_t prng_next (u8_t* prngbuf) { + u8_t i = prngbuf[0]; + ASSERT(i != 0); + if( i == 16 ) { + lce_encKey0(prngbuf); + i = 0; + } + u8_t v = prngbuf[i++]; + prngbuf[0] = i; + return v; +} + +// generate pseudo-random permutation from start..end-1 +static void perm (unsigned char* p, int start, int end, u8_t* prng) { + for (int i = 0; i < (end - start); i++) { + uint32_t j = (prng_next(prng) << 8 ) | prng_next(prng); + j %= (i + 1); // has small bias + p[i] = p[j]; + p[j] = i + start; + } +} + +// generate a pseudo-random hoplist +static void generateHopList (u8_t* hoplist, int nch) { + int nb = nch >> 3; // number of 8-ch blocks + + u8_t prng[16]; // prng state + memcpy(prng, "\x10hoplist", 8); + os_getDevEui(prng + 8); + + u8_t bp[nb]; // block permutation + bp[0] = 0; // always start with block 0 + perm(bp + 1, 1, nb, prng); + + for (int b = 0; b < nb; b++) { + unsigned char cp[8]; + perm(cp, 0, 8, prng); // channel permutation + for (int c = 0; c < 8; c++) { + hoplist[c*nb+b] = bp[b]*8+cp[c]; + } + } +} + +static int numChannels (void) { + // CN470 baseFreqFix==0 + // US915/AU915 baseFreqFix!=0 + return (REGION.baseFreqFix ? 9 : 8) * REGION.numChBlocks; +} + +// return: 1 - some channels were disabled, 0 - all channels were already enabled +static u8_t enableAllChannels_fix (void) { + int nch = numChannels(); + u8_t rv = 0; + for (u8_t i = 0; i < (nch >> 4); i++) { + if (LMIC.fix.channelMap[i] != 0xffff) { + LMIC.fix.channelMap[i] = 0xffff; + rv = 1; + } + } + if (nch & 0xf) { + u8_t newval = (1 << (nch & 0xf)) - 1; + if (LMIC.fix.channelMap[nch >> 4] != newval) { + LMIC.fix.channelMap[nch >> 4] = newval; + rv = 1; + } + } + return rv; +} + +static void disableChannel_fix (u8_t chidx) { + if (chidx < numChannels()) { + LMIC.fix.channelMap[chidx >> 4] &= ~(1 << (chidx & 0xF)); + } + // safety net - all channels disabled -> turn all on -- XXX maybe we shouldn't? + for (u8_t i = 0; i < sizeof(LMIC.fix.channelMap) / sizeof(u16_t); i++) { + if (LMIC.fix.channelMap[i]) { + return; + } + } + enableAllChannels_fix(); +} + +static void initDefaultChannels_fix (void) { + generateHopList(LMIC.fix.hoplist, REGION.numChBlocks * 8); + +#if 0 + for( int i = 0; i < 64; i++ ) { + debug_printf("%2d ", LMIC.fix.hoplist[i]); + if( (i & 7) == 7 ) { + debug_printf("\r\n"); + } + } +#endif + + os_clearMem(LMIC.fix.channelMap, sizeof(LMIC.fix.channelMap)); + enableAllChannels_fix(); +} + +static void prepareDn_fix () { + LMIC.freq = REGION.baseFreqDn + + (LMIC.txChnl % (REGION.numChDnBlocks*8)) + * (REGION.baseFreqFix ? DNCHSPACING_500kHz : DNCHSPACING_125kHz); +} + +static int activeFhssChannelCount_fix (u16_t* channelMap) { + int i, cc = 0; + for( i = 0; i < REGION.numChBlocks; i+=2 ) { + u16_t map = channelMap[i/2]; + if( i+1 == REGION.numChBlocks ) { + map &= 0x00ff; // numChBlocks is odd - avoid reading special channels (e.g. 500kHz) + } + cc += __builtin_popcount(map); + } + return cc; +} + +static u8_t checkChannelMap_fix (u16_t* map) { +#if CFG_us915 + // US915 FHSS requires at least 2 FHSS channels + if( isREGION(US915) ) { + if( activeFhssChannelCount_fix(map) == 1 ) { + return 0; + } + } +#endif + u16_t anyon = 0; + for( u8_t i=0; i> 1); u++) { + dest[u] = en125; + } + dest[REGION.numChBlocks >> 1] = chmap; + } else if( chpage == MCMD_LADR_CHP_BLK8 ) { + dest[REGION.numChBlocks >> 1] = chmap & 0xFF; + for (u8_t u = 0; u < (REGION.numChBlocks >> 1); u++) { + dest[u] = ((chmap & 1) ? 0x00ff : 0) | ((chmap & 2) ? 0xff00 : 0); + chmap >>= 2; + } + } else { + int nch = numChannels(); + chpage >>= MCMD_LADR_CHPAGE_SHIFT; + if (chpage >= ((nch+15) >> 4)) { + return 0; + } + if ((nch & 15) && chpage == (REGION.numChBlocks >> 1)) { // partial map in last 16bit word + chmap &= ~(0xffff << (nch & 15)); + } + dest[chpage] = chmap; + } + return 1; +} + +static void updateTx_fix (ostime_t txbeg) { //XXX:BUG: this is US915/AU915 centric - won't work for CN470 + ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen); + u8_t chnl = LMIC.txChnl; + LMIC.txpow = LMIC.txPowAdj + REGION.maxEirp; + if( chnl < REGION.numChBlocks*8 ) { + LMIC.freq = REGION.baseFreq125 + chnl*UPCHSPACING_125kHz; +#if CFG_cn470 + if( isREGION(CN470) ) + goto updateGDC; +#endif + +#if CFG_us915 + if( isREGION(US915) ) { + // US915 FHSS: max 1 transmission every 400 ms + setAvail(&LMIC.globalAvail, os_time2XTime(txbeg + ms2osticks(400), os_getXTime())); + + // US915 hybrid mode (i.e. less than 50 channels): limit TX power to 21dBm + if( LMIC.txpow > 21 && activeFhssChannelCount_fix(LMIC.fix.channelMap) < 50 ) { + LMIC.txpow = 21; + } + } +#endif + } else { + LMIC.freq = REGION.baseFreqFix + (chnl-REGION.numChBlocks*8)*UPCHSPACING_500kHz; +#if CFG_us915 + if( isREGION(US915) ) { + // US915 DTS mode: limit TX power to 26dBm + if( LMIC.txpow > 26 ) { + LMIC.txpow = 26; + } + } +#endif + } + +#if CFG_cn470 + updateGDC: +#endif + // Update global duty cycle stats + if( LMIC.globalDutyRate != 0 ) { + LMIC.globalDutyAvail = txbeg + (airtime<>1] >> ((beg8 & 1)<<3)) & 0xff ) { + return true; + } + } + return false; +} + +static void syncDatarate_fix () { + if( !checkChannel_fix(LMIC.fix.channelMap, LMIC.datarate) ) { + setDrTxpow(DRCHG_SET, + LMIC.datarate == REGION.fixDr ? fastest125() : REGION.fixDr, + KEEP_TXPOWADJ); + } +} + +#if !defined(DISABLE_CLASSB) +static void setBcnRxParams_fix (void) { + LMIC.dataLen = 0; + LMIC.rps = setIh(setNocrc(dndr2rps(REGION.beaconDr),1),REGION.beaconLen); +#if CFG_cn470 + if( isREGION(CN470) ) { + LMIC.freq = (LMIC.bcnFreq ? LMIC.bcnFreq : 508300000 + LMIC.bcnChnl * DNCHSPACING_125kHz); + return; + } +#endif + // US915/AU915 + LMIC.freq = (LMIC.bcnFreq ? LMIC.bcnFreq : REGION.baseFreqDn + LMIC.bcnChnl * DNCHSPACING_500kHz); +} +#endif + +static ostime_t nextTx_fix (ostime_t now) { + if( LMIC.opmode & OP_NEXTCHNL ) { + if( LMIC.datarate == REGION.fixDr ) { + u8_t off = REGION.numChBlocks*8; + for( u8_t i=0, e=REGION.numChBlocks; i> 4)] & (1<<(chnl & 0xF))) != 0 ) { + LMIC.txChnl = chnl; + break; + } + } + } else { + // 125kHz + for( u8_t i=0, e=REGION.numChBlocks*8; i> 4)] & (1<<(chnl & 0xF))) != 0 ) { + LMIC.txChnl = chnl; + break; + } + } + } + } + LMIC.opmode &= ~OP_NEXTCHNL; // channel decision is stable + osxtime_t xnow = os_time2XTime(now, os_getXTime()); + osxtime_t avail = getAvail(LMIC.globalAvail); + return (ostime_t) ((xnow >= avail) ? xnow : avail); +} + +#endif // REG_FIX + + +static void runEngineUpdate (osjob_t* osjob) { + (void)osjob; // unused + engineUpdate(); +} + +static void opmodePoll (void) { + if ((LMIC.opmode & OP_POLL) == 0) { + LMIC.opmode |= OP_POLL; + LMIC.polltime = nextTx(os_getTime()); + os_setApproxTimedCallback(&LMIC.polljob, LMIC.polltime + LMIC.polltimeout, runEngineUpdate); + } +} + +static void reportEvent (ev_t ev) { + TRACE_EV(ev); + ON_LMIC_EVENT(ev); + engineUpdate(); +} + + +static void runReset (osjob_t* osjob) { + (void)osjob; // unused + // Disable session + LMIC_reset(); + LMIC_startJoining(); + reportEvent(EV_RESET); +} + +static void stateJustJoined (void) { + LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI); + LMIC.opmode |= OP_NEXTCHNL; + LMIC.txCnt = 0; + LMIC.seqnoDn = LMIC.seqnoUp = 0; +#if defined(CFG_lorawan11) + LMIC.seqnoADn = 0; +#endif + LMIC.rejoinCnt = 0; + LMIC.foptsUpLen = 0; + LMIC.dnConf = LMIC.devsAns = 0; + LMIC.dnfqAns = LMIC.dnfqAnsPend = LMIC.dnfqAcks = 0; + LMIC.moreData = LMIC.dn2Ans = LMIC.dn1DlyAns = LMIC.dutyCapAns = 0; + LMIC.dn2Freq = REGION.rx2Freq; + LMIC.gwmargin = 0; + LMIC.gwcnt = 0; +#if !defined(DISABLE_CLASSB) + LMIC.bcnfAns = 0; + LMIC.bcnChnl = 0; + LMIC.bcnFreq = 0; + LMIC.ping.freq = REGION.pingFreq; + LMIC.ping.dr = REGION.pingDr; +#endif +#if defined(CFG_lorawan11) + if( (LMIC.opts &= OPT_LORAWAN11) ) { + LMIC.opts |= OPT_OPTNEG; + } +#endif +} + + +// ================================================================================ +// Decoding frames + + +#if !defined(DISABLE_CLASSB) +// Decode beacon - do not overwrite bcninfo unless we have a match! +static int decodeBeacon (void) { + u8_t* d = LMIC.frame; + if( LMIC.dataLen != REGION.beaconLen || os_rlsbf2(&d[REGION.beaconOffInfo-2]) != os_crc16(d,REGION.beaconOffInfo-2) ) + return 0; // first (common) part fails CRC check + + LMIC.bcninfo.flags &= ~(BCN_PARTIAL|BCN_FULL); + // Match - update bcninfo structure + LMIC.bcninfo.snr = LMIC.snr; + LMIC.bcninfo.rssi = LMIC.rssi; + LMIC.bcninfo.txtime = LMIC.rxtime - REGION.beaconAirtime; + LMIC.bcninfo.time = os_rlsbf4(&d[REGION.beaconOffInfo-2-4]); + LMIC.bcninfo.flags |= BCN_PARTIAL; + + // Check 2nd set + if( os_rlsbf2(&d[LMIC.dataLen-2]) != os_crc16(d,LMIC.dataLen-2) ) + return 1; + // Second set of fields is ok + LMIC.bcninfo.lat = (s32_t)os_rlsbf4(&d[REGION.beaconOffInfo]) >> 8; // read as signed 24-bit + LMIC.bcninfo.lon = (s32_t)os_rlsbf4(&d[REGION.beaconOffInfo+3]) >> 8; // ditto + LMIC.bcninfo.info = d[REGION.beaconOffInfo]; + LMIC.bcninfo.flags |= BCN_FULL; + return 2; +} +#endif + +static int maxDnLen (rps_t rps) { + dr_t dndr = rps2dndr(rps); + if( dndr < 16 && REGION.dr2maxAppPload[dndr] ) { + return 13 + REGION.dr2maxAppPload[dndr]; + } else { + return MAX_LEN_FRAME; + } +} + +static bit_t decodeFrame (void) { + u8_t* d = LMIC.frame; + u8_t hdr = d[0]; + u8_t ftype = hdr & HDR_FTYPE; + int dlen = LMIC.dataLen; + const char *window = (LMIC.txrxFlags & TXRX_DNW1) ? "RX1" : ((LMIC.txrxFlags & TXRX_DNW2) ? "RX2" : "Other"); + if( dlen < OFF_DAT_OPTS+4 || + dlen > maxDnLen(LMIC.rps) || + (hdr & HDR_MAJOR) != HDR_MAJOR_V1 || + (ftype != HDR_FTYPE_DADN && ftype != HDR_FTYPE_DCDN) ) { + // Basic sanity checks failed + norx: + debug_printf("Invalid downlink[window=%s]\r\n", window); + LMIC.dataLen = 0; + return 0; + } + // Validate exact frame length + // Note: device address was already read+evaluated in order to arrive here. + int fct = d[OFF_DAT_FCT]; + u32_t addr = os_rlsbf4(&d[OFF_DAT_ADDR]); + u32_t seqno = os_rlsbf2(&d[OFF_DAT_SEQNO]); + int olen = fct & FCT_OPTLEN; + int ackup = (fct & FCT_ACK) != 0 ? 1 : 0; // ACK last up frame + int poff = OFF_DAT_OPTS+olen; + int pend = dlen-4; // MIC + + if( addr != LMIC.devaddr ) { + goto norx; + } + if( poff > pend ) { + goto norx; + } + + int port = -1; + int replayConf = 0; + + if( pend > poff ) + port = d[poff++]; + + if( port == 0 && olen > 0 ) + goto norx; + + u32_t* pseqnoDn; +#if defined(CFG_lorawan11) + pseqnoDn = (port > 0 && (LMIC.opts & OPT_LORAWAN11)) + ? &LMIC.seqnoADn : &LMIC.seqnoDn; +#else + pseqnoDn = &LMIC.seqnoDn; +#endif + seqno = *pseqnoDn + (s16_t)(seqno - *pseqnoDn); + + if( !lce_verifyMic(LCE_NWKSKEY, LMIC.devaddr, seqno, d, pend) ) { + goto norx; + } + if( seqno < *pseqnoDn ) { + if( (s32_t)seqno > (s32_t)*pseqnoDn ) { + goto norx; + } + if( seqno != *pseqnoDn-1 || ftype != HDR_FTYPE_DCDN ) { + goto norx; + } + // Replay of previous sequence number allowed only if + // previous frame and repeated both requested confirmation + replayConf = 1; + } + else { + *pseqnoDn = seqno+1; // next number to be expected + } + // DN frame requested confirmation - provide ACK once with next UP frame + LMIC.dnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0); + + if( LMIC.dnConf || (fct & FCT_MORE) ) + opmodePoll(); + + // We heard from network + LMIC.rejoinCnt = 0; + if( LMIC.adrAckReq != LINK_CHECK_OFF ) + LMIC.adrAckReq = LINK_CHECK_INIT; + + // Process OPTS + u8_t* opts = &d[OFF_DAT_OPTS]; + int oidx = 0; + + if( !replayConf ) { + // Handle payload only if not a replay + // Decrypt payload - if any + if( port >= 0 && pend-poff > 0 ) + lce_cipher(port <= 0 ? LCE_NWKSKEY : LCE_APPSKEY, + LMIC.devaddr, seqno, /*dn*/1, d+poff, pend-poff); + } else { + // treat replayed frame as empty + pend = poff = OFF_DAT_OPTS; + port = -1; + } + + if( port == 0 && pend-poff > 0 ) { + // We process both FOpts and FRMPayload - FPort=0 inbetween is ignored + olen = pend-OFF_DAT_OPTS; + } + + if( (LMIC.dn2Ans || LMIC.dn1DlyAns || LMIC.dnfqAns || LMIC.dnfqAnsPend ) + && !(LMIC.txrxFlags & TXRX_PING) && !((LMIC.clmode & CLASS_C) && (LMIC.txrxFlags & TXRX_DNW2)) ) { + // Ack of RXParamSetup is very delicate since it might lead to loosing shared state between NWKS/device. + // The server proves it has seen the RXParamSetupAns from the device by responding in a RX window anchored + // to the frame that carried the RXParamSetupAns! This means RX from ping slots **and** any class C using RX2 + // is not prove of reception! Thus, keep sending the RXParamSetupAns. + // Ditto: RXTimingSetup + LMIC.dn2Ans = LMIC.dn1DlyAns = LMIC.dnfqAns = LMIC.dnfqAnsPend = LMIC.dnfqAcks = 0; + } + LMIC.foptsUpLen = 0; + while( oidx < olen ) { + switch( opts[oidx] ) { + case 0: // FPort=0 if we had MAC commands in payload + oidx += 1; + continue; + + case MCMD_LCHK_ANS: { + LMIC.gwmargin = opts[oidx+1]; + LMIC.gwcnt = opts[oidx+2]; + oidx += 3; + continue; + } + case MCMD_LADR_REQ: { + u8_t p1, chpage, nbtrans, cnt = 0; + u16_t chmap; +#ifdef REG_FIX + u16_t dmap[CHMAP_SZ]; +#else + u16_t dmap[1]; +#endif + if (REG_IS_FIX()) { +#ifdef REG_FIX + os_copyMem(dmap, LMIC.fix.channelMap, sizeof(LMIC.fix.channelMap)); +#endif + } + u8_t ans = MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK; + do { + p1 = opts[oidx+1]; // txpow + DR + chmap = os_rlsbf2(&opts[oidx+2]); // list of enabled channels + chpage = opts[oidx+4] & MCMD_LADR_CHPAGE_MASK; // channel page + nbtrans = opts[oidx+4] & MCMD_LADR_REPEAT_MASK; // up repeat count + oidx += 5; + cnt += 1; + if( !applyChannelMap(chpage, chmap, dmap) ) { + ans &= ~MCMD_LADR_ANS_CHACK; + } + } while (oidx < olen && opts[oidx] == MCMD_LADR_REQ); + + if( (ans & MCMD_LADR_ANS_CHACK) && !checkChannelMap(dmap) ) { + ans &= ~MCMD_LADR_ANS_CHACK; + } + if( nbtrans == 0 ) { + nbtrans = LMIC.nbTrans; // keep unchanged + } + dr_t dr = (dr_t)(((p1 & MCMD_LADR_DR_MASK) >> MCMD_LADR_DR_SHIFT)); + s8_t powadj = (s8_t)((p1 & MCMD_LADR_POW_MASK) >> MCMD_LADR_POW_SHIFT); +#if 0 + debug_printf("ADR: p1=%02x,dr=%d,powadj=%d,chmap=%04x,chpage=%d,nbtrans=%d\r\n", + p1, dr, powadj, chmap, chpage, nbtrans); +#endif + if( LMIC.datarate == CUSTOM_DR ) { + // Reject modifying a custom DR. The network probably + // does not really know how to handle this, but at least + // they know we did not change the DR. + ans &= ~MCMD_LADR_ANS_DRACK; + } else if( dr == 15 ) { + dr = LMIC.datarate; // Do not change DR + } else if( !validDR(dr) || ( REG_IS_FIX() +#ifdef REG_FIX + && !checkChannel_fix(dmap, dr) +#endif + )) { + ans &= ~MCMD_LADR_ANS_DRACK; + } + if( ans == (MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK) ) { + // Nothing went wrong - use settings + if (REG_IS_FIX()) { +#ifdef REG_FIX + os_copyMem(LMIC.fix.channelMap, dmap, sizeof(LMIC.fix.channelMap)); +#endif + } else { +#ifdef REG_DYN + LMIC.dyn.channelMap = *dmap; +#endif + } + LMIC.nbTrans = nbtrans; + // XXX: so far all regions define a power reduction table in the form: + // XXX: MaxEIRP - 2*powadj dB with 0 <= powadj <= REGMAX + // XXX: where REGMAX is a region specific maximum. + // XXX: We currently do not check if the max value is exceeded (a field in LMIC.region->maxPowAdjIdx?) + setDrTxpow(DRCHG_NWKCMD, dr, powadj==15 ? KEEP_TXPOWADJ : -2*powadj); + reportEvent(EV_DATARATE); + } + while( cnt-- > 0 ) { + LMIC.foptsUp[LMIC.foptsUpLen++] = MCMD_LADR_ANS; + LMIC.foptsUp[LMIC.foptsUpLen++] = ans; + } + syncDatarate(); // fix DR if no more channel allowing this datarate + LMIC.opmode |= OP_NEXTCHNL; // DR might have changed, channel might no longer be available + continue; + } + case MCMD_DEVS_REQ: { + LMIC.margin = (LMIC.snr >> 2) & 0x3f; + LMIC.devsAns = 1; + oidx += 1; + opmodePoll(); + continue; + } + case MCMD_DN2P_SET: { + u8_t dr = opts[oidx+1] & 0xF; + u8_t off = (opts[oidx+1] >> 4) & 7; + freq_t freq = rdFreq(&opts[oidx+2]); + oidx += 5; + u8_t ans = 0; + if( validDR(dr) && LMIC.dn2Dr != CUSTOM_DR) //XXX:BUG validDNDR + ans |= MCMD_DN2P_ANS_DRACK; + if( freq > 0 ) + ans |= MCMD_DN2P_ANS_CHACK; + if( REGION.rx1DrOff[off] != ILLEGAL_RX1DRoff ) + ans |= MCMD_DN2P_ANS_OFFACK; + if( ans == (MCMD_DN2P_ANS_OFFACK|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK) ) { + LMIC.dn1DrOffIdx = off; + LMIC.dn2Dr = dr; + LMIC.dn2Freq = freq; + } + u8_t i = LMIC.foptsUpLen; + LMIC.foptsUpLen = i+2; + LMIC.foptsUp[i+0] = MCMD_DN2P_ANS; + LMIC.foptsUp[i+1] = ans; + LMIC.dn2Ans = MCMD_DN2P_ANS_REPLY | ans; // answer pending + opmodePoll(); + continue; + } + case MCMD_DCAP_REQ: { + u8_t cap = opts[oidx+1]; + oidx += 2; + LMIC.globalDutyRate = cap & 0xF; + LMIC.globalDutyAvail = os_getTime(); + LMIC.dutyCapAns = 1; + continue; + } + case MCMD_SNCH_REQ: { + u8_t ans = MCMD_SNCH_ANS_PEND; +#ifdef REG_DYN + if (!REG_IS_FIX()) { // only available in dynamic regions + u8_t chidx = opts[oidx+1]; + freq_t freq = rdFreq(&opts[oidx+2]); + if( chidx < MIN_DYN_CHNLS && REGION.defaultCh[chidx] ) { + // don't allow modification of default channels + } else if( freq == 0 && chidx < MAX_DYN_CHNLS ) { + disableChannel_dyn(chidx); + ans = MCMD_SNCH_ANS_PEND|MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK; + } else { + u8_t mindr = opts[oidx+5] & 0xF; + u8_t maxdr = opts[oidx+5] >> 4; + + if( validDR(mindr) && validDR(maxdr) && mindr <= maxdr && //XXX:BUG use a validUPDR? + (chidx >= MIN_DYN_CHNLS || (mindr == 0 && maxdr >= fastest125())) ) // XXX: correct? + ans |= MCMD_SNCH_ANS_DRACK; + if( chidx <= MAX_DYN_CHNLS && freq >= 0 ) + ans |= MCMD_SNCH_ANS_FQACK; + if( ans == (MCMD_SNCH_ANS_PEND|MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK) ) + setupChannel_dyn(chidx, freq, DR_RANGE_MAP(mindr,maxdr)); + } + } +#endif + oidx += 6; + opmodePoll(); + u8_t i = LMIC.foptsUpLen; + LMIC.foptsUpLen = i+2; + LMIC.foptsUp[i+0] = MCMD_SNCH_ANS; + LMIC.foptsUp[i+1] = ans & ~MCMD_SNCH_ANS_RFU; + continue; + } + case MCMD_DNFQ_REQ: { +#ifdef REG_DYN + if (!REG_IS_FIX()) { // only available in dynamic regions + u8_t ans = MCMD_DNFQ_ANS_PEND; + u8_t chidx = opts[oidx+1]; + freq_t freq = rdFreq(&opts[oidx+2]); + if( chidx <= MAX_DYN_CHNLS && LMIC.dyn.chUpFreq[chidx] != 0 ) + ans |= MCMD_DNFQ_ANS_CHACK; + if( freq > 0 ) + ans |= MCMD_DNFQ_ANS_FQACK; + if( ans == (MCMD_DNFQ_ANS_PEND|MCMD_DNFQ_ANS_CHACK|MCMD_DNFQ_ANS_FQACK) ) + LMIC.dyn.chDnFreq[chidx] = freq; + if( LMIC.dnfqAns + LMIC.dnfqAnsPend < 16 ) + LMIC.dnfqAcks |= ans << (2*(LMIC.dnfqAns + LMIC.dnfqAnsPend)); + LMIC.dnfqAns += 1; + u8_t i = LMIC.foptsUpLen; + LMIC.foptsUpLen = i+2; + LMIC.foptsUp[i+0] = MCMD_DNFQ_ANS; + LMIC.foptsUp[i+1] = ans & ~MCMD_DNFQ_ANS_RFU; + } +#endif + oidx += 5; + opmodePoll(); + continue; + } + case MCMD_RXTM_REQ: { + LMIC.dn1Dly = opts[oidx+1] & 0xF; + if( LMIC.dn1Dly == 0 ) + LMIC.dn1Dly = 1; + LMIC.dn1DlyAns = 0x80; + opmodePoll(); + oidx += 2; + continue; + } +#if !defined(DISABLE_CLASSB) + case MCMD_PITV_ANS: { + if( (LMIC.ping.intvExp & 0x80) ) { + LMIC.ping.intvExp &= 0x7F; // clear pending bit + LMIC.opmode |= OP_PINGABLE; + } // else: ignore if we weren't waiting for it + oidx += 1; + continue; + } + case MCMD_PNGC_REQ: { + freq_t freq = rdFreq(&opts[oidx+1]); + u8_t dr = opts[oidx+4] & 0xF; + oidx += 5; + u8_t ans = 0; + if( validDR(dr) ) //XXX:BUG: validDNDR + ans |= MCMD_PNGC_ANS_DRACK; + if( freq >= 0) + ans |= MCMD_PNGC_ANS_FQACK; + if( ans == (MCMD_PNGC_ANS_FQACK|MCMD_PNGC_ANS_DRACK) ) { + LMIC.ping.freq = freq ?: REGION.pingFreq; + LMIC.ping.dr = dr; + } + u8_t i = LMIC.foptsUpLen; + LMIC.foptsUpLen = i+2; + LMIC.foptsUp[i+0] = MCMD_PNGC_ANS; + LMIC.foptsUp[i+1] = ans; + continue; + } + case MCMD_TIME_ANS: { + u32_t secs = os_rlsbf4(&opts[oidx+1]); + u8_t frac = opts[oidx+5]; + osxtime_t ref = os_time2XTime(LMIC.txend, os_getXTime()); + LMIC.gpsEpochOff = secs * OSTICKS_PER_SEC + (((frac * OSTICKS_PER_SEC) >> 8) + 128) - ref; + LMIC.askForTime = 0; // stop asking for time + oidx += 6; + // Currently, we only ask for time when we want to track a beacon + // If there are other reasons for getting MCMD_TIME_ANS we have to discern them here + // Set up tracking of next beacon based on the obtained time: + // Accuracy error: 1/512 sec = ~2ms - spec promises +/-100ms + LMIC.bcninfo.txtime = LMIC.txend - (secs & 0x7F) * OSTICKS_PER_SEC - (((frac * OSTICKS_PER_SEC) + 128) >> 8); + LMIC.bcninfo.flags = 0; // no previous beacon as reference (BCN_PARTIAL|BCN_FULL cleared) + calcBcnRxWindowFromMillis(100,1); + LMIC.bcnChnl = (1+(secs >> 7)) % numBcnChannels(); + LMIC.opmode = (LMIC.opmode & ~OP_SCAN) | OP_TRACK; + continue; + } + case MCMD_BCNI_ANS: { + // Ignore if tracking already enabled + if( (LMIC.opmode & OP_TRACK) == 0 ) { + LMIC.bcnChnl = opts[oidx+3]; + // Disable tracking + LMIC.opmode |= OP_TRACK; + // Cleared later in txComplete handling - triggers EV_BEACON_FOUND + ASSERT(LMIC.askForTime!=0); + // Setup RX parameters + LMIC.bcninfo.txtime = (LMIC.rxtime + + ms2osticks(os_rlsbf2(&opts[oidx+1]) * MCMD_BCNI_TUNIT) + + ms2osticksCeil(MCMD_BCNI_TUNIT/2) + - BCN_INTV_osticks); + LMIC.bcninfo.flags = 0; // txtime above cannot be used as reference (BCN_PARTIAL|BCN_FULL cleared) + calcBcnRxWindowFromMillis(MCMD_BCNI_TUNIT,1); // error of +/-N ms + } + oidx += 4; + continue; + } + case MCMD_BCNF_REQ: { + freq_t freq = rdFreq(&opts[oidx+1]); + u8_t ans = MCMD_BCNF_ANS_PEND; + if( freq >= 0 ) + ans |= MCMD_BCNF_ANS_FQACK; + LMIC.bcnfAns = ans; + if( ans == (MCMD_BCNF_ANS_PEND | MCMD_BCNF_ANS_FQACK) ) + LMIC.bcnFreq = freq; + oidx += 4; + continue; + } +#endif +#if defined(CFG_lorawan11) + case MCMD_ADRP_REQ: { + LMIC_setLinkCheck(1 << (opts[oidx+1] >> 4), 1 << (opts[oidx+1] & 0xf)); + LMIC.foptsUp[LMIC.foptsUpLen++] = MCMD_ADRP_ANS; + oidx += 2; + continue; + } + case MCMD_RKEY_CNF: { + // Ignore if we did not ask for options negotiation + if( LMIC.opts & OPT_OPTNEG ) { + LMIC.opts = OPT_LORAWAN11; + } + oidx += 2 + (opts[oidx+1] >> 4); + continue; + } + case MCMD_DEVMD_CONF: { + if( (LMIC.clmode & PEND_CLASS_C) && opts[oidx+1] == ((LMIC.clmode & CLASS_C) ? 0 : 2) ) { + decPollcnt(); + LMIC.clmode &= ~PEND_CLASS_C; + LMIC.clmode ^= CLASS_C; + } // else: unexpected confirm or unexpected class -- ignore + oidx += 2; + continue; + } +#endif + } + break; + } + +#if defined(CFG_lorawan11) + if( LMIC.opts & OPT_OPTNEG ) { + // Don't keep asking for options negotiation if the LNS does not want to answer us + LMIC.opts &= ~OPT_OPTNEG; + } +#endif + + if( // NWK acks but we don't have a frame pending + (ackup && LMIC.pendTxConf == 0) || + // We sent up confirmed and we got a response in DNW1/DNW2 + // BUT it did not carry an ACK - this should never happen + // Do not resend and assume frame was not ACKed. + (!ackup && LMIC.pendTxConf != 0) ) { + } + + if( LMIC.pendTxConf != 0 ) { // we requested an ACK + LMIC.txrxFlags |= ackup ? TXRX_ACK : TXRX_NACK; + LMIC.pendTxConf = 0; + } + + if( port < 0 ) { + LMIC.txrxFlags |= TXRX_NOPORT; + LMIC.dataBeg = poff; + LMIC.dataLen = 0; + } else { + LMIC.txrxFlags |= TXRX_PORT; + LMIC.dataBeg = poff; + LMIC.dataLen = pend-poff; + } + debug_printf("Received downlink[window=%s,port=%d,ack=%d]\r\n", window, port, ackup); + return 1; +} + +static bit_t decodeMultiCastFrame (void) { + u8_t* d = LMIC.frame; + u8_t hdr = d[0]; + u8_t ftype = hdr & HDR_FTYPE; + int dlen = LMIC.dataLen; + if( dlen < OFF_DAT_OPTS+4 || + (hdr & HDR_MAJOR) != HDR_MAJOR_V1 || + ftype != HDR_FTYPE_DADN ) { + // Basic sanity checks failed + norx: + LMIC.dataLen = 0; + return 0; + } + // Validate exact frame length + int fct = d[OFF_DAT_FCT]; + u32_t addr = os_rlsbf4(&d[OFF_DAT_ADDR]); + u32_t seqno = os_rlsbf2(&d[OFF_DAT_SEQNO]); + int poff = OFF_DAT_OPTS; + int pend = dlen-4; // MIC + + // check for multicast session with this address + session_t* s; + for(s = LMIC.sessions; sgrpaddr!=addr; s++); + if( s == LMIC.sessions+MAX_MULTICAST_SESSIONS ) { + goto norx; + } + // check for short frame + if( poff > pend ) { + goto norx; + } + // check for port + int port = -1; + if( pend > poff ) { + port = d[poff++]; + } + if( port == 0 ) { + goto norx; + } + + // check for bad flags or options (only FPending allowed, no options) + if( fct & ~FCT_MORE ) { + goto norx; + } + + seqno = s->seqnoADn + (u16_t)(seqno - s->seqnoADn); + + // verify MIC + if( !lce_verifyMic(LCE_MCGRP_0 + (s-LMIC.sessions), s->grpaddr, seqno, d, pend) ) { + goto norx; + } + // check down frame counter + if( seqno < s->seqnoADn ) { + goto norx; + } + s->seqnoADn = seqno+1; // next number to be expected + + // We heard from network + LMIC.rejoinCnt = 0; + if( LMIC.adrAckReq != LINK_CHECK_OFF ) + LMIC.adrAckReq = LINK_CHECK_INIT; + + // Decrypt payload - if any + if( pend-poff > 0 ) { + lce_cipher(LCE_MCGRP_0 + (s-LMIC.sessions), s->grpaddr, seqno, /*dn*/1, d+poff, pend-poff); + } + + if( port < 0 ) { + LMIC.txrxFlags |= TXRX_NOPORT; + LMIC.dataBeg = poff; + LMIC.dataLen = 0; + } else { + LMIC.txrxFlags |= TXRX_PORT; + LMIC.dataBeg = poff; + LMIC.dataLen = pend-poff; + } + return 1; +} + + +// ================================================================================ +// TX/RX transaction support + + +static void setupRx2 (void) { + LMIC.txrxFlags = (LMIC.txrxFlags & TXRX_NOTX) | TXRX_DNW2; + LMIC.rps = dndr2rps(LMIC.dn2Dr); + LMIC.freq = LMIC.dn2Freq; + LMIC.dataLen = 0; + os_radio(RADIO_RX); +} + + +static void schedRx2 (u8_t delay, osjobcb_t func) { +#if defined(CFG_eu868) && !defined(CFG_kr920) + if( isFsk(dndr2rps(LMIC.dn2Dr)) ) { + LMIC.rxtime = LMIC.txend + delay*sec2osticks(1) - PRERX_FSK*us2osticksRound(160); // (8bit/50kbps=160us) + LMIC.rxsyms = RXLEN_FSK; + } + else +#endif + { + // Add 1.5 symbols we need 5 out of 8. Try to sync 1.5 symbols into the preamble. + LMIC.rxtime = LMIC.txend + delay*sec2osticks(1) + dr2hsym(LMIC.dn2Dr, PAMBL_SYMS-MINRX_SYMS); + adjustByRxdErr(delay, LMIC.dn2Dr); + } + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); +} + +static void setupRx1 (osjobcb_t func) { + LMIC.txrxFlags = (LMIC.txrxFlags & TXRX_NOTX) | TXRX_DNW1; + // Turn LMIC.rps from TX over to RX + LMIC.rps = setNocrc(LMIC.rps,1); + LMIC.dataLen = 0; + LMIC.osjob.func = func; + os_radio(RADIO_RX); +} + + +// Called by HAL once TX complete and delivers exact end of TX time stamp in LMIC.rxtime +static void txDone (u8_t delay, osjobcb_t func) { +#if !defined(DISABLE_CLASSB) + if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE|OP_PINGINI)) == (OP_TRACK|OP_PINGABLE) ) { + rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! + LMIC.opmode |= OP_PINGINI; + } +#endif + prepareDn(); + LMIC.rps = dndr2rps(LMIC.dndr); + + if( isFsk(LMIC.rps) ) { + LMIC.rxtime = LMIC.txend + delay*sec2osticks(1) - PRERX_FSK*us2osticksRound(160); // (8bit/50kbps=160us) + LMIC.rxsyms = RXLEN_FSK; + } else { + LMIC.rxtime = LMIC.txend + delay*sec2osticks(1) + dr2hsym(LMIC.dndr, PAMBL_SYMS-MINRX_SYMS); + LMIC.rxsyms = MINRX_SYMS; + adjustByRxdErr(delay, LMIC.dndr); + } + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); +} + + +// ======================================== Join frames + + +static void onJoinFailed (osjob_t* osjob) { + (void)osjob; // unused + // Notify app - must call LMIC_reset() to stop joining + // otherwise join procedure continues. + reportEvent(EV_JOIN_FAILED); +} + + +static bit_t processJoinAccept (void) { + ASSERT(LMIC.txrxFlags != TXRX_DNW1 || LMIC.dataLen != 0); + ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); + + if( LMIC.dataLen == 0 ) { + nojoinframe: + if( (LMIC.opmode & OP_JOINING) == 0 ) { + ASSERT((LMIC.opmode & OP_REJOIN) != 0); + // REJOIN attempt for roaming + LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND); + if( LMIC.rejoinCnt < 10 ) + LMIC.rejoinCnt++; + reportEvent(EV_REJOIN_FAILED); + return 1; + } + LMIC.opmode &= ~OP_TXRXPEND; + ostime_t delay = nextJoinState(); + // update txend + LMIC.txend = os_getTime() + delay; + // Build next JOIN REQUEST with next engineUpdate call + // Optionally, report join failed. + // Both after a random/chosen amount of ticks. + os_setApproxTimedCallback(&LMIC.osjob, LMIC.txend, + ((delay&1) != 0) + ? FUNC_ADDR(onJoinFailed) // one JOIN iteration done and failed + : FUNC_ADDR(runEngineUpdate)); // next step to be delayed + return 1; + } + u8_t hdr = LMIC.frame[0]; + u8_t dlen = LMIC.dataLen; + if( (dlen != LEN_JA && dlen != LEN_JAEXT) + || (hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) { + badframe: + if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) + return 0; + goto nojoinframe; + } + if( !lce_processJoinAccept(LMIC.frame, dlen, LMIC.devNonce) ) { + goto badframe; + } + + LMIC.devaddr = os_rlsbf4(LMIC.frame+OFF_JA_DEVADDR); // (can be zero!) + LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_JA_NETID]) & 0xFFFFFF; + LMIC.dn1Dly = os_minmax(1, LMIC.frame[OFF_JA_RXDLY] & 0xF, 15); + LMIC.dn2Dr = LMIC.frame[OFF_JA_DLSET] & JA_DLS_RX2DR; + LMIC.dn1DrOffIdx = (LMIC.frame[OFF_JA_DLSET] & JA_DLS_RX1DROFF) >> 4; + if( REGION.rx1DrOff[LMIC.dn1DrOffIdx] == ILLEGAL_RX1DRoff ) + goto badframe; +#if defined(CFG_lorawan11) + LMIC.opts = (LMIC.frame[OFF_JA_DLSET] & JA_DLS_OPTNEG) ? OPT_LORAWAN11 : 0; +#endif + + if( dlen > LEN_JA ) { + dlen = OFF_CFLIST; +#ifdef REG_FIX + if (REG_IS_FIX()) { + if (LMIC.frame[OFF_CFLIST + 15] != 1) { // must be CFList type 1 + goto badframe; + } + LMIC.frame[OFF_CFLIST + 15] = 0; // so we can read the last byte with os_rlsbf2() + for (u8_t i=0; i < 8 && i < CHMAP_SZ; i++, dlen += 2) { + LMIC.fix.channelMap[i] = os_rlsbf2(&LMIC.frame[dlen]); + } + } else +#endif // REG_FIX + { +#ifdef REG_DYN + if (LMIC.frame[OFF_CFLIST + 15] != 0) { // must be CFList type 0 + goto badframe; + } + for( u8_t chidx=3; chidx<8; chidx++, dlen+=3 ) { + s32_t freq = rdFreq(&LMIC.frame[dlen]); + if( freq > 0 ) { + setupChannel_dyn(chidx, freq, 0); + debug_printf("Setup channel[idx=%d,freq=%.1F]\r\n", chidx, freq, 6); + } + } +#endif // REG_DYN + } + } + + ASSERT((LMIC.opmode & (OP_JOINING|OP_REJOIN))!=0); + if( (LMIC.opmode & OP_REJOIN) != 0 ) { + // Lower DR every try below current UP DR + LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt); + } + addRxdErr(DELAY_JACC1 + (LMIC.txrxFlags & TXRX_DNW2 ? DELAY_EXTDNW2 : 0)); + stateJustJoined(); + reportEvent(EV_JOINED); + return 1; +} + + +static void processRx2Jacc (osjob_t* osjob) { + (void)osjob; // unused + if( LMIC.dataLen == 0 ) + LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot + processJoinAccept(); +} + + +static void setupRx2Jacc (osjob_t* osjob) { + (void)osjob; // unused + LMIC.osjob.func = FUNC_ADDR(processRx2Jacc); + setupRx2(); +} + + +static void processRx1Jacc (osjob_t* osjob) { + (void)osjob; // unused + if( LMIC.dataLen == 0 || !processJoinAccept() ) + schedRx2(DELAY_JACC2, FUNC_ADDR(setupRx2Jacc)); +} + + +static void setupRx1Jacc (osjob_t* osjob) { + (void)osjob; // unused + setupRx1(FUNC_ADDR(processRx1Jacc)); +} + + +static void jreqDone (osjob_t* osjob) { + (void)osjob; // unused + txDone(DELAY_JACC1, FUNC_ADDR(setupRx1Jacc)); + reportEvent(EV_TXDONE); +} + +// ======================================== Data frames + +// Fwd decl. +static bit_t processDnData(void); + +static void processRx2DnDataDelay (osjob_t* osjob) { + (void)osjob; // unused + processDnData(); +} + +static void processRx2DnData (osjob_t* osjob) { + (void)osjob; // unused + if( LMIC.dataLen == 0 ) { + LMIC.txrxFlags = (LMIC.txrxFlags & TXRX_NOTX) | 0; // nothing in 1st/2nd DN slot + // Delay callback processing to avoid up TX while gateway is txing our missed frame! + // Since DNW2 uses SF12 by default we wait 3 secs. + os_setTimedCallback(&LMIC.osjob, + (os_getTime() + /* XXX DNW2_SAFETY_ZONE */ + rndDelay(2)), + FUNC_ADDR(processRx2DnDataDelay)); + return; + } + processDnData(); +} + + +static void setupRx2DnData (osjob_t* osjob) { + (void)osjob; // unused + LMIC.osjob.func = FUNC_ADDR(processRx2DnData); + setupRx2(); +} + + +static void processRx1DnData (osjob_t* osjob) { + (void)osjob; // unused + if( LMIC.dataLen == 0 || !processDnData() ) { + schedRx2(LMIC.dn1Dly+DELAY_EXTDNW2, FUNC_ADDR(setupRx2DnData)); + } +} + +static void processRx2ClassC (osjob_t* osjob) { + (void)osjob; // unused + if( LMIC.dataLen != 0 ) { + LMIC.txrxFlags = TXRX_DNW2; + if ((LMIC.devaddr == os_rlsbf4(&LMIC.frame[OFF_DAT_ADDR]) && decodeFrame()) || decodeMultiCastFrame() ) { + reportEvent(EV_RXCOMPLETE); + return; + } + } + engineUpdate(); +} + +static void setupRx2ClassC () { + LMIC.osjob.func = FUNC_ADDR(processRx2ClassC); + LMIC.txrxFlags = TXRX_DNW2; + LMIC.rps = dndr2rps(LMIC.dn2Dr); + LMIC.freq = LMIC.dn2Freq; + LMIC.dataLen = 0; + os_radio(RADIO_RXON); +} + +static void processRx1ClassC (osjob_t* osjob) { + (void)osjob; // unused + if( !processDnData() ) { + setupRx2ClassC(); + } +} + +static void setupRx1DnData (osjob_t* osjob) { + (void)osjob; // unused + setupRx1(FUNC_ADDR(processRx1DnData)); +} + +static void setupRx1ClassC (osjob_t* osjob) { + (void)osjob; // unused + setupRx1(FUNC_ADDR(processRx1ClassC)); +} + +static void txError (void) { + LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND); + LMIC.txrxFlags = TXRX_NOTX; + LMIC.dataBeg = 0; + LMIC.dataLen = 0; + LMIC.pendTxNoRx = 0; + reportEvent(EV_TXCOMPLETE); +} + + +static void updataDone (osjob_t* osjob) { + (void)osjob; // unused + // check if rx window is to be scheduled + // (dataLen is reset by radio if tx didn't complete regularly and txend is unknown) + if( LMIC.pendTxNoRx || LMIC.dataLen == 0 ) { + // transaction done + txError(); + } else { + // schedule down window1 reception + txDone(LMIC.dn1Dly, + (LMIC.clmode & CLASS_C) == 0 + ? FUNC_ADDR(setupRx1DnData) + : FUNC_ADDR(setupRx1ClassC)); + } + reportEvent(EV_TXDONE); +} + +// ======================================== + + +static void buildDataFrame (void) { + bit_t txdata = ((LMIC.opmode & (OP_TXDATA|OP_POLL)) != OP_POLL); + u8_t dlen = txdata ? LMIC.pendTxLen : 0; + + // Piggyback MAC options + // Prioritize by importance + int end = OFF_DAT_OPTS; +#if defined(CFG_lorawan11) + if( LMIC.opts & OPT_OPTNEG ) { + LMIC.frame[end+0] = MCMD_RKEY_IND; + LMIC.frame[end+1] = MCMD_RKEY_VERSION_1_1; + end += 2; + } + if( (LMIC.clmode & PEND_CLASS_C) ) { + LMIC.frame[end+0] = MCMD_DEVMD_IND; + LMIC.frame[end+1] = (LMIC.clmode & CLASS_C) ? 0 : 2; + end += 2; + } +#endif + if( LMIC.foptsUpLen ) { + u8_t n = LMIC.foptsUpLen; + os_copyMem(&LMIC.frame[end], LMIC.foptsUp, n); + end += n; + } +#if !defined(DISABLE_CLASSB) + if( LMIC.ping.intvExp & 0x80 ) { + // Announce ping interval - LNS hasn't acked it yet + LMIC.frame[end] = MCMD_PITV_REQ; + LMIC.frame[end+1] = LMIC.ping.intvExp & 0x7; + end += 2; + } +#endif + if( LMIC.dutyCapAns ) { + if( end <= OFF_DAT_OPTS + 15 - 1 ) { + LMIC.frame[end] = MCMD_DCAP_ANS; + end += 1; + } + LMIC.dutyCapAns = 0; + } + if( LMIC.dn2Ans ) { + // Note: this is cleared with reception of a frame in a class A RX1/RX2 window + if( (LMIC.dn2Ans & MCMD_DN2P_ANS_PEND) && end <= OFF_DAT_OPTS + 15 - 2 ) { + LMIC.frame[end+0] = MCMD_DN2P_ANS; + LMIC.frame[end+1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU; + end += 2; + } else { + LMIC.dn2Ans ^= MCMD_DN2P_ANS_REPLY|MCMD_DN2P_ANS_PEND; + } + } + if( LMIC.dn1DlyAns && end <= OFF_DAT_OPTS + 15 - 1 ) { + // Note: this is cleared with reception of a frame in a class A RX1/RX2 window + LMIC.frame[end+0] = MCMD_RXTM_ANS; + end += 1; + } + if( LMIC.dnfqAns || LMIC.dnfqAnsPend ) { + // Note: this is cleared with reception of a frame in a class A RX1/RX2 window + for( u8_t i=0; i < LMIC.dnfqAnsPend && end <= OFF_DAT_OPTS + 15 - 2; i++ ) { + LMIC.frame[end+0] = MCMD_DNFQ_ANS; + LMIC.frame[end+1] = (LMIC.dnfqAcks >> (2*i)) & 3; + end += 2; + } + LMIC.dnfqAnsPend += LMIC.dnfqAns; + LMIC.dnfqAns = 0; + } + if( LMIC.devsAns ) { // answer to device status + if( end <= OFF_DAT_OPTS + 15 - 3 ) { + LMIC.frame[end+0] = MCMD_DEVS_ANS; + LMIC.frame[end+1] = os_getBattLevel(); + LMIC.frame[end+2] = LMIC.margin; + } + LMIC.devsAns = 0; + end += 3; + } +#if !defined(DISABLE_CLASSB) + if( LMIC.askForTime > 0 ) { + LMIC.frame[end] = MCMD_TIME_REQ; + end += 1; + } + if( LMIC.bcnfAns ) { + LMIC.frame[end+0] = MCMD_BCNF_ANS; + LMIC.frame[end+1] = LMIC.bcnfAns; + LMIC.bcnfAns = 0; + end += 2; + } +#endif + if( LMIC.gwmargin == 255 ) { + LMIC.frame[end] = MCMD_LCHK_REQ; + end += 1; + } + + int foptslen = end - OFF_DAT_OPTS; + + if( foptslen && txdata && LMIC.pendTxPort == 0 ) { + debug_printf("Payload and MAC commands on port 0, sending only MAC commands\n"); + // app layer wants to transmit on port 0, but we have fopts + txdata = 0; + LMIC.txrxFlags |= TXRX_NOTX; + } + + int foptslen_max = 15; + if( foptslen > foptslen_max ) { + debug_printf("MAC commands large (%u > %u), sending only MAC commands\n", foptslen, foptslen_max); + // too big for FOpts, send as MAC frame with port=0 (cancels application payload) + memcpy(LMIC.pendTxData, LMIC.frame+OFF_DAT_OPTS, foptslen); + dlen = foptslen; + LMIC.pendTxPort = 0; + LMIC.pendTxConf = 0; + if( txdata ) { + LMIC.txrxFlags |= TXRX_NOTX; + } else { + txdata = 1; + } + end = OFF_DAT_OPTS; + } + + int flen, flen_max = MAX_LEN_FRAME; + if (LMIC.datarate != CUSTOM_DR) + flen_max = LMIC_maxAppPayload() + 13; +again: + flen = end + (txdata ? 5+dlen : 4); + if( flen > flen_max ) { + // frame too large + if( txdata ) { + if( LMIC.pendTxPort ) { + if( foptslen ) { + debug_printf("Frame too large (%u > %u), sending only MAC commands\n", flen, flen_max); + // cancel application payload + txdata = 0; + LMIC.txrxFlags |= TXRX_NOTX; + goto again; + } else { + debug_printf("Frame too large (%u > %u), not sending\n", flen, flen_max); + // cancel transmission completely + LMIC.dataLen = 0; + return; + } + } + } + // still too big -- truncate FOpts + flen = flen_max; + if( LMIC.pendTxPort ) { + end = flen_max - 4; + } + } + LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DAUP | HDR_MAJOR_V1; + LMIC.frame[OFF_DAT_FCT] = (LMIC.dnConf | (LMIC.pendTxNoRx ? 0 : LMIC.adrEnabled + | ((LMIC.adrAckReq >= 0 && ((LMIC.opmode & OP_LINKDEAD) == 0 || !LMIC.adrEnabled)) ? FCT_ADRARQ : 0)) + | ((LMIC.opmode & (OP_TRACK|OP_PINGABLE)) == (OP_TRACK|OP_PINGABLE) ? FCT_CLASSB : 0) + | (end-OFF_DAT_OPTS)); + os_wlsbf4(LMIC.frame+OFF_DAT_ADDR, LMIC.devaddr); + + if( LMIC.txCnt == 0 || (LMIC.opmode & (OP_TXDATA|OP_POLL)) == OP_POLL ) { + LMIC.txCnt = 0; + LMIC.seqnoUp += 1; + } + os_wlsbf2(LMIC.frame+OFF_DAT_SEQNO, LMIC.seqnoUp-1); + + // Clear pending DN confirmation + LMIC.dnConf = 0; + + if( txdata ) { + if( LMIC.pendTxConf ) { + // Confirmed only makes sense if we have a payload (or at least a port) + LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DCUP | HDR_MAJOR_V1; + } + LMIC.frame[end] = LMIC.pendTxPort; + os_copyMem(LMIC.frame+end+1, LMIC.pendTxData, dlen); + lce_cipher(LMIC.pendTxPort==0 ? LCE_NWKSKEY : LCE_APPSKEY, + LMIC.devaddr, LMIC.seqnoUp-1, /*up*/0, LMIC.frame+end+1, dlen); + } + lce_addMic(LCE_NWKSKEY, LMIC.devaddr, LMIC.seqnoUp-1, LMIC.frame, flen-4); + LMIC.dataLen = flen; +} + + +#if !defined(DISABLE_CLASSB) +// Callback from HAL during scan mode or when job timer expires. +static void onBcnScanRx (osjob_t* job) { + // stop radio and its job + os_radio(RADIO_STOP); + if( decodeBeacon() ) { + // Found our 1st beacon + // We don't have a previous beacon to calc some drift - assume some max drift + calcBcnRxWindowFromMillis(BCN_100PPM_ms,1); + LMIC.opmode = (LMIC.opmode & ~OP_SCAN) | OP_TRACK; + reportEvent(EV_BEACON_FOUND); // can be disabled in callback + return; + } + if( (os_getTime() - LMIC.bcninfo.txtime) >= 0 ) { + LMIC.opmode &= ~(OP_SCAN | OP_TRACK); + reportEvent(EV_SCAN_TIMEOUT); + return; + } + engineUpdate(); +} + + +// Enable receiver to listen to incoming beacons +// This mode ends with events: EV_SCAN_TIMEOUT/EV_SCAN_BEACON +// Implicitely cancels any pending TX/RX transaction. +// Also cancels an ongoing joining procedure. +static void startScan (void) { + if( (LMIC.opmode & (OP_TRACK|OP_SHUTDOWN)) != 0 ) + return; // already tracking a beacon or shutting down + // Set scan timeout - one sec longer than beacon period + LMIC.bcninfo.txtime = os_getTime() + sec2osticks(BCN_INTV_sec+1); + LMIC.opmode |= OP_SCAN; + LMIC.askForTime = 0; +} + + +u8_t LMIC_enableTracking (u8_t tryAskForTime) { + if( (LMIC.opmode & (OP_SCAN|OP_SHUTDOWN)) != 0 ) + return 0; // already scanning or shutdown in progress - ignore + if( (LMIC.opmode & OP_TRACK) != 0 ) + return 2; // already tracking a beacon - we're done + // If BCN info requested from NWK then app has to take care + // of sending data up so that MCMD_BCNI_REQ can be attached. + LMIC.missedBcns = 0; + LMIC.askForTime = tryAskForTime; + if( tryAskForTime == 0 ) { + startScan(); + reportEvent(EV_START_SCAN); + return 1; + } + opmodePoll(); + engineUpdate(); + return 1; // enabled +} + + +void LMIC_disableTracking (void) { + LMIC.opmode &= ~(OP_SCAN|OP_TRACK); + LMIC.askForTime = 0; + engineUpdate(); +} + + +// called by radio when data arrived or by timeout job +static void scan_done (osjob_t* job) { + if( LMIC.dataLen ) { // frame received (scan_done scheduled by irqjob after rxdone irq, radio stopped) + if( decodeMultiCastFrame() ) { + // good frame + LMIC.opmode &= ~OP_NOENGINE; + reportEvent(EV_SCAN_FOUND); + } else { + // bad frame + BACKTRACE(); + LMIC.dataLen = 0; + // continue scanning until timeout + os_setTimedCallback(&LMIC.osjob, LMIC.bcninfo.txtime, scan_done); // job/func also used for radio callback! + os_radio(RADIO_RXON); + } + } else { // timeout + // stop radio and its job + os_radio(RADIO_STOP); + LMIC.opmode &= ~OP_NOENGINE; + reportEvent(EV_SCAN_TIMEOUT); + } +} + + +// start scanning for multi-cast data frame until timeout (LMIC.freq and LMIC.dndr must be set) +// will generate EV_SCAN_FOUND or EV_SCAN_TIMEOUT events +int LMIC_scan (ostime_t timeout) { + if( (LMIC.opmode & (OP_SCAN|OP_TRACK|OP_SHUTDOWN)) != 0 ) { + return 0; // already in progress or failed to enable + } + + // cancel onging TX/RX transaction + LMIC.txCnt = LMIC.dnConf = 0; + LMIC.opmode = (LMIC.opmode | OP_NOENGINE) & ~(OP_TXRXPEND); + LMIC.dataLen = 0; + + // start scanning until timeout + LMIC.rps = dndr2rps(LMIC.dndr); + LMIC.bcninfo.txtime = os_getTime() + timeout; // save timeout + os_setTimedCallback(&LMIC.osjob, LMIC.bcninfo.txtime, scan_done); // job/func also used for radio callback! + os_radio(RADIO_RXON); + + return 1; // enabled +} + + +// called by radio when data arrived or on symbol timeout +static void track_done (osjob_t* job) { + LMIC.opmode &= ~OP_NOENGINE; + if( LMIC.dataLen && decodeMultiCastFrame() ) { + reportEvent(EV_BEACON_TRACKED); + } else { + LMIC.dataLen = 0; + reportEvent(EV_BEACON_MISSED); + } +} + + +static void track_start (osjob_t* job) { + LMIC.rxsyms = MINRX_SYMS; + LMIC.osjob.func = track_done; + os_radio(RADIO_RX); +} + + +// try to receive single multi-cast data frame at specified time (LMIC.freq and LMIC.dndr must be set) +// will generate EV_BEACON_TRACKED or EV_BEACON_MISSED events +int LMIC_track (ostime_t when) { + if( (LMIC.opmode & (OP_SCAN|OP_TRACK|OP_SHUTDOWN)) != 0 ) { + return 0; // already in progress or failed to enable + } + + // cancel onging TX/RX transaction + LMIC.txCnt = LMIC.dnConf = 0; + LMIC.opmode = (LMIC.opmode | OP_NOENGINE) & ~(OP_TXRXPEND); + LMIC.dataLen = 0; + + // schedule single rx at given time considering ramp-up time + LMIC.rps = dndr2rps(LMIC.dndr); + LMIC.rxtime = when + dr2hsym(LMIC.dndr, PAMBL_SYMS-MINRX_SYMS); + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, track_start); + return 1; +} +#endif + + +// ================================================================================ +// +// Join stuff +// +// ================================================================================ + +static void buildJoinRequest (u8_t ftype) { + // Do not use pendTxData since we might have a pending + // user level frame in there. Use RX holding area instead. + LMIC.devNonce = hal_dnonce_next(); + u8_t* d = LMIC.frame; + d[OFF_JR_HDR] = ftype; + os_getJoinEui(d + OFF_JR_JOINEUI); + os_getDevEui(d + OFF_JR_DEVEUI); + os_wlsbf2(d + OFF_JR_DEVNONCE, LMIC.devNonce); + lce_addMicJoinReq(d, OFF_JR_MIC); + LMIC.dataLen = LEN_JR; +} + +static void startJoining (osjob_t* osjob) { + (void)osjob; // unused + // reset and calibrate radio + LMIC.freq = (REGION.maxFreq - REGION.minFreq) / 2; + os_radio(RADIO_INIT); + // report event and update engine + reportEvent(EV_JOINING); +} + +// Start join procedure if not already joined. +bit_t LMIC_startJoining (void) { + if( LMIC.netid == NETID_NONE ) { + // There should be no TX/RX going on + ASSERT((LMIC.opmode & (OP_POLL|OP_TXRXPEND)) == 0); + // Lift any previous duty limitation + LMIC.globalDutyRate = 0; + // Cancel scanning + LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL); + // Setup state + LMIC.rejoinCnt = LMIC.txCnt = LMIC.pendTxConf = 0; + initJoinLoop(); + LMIC.opmode |= OP_JOINING; + LMIC.clmode = 0; + LMIC.pollcnt = 0; + // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS + os_setCallback(&LMIC.osjob, FUNC_ADDR(startJoining)); + return 1; + } + return 0; // already joined +} + + +// ================================================================================ +// +// +// +// ================================================================================ + +#if !defined(DISABLE_CLASSB) +static void processPingRx (osjob_t* osjob) { + (void)osjob; // unused + if( LMIC.dataLen != 0 ) { + LMIC.txrxFlags = TXRX_PING; + if( decodeFrame() ) { + reportEvent(EV_RXCOMPLETE); + return; + } + } + // Pick next ping slot + engineUpdate(); +} +#endif + + +static bit_t processDnData (void) { + ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); + + if( LMIC.dataLen == 0 ) { + norx: + // Nothing received - implies no port + LMIC.txrxFlags = (LMIC.txrxFlags & TXRX_NOTX) | TXRX_NOPORT; + if( (LMIC.opmode & OP_TXDATA) ) { + LMIC.txCnt += 1; + if( (int8_t)LMIC.txCnt < (int8_t)LMIC.nbTrans ) { // int8_t => implicit check for IGN_NBTRANS + // Schedule another retransmission + txDelay(LMIC.rxtime, RETRY_PERIOD_secs); + LMIC.opmode = (LMIC.opmode & ~OP_TXRXPEND) | OP_NEXTCHNL; + os_setCallback(&LMIC.osjob, FUNC_ADDR(runEngineUpdate)); + goto txcontinue; + } else { + LMIC.opmode &= ~OP_TXDATA; + LMIC.foptsUpLen = 0; // clear fopts + } + if( LMIC.pendTxConf != 0 ) // we requested an ACK + LMIC.txrxFlags |= TXRX_NACK; + } + if( LMIC.adrAckReq != LINK_CHECK_OFF ) + LMIC.adrAckReq += 1; + LMIC.dataBeg = LMIC.dataLen = 0; + txcomplete: + if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0 && (LMIC.opmode & OP_LINKDEAD) != 0 ) { + LMIC.opmode &= ~OP_LINKDEAD; + reportEvent(EV_LINK_ALIVE); + } + LMIC.nbTrans &= ~IGN_NBTRANS; // auto clear ignore + LMIC.opmode &= ~OP_TXRXPEND; + reportEvent(EV_TXCOMPLETE); + // If we haven't heard from NWK in a while although we asked for a sign + // assume link is dead - notify application and keep going + if( LMIC.adrAckReq >= (s32_t) LINK_CHECK_DEAD ) { + // We haven't heard from NWK for some time although we + // asked for a response for some time - assume we're disconnected. + if( LMIC.adrEnabled ) { // If ADR is enabled, reset to default tx power and lower DR one notch. + if (LMIC.txPowAdj) { + setDrTxpow(DRCHG_NOADRACK, LMIC.datarate, 0); + } + if (lowerDR(LMIC.datarate, 1) != LMIC.datarate) { + setDrTxpow(DRCHG_NOADRACK, lowerDR(LMIC.datarate, 1), KEEP_TXPOWADJ); + } else if (REG_IS_FIX() +#ifdef REG_FIX + && enableAllChannels_fix() +#endif + ) { + // some channels were disabled + // NOTE: we now enter a network/device state mismatch!! + } else { + // nothing we can do anymore + LMIC.opmode |= /* XXX OP_REJOIN| */ OP_LINKDEAD; + } + LMIC.adrAckReq = 0; + } else { // if ADR is not enabled, we can't do anything -- signal to application that link is dead + LMIC.opmode |= OP_LINKDEAD; + } + reportEvent((LMIC.opmode & OP_LINKDEAD) ? EV_LINK_DEAD : EV_ADR_BACKOFF); + } +#if !defined(DISABLE_CLASSB) + // If this falls to zero the NWK did not answer our MCMD_TIME_REQ commands - try full scan + if( LMIC.askForTime > 0 ) { + if( --LMIC.askForTime == 0 ) { + startScan(); // NWK did not answer - try scan + reportEvent(EV_START_SCAN); + } + else { + opmodePoll(); + } + } +#endif + txcontinue: + return 1; + } + //check address and call either unicast or multicast decoding of the LoRaWAN frame + bit_t d = 0; + if (LMIC.devaddr == os_rlsbf4(&LMIC.frame[OFF_DAT_ADDR])) + d = decodeFrame(); + else + d = decodeMultiCastFrame(); // checks group address using the multicast session + if (!d) { + if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) + return 0; + goto norx; + } + addRxdErr(LMIC.dn1Dly + (LMIC.txrxFlags & TXRX_DNW2 ? DELAY_EXTDNW2 : 0)); + if( (LMIC.opmode & OP_TXDATA) ) + LMIC.txCnt += 1; + LMIC.opmode &= ~OP_TXDATA; + LMIC.pendTxConf = 0; + goto txcomplete; +} + + +#if !defined(DISABLE_CLASSB) +static void processBeacon (osjob_t* osjob) { + (void)osjob; // unused + ostime_t lasttx = LMIC.bcninfo.txtime; // save previous - decodeBeacon overwrites + u8_t flags = LMIC.bcninfo.flags; + ev_t ev; + + if( decodeBeacon() >= 1 ) { + ev = EV_BEACON_TRACKED; + if( (flags & (BCN_PARTIAL|BCN_FULL)) == 0 ) { + // We don't have a previous beacon to calc some drift - assume some max value + calcBcnRxWindowFromMillis(BCN_100PPM_ms,0); + goto rev; + } + // We have a previous BEACON to calculate some drift + s16_t drift = (LMIC.bcninfo.txtime - lasttx) - BCN_INTV_osticks; + if( LMIC.missedBcns > 0 ) { + drift = LMIC.drift + (drift - LMIC.drift) / (LMIC.missedBcns+1); + } + if( (LMIC.bcninfo.flags & BCN_NODRIFT) == 0 ) { + s16_t diff = LMIC.drift - drift; + if( diff < 0 ) diff = -diff; + LMIC.lastDriftDiff = diff; + if( LMIC.maxDriftDiff < diff ) + LMIC.maxDriftDiff = diff; + LMIC.bcninfo.flags &= ~BCN_NODDIFF; + } + LMIC.drift = drift; + LMIC.missedBcns = LMIC.rejoinCnt = 0; + LMIC.bcninfo.flags &= ~BCN_NODRIFT; + ASSERT((LMIC.bcninfo.flags & (BCN_PARTIAL|BCN_FULL)) != 0); + } else { + ev = EV_BEACON_MISSED; + LMIC.bcninfo.txtime += BCN_INTV_osticks + LMIC.drift; + LMIC.bcninfo.time += BCN_INTV_sec; + LMIC.missedBcns++; + // Delay any possible TX after surmised beacon - it's there although we missed it + txDelay(LMIC.bcninfo.txtime + BCN_RESERVE_osticks, 4); + if( LMIC.missedBcns > MAX_MISSED_BCNS ) + LMIC.opmode |= OP_REJOIN; // try if we can roam to another network + if( LMIC.bcnRxsyms > MAX_RXSYMS ) { + LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN); + reportEvent(EV_LOST_TSYNC); + return; + } + } + LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - calcRxWindow(0, REGION.beaconDr); + LMIC.bcnRxsyms = LMIC.rxsyms; + rev: + LMIC.bcnChnl = (LMIC.bcnChnl+1) % numBcnChannels(); + if( (LMIC.opmode & OP_PINGINI) != 0 ) + rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! + reportEvent(ev); +} + +static void startRxBcn (osjob_t* osjob) { + (void)osjob; // unused + LMIC.osjob.func = FUNC_ADDR(processBeacon); + os_radio(RADIO_RX); +} + + +static void startRxPing (osjob_t* osjob) { + (void)osjob; // unused + LMIC.osjob.func = FUNC_ADDR(processPingRx); + os_radio(RADIO_RX); +} +#endif + + + +// Decide what to do next for the MAC layer of a device +static void engineUpdate (void) { + debug_printf("engineUpdate[opmode=0x%x]\r\n", LMIC.opmode); + // Check for ongoing state: scan or TX/RX transaction + if( (LMIC.opmode & (OP_NOENGINE|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) { + return; + } + +#ifdef CFG_autojoin + if( LMIC.netid == NETID_NONE && (LMIC.opmode & OP_JOINING) == 0 ) { + LMIC_startJoining(); + return; + } +#endif // CFG_autojoin + + ostime_t now = os_getTime(); + ostime_t txbeg = 0; + +#if !defined(DISABLE_CLASSB) + ostime_t rxtime = 0; + + if( (LMIC.opmode & OP_SCAN) != 0 ) { + // Looking for a beacon - LMIC.bcninfo.txtime is timeout for scan + // Cancel onging TX/RX transaction + LMIC.dataLen = 0; + LMIC.txCnt = LMIC.dnConf = LMIC.bcninfo.flags = 0; + LMIC.opmode &= ~OP_TXRXPEND; + LMIC.bcnChnl = 0; + setBcnRxParams(); + os_setTimedCallback(&LMIC.osjob, LMIC.bcninfo.txtime, FUNC_ADDR(onBcnScanRx)); + os_radio(RADIO_RXON); + return; + } + if( (LMIC.opmode & OP_TRACK) != 0 ) { + // We are tracking a beacon + rxtime = LMIC.bcnRxtime - RX_RAMPUP; +#if CFG_simul + // Simulation is sometimes late - don't die here but keep going. + // Results in a missed beacon. On the HW this spells a more serious problem. + if( (ostime_t)(rxtime-now) < 0 ) { + fprintf(stderr, "ERROR: engineUpdate/OP_TRACK: delta=%d now=0x%X rxtime=0x%X LMIC.bcnRxtime=0x%X RX_RAMPUP=%d\n", + (ostime_t)(rxtime-now),now,rxtime,LMIC.bcnRxtime,RX_RAMPUP); + } +#else + ASSERT( (ostime_t)(rxtime-now) >= 0 ); +#endif + } +#endif + if( LMIC.pollcnt ) + opmodePoll(); + + if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA)) != 0 || + ((LMIC.opmode & OP_POLL) && now - LMIC.polltime >= LMIC.polltimeout)) { + // Need to TX some data... + // Assuming txChnl points to channel which first becomes available again. + bit_t jacc = ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) != 0 ? 1 : 0); + if (jacc) + debug_verbose_printf("Uplink join pending\r\n", os_getTime()); + else + debug_verbose_printf("Uplink data pending\r\n", os_getTime()); + // Find next suitable channel and return availability time + if( (LMIC.opmode & OP_NEXTCHNL) != 0 ) { + txbeg = LMIC.txend = nextTx(now+TX_RAMPUP); + debug_verbose_printf("Airtime available at %t (channel duty limit)\r\n", txbeg); + } else { + txbeg = LMIC.txend; + debug_verbose_printf("Airtime available at %t (previously determined)\r\n", txbeg); + } + // Delayed TX or waiting for duty cycle? + if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0) && (txbeg - LMIC.globalDutyAvail) < 0 ) { + txbeg = LMIC.globalDutyAvail; + debug_verbose_printf("Airtime available at %t (global duty limit)\r\n", txbeg); + } +#if !defined(DISABLE_CLASSB) + // If we're tracking a beacon... + // then make sure TX-RX transaction is complete before beacon + if( (LMIC.opmode & OP_TRACK) != 0 && + txbeg + (jacc ? JOIN_GUARD_osticks : TXRX_GUARD_osticks) - rxtime > 0 ) { + + debug_verbose_printf("Awaiting beacon before uplink\r\n"); + + // Not enough time to complete TX-RX before beacon - postpone after beacon. + // In order to avoid clustering of postponed TX right after beacon randomize start! + txDelay(rxtime + BCN_RESERVE_osticks, 16); + txbeg = 0; + goto checkrx; + } +#endif + // Earliest possible time vs overhead to setup radio + if( txbeg - (now + TX_RAMPUP) <= 0 ) { + debug_verbose_printf("Ready for uplink\r\n"); + // We could send right now! + txbeg = now + TX_RAMPUP; + dr_t txdr = LMIC.datarate; + if( jacc ) { + u8_t ftype; + if( (LMIC.opmode & OP_REJOIN) != 0 ) { + txdr = lowerDR(txdr, LMIC.rejoinCnt); + ftype = HDR_FTYPE_REJOIN; + } else { + ftype = HDR_FTYPE_JREQ; + } + LMIC.txrxFlags = 0; + buildJoinRequest(ftype); + LMIC.osjob.func = FUNC_ADDR(jreqDone); + } else { + // XXX:TODO - also handle LMIC.seqnoADn rollover + if( LMIC.seqnoDn >= 0xFFFFFF80 ) { + // Imminent roll over - proactively reset MAC + // Device has to react! NWK will not roll over and just stop sending. + // Thus, we have N frames to detect a possible lock up. + debug_printf("Down FCNT about to rollover (0x%lx), resetting session\n", LMIC.seqnoDn); + reset: + os_setCallback(&LMIC.osjob, FUNC_ADDR(runReset)); + return; + } + if( (LMIC.txCnt==0 && LMIC.seqnoUp == 0xFFFFFFFF) ) { + debug_printf("Up FCNT about to rollover (0x%lx), resetting session\n", LMIC.seqnoUp); + // Roll over of up seq counter + // Do not run RESET event callback from here! + // App code might do some stuff after send unaware of RESET. + goto reset; + } + LMIC.txrxFlags = 0; + buildDataFrame(); + if( LMIC.dataLen == 0 ) { + debug_printf("Zero data length, not sending\n"); + txError(); + return; + } + LMIC.osjob.func = FUNC_ADDR(updataDone); + } + LMIC.rps = updr2rps(txdr); + // For CUSTOM_DR, assume that rps already has the right CR + // and that dndr is also preset to the right DR. + if (txdr != CUSTOM_DR) { + LMIC.rps = setCr(LMIC.rps, LMIC.errcr); + // Calculate dndr to use based on txdr (can be != LMIC.datarate for joins) + LMIC.dndr = prepareDnDr(txdr); + } + LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL; + updateTx(txbeg); + reportEvent(EV_TXSTART); + os_radio(RADIO_TX); + return; + } + debug_verbose_printf("Uplink delayed until %t\r\n", txbeg); + // Cannot yet TX + if( (LMIC.opmode & OP_TRACK) == 0 ) + goto txdelay; // We don't track the beacon - nothing else to do - so wait for the time to TX + // Consider RX tasks + if( txbeg == 0 ) // zero indicates no TX pending + txbeg += 1; // TX delayed by one tick (insignificant amount of time) + } else { + // No TX pending - no scheduled RX + if( (LMIC.opmode & OP_TRACK) == 0 ) { + if( (LMIC.clmode & CLASS_C) ) { + setupRx2ClassC(); + } + return; + } + } + +#if !defined(DISABLE_CLASSB) + // Are we pingable? + checkrx: + if( (LMIC.opmode & OP_PINGINI) != 0 ) { + // One more RX slot in this beacon period? + if( rxschedNext(&LMIC.ping, now+RX_RAMPUP) ) { + if( txbeg != 0 && (txbeg - LMIC.ping.rxtime) < 0 ) + goto txdelay; + LMIC.rxsyms = LMIC.ping.rxsyms; + LMIC.rxtime = LMIC.ping.rxtime; + LMIC.freq = LMIC.ping.freq; // XXX:US like => calc based on beacon time! + LMIC.rps = dndr2rps(LMIC.ping.dr); + LMIC.dataLen = 0; + ASSERT(LMIC.rxtime - now+RX_RAMPUP >= 0 ); + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, FUNC_ADDR(startRxPing)); + return; + } + // no - just wait for the beacon + } + + if( txbeg != 0 && (txbeg - rxtime) < 0 ) + goto txdelay; + + setBcnRxParams(); + LMIC.rxsyms = LMIC.bcnRxsyms; + LMIC.rxtime = LMIC.bcnRxtime; + if( now - rxtime >= 0 ) { + LMIC.osjob.func = FUNC_ADDR(processBeacon); + os_radio(RADIO_RX); + return; + } + os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn)); + return; +#endif + + txdelay: + if( (LMIC.clmode & CLASS_C) ) { + setupRx2ClassC(); + } + os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate)); +} + + +void LMIC_setAdrMode (bit_t enabled) { + LMIC.adrEnabled = enabled ? FCT_ADREN : 0; +} + + +// Should we have/need an ext. API like this? +void LMIC_setDrTxpow (dr_t dr, s8_t txpowadj) { + ASSERT(dr != CUSTOM_DR); // Use setCustomDr() for that + setDrTxpow(DRCHG_SET, dr, txpowadj); + syncDatarate(); + // Make sure dndr is not CUSTOM_DR anymore (actual value does not + // matter, it will be set later *if* not CUSTOM_DR). + LMIC.dndr = dr; +} + +dr_t LMIC_setCustomDr (rps_t custom_rps, dr_t dndr) { + dr_t old_dr = LMIC.datarate; + setDrTxpow(DRCHG_SET, CUSTOM_DR, KEEP_TXPOWADJ); + LMIC.dndr = dndr; + LMIC.custom_rps = custom_rps; + return old_dr; +} + +void LMIC_selectChannel(u8_t channel) { + LMIC.opmode &= ~OP_NEXTCHNL; + LMIC.txChnl = channel; + LMIC.txend = os_getTime(); +} + + +void LMIC_shutdown (void) { + os_clearCallback(&LMIC.osjob); + os_radio(RADIO_STOP); + LMIC.opmode |= OP_SHUTDOWN; +} + + +int LMIC_regionIdx (u8_t regionCode) { + if( regionCode == REGCODE_UNDEF ) { + return 0; + } + for( int idx = 0; idx < REGIONS_COUNT; idx++ ) { + if (REGIONS[idx].regcode == regionCode) { + return idx; + } + } + return -1; +} + +u8_t LMIC_regionCode (u8_t regionIdx) { + ASSERT(regionIdx < REGIONS_COUNT); + return REGIONS[regionIdx].regcode; +} + +void LMIC_reset_ex (u8_t regionCode) { + os_radio(RADIO_STOP); + os_clearCallback(&LMIC.osjob); + + os_clearMem((u8_t*) &LMIC, sizeof(LMIC)); + + // set region + int regionIdx = LMIC_regionIdx(regionCode); + ASSERT(regionIdx >= 0); + LMIC.region = ®IONS[regionIdx]; + // reset and calibrate radio + LMIC.freq = (REGION.maxFreq - REGION.minFreq) / 2; + os_radio(RADIO_INIT); + + LMIC.opmode = OP_NONE; + LMIC.netid = NETID_NONE; + LMIC.errcr = CR_4_5; + LMIC.adrEnabled = FCT_ADREN; + LMIC.datarate = fastest125(); + LMIC.dn1Dly = 1; + LMIC.dn2Dr = REGION.rx2Dr; // we need this for 2nd DN window of join accept + LMIC.dn2Freq = REGION.rx2Freq; // ditto +#if !defined(DISABLE_CLASSB) + LMIC.ping.freq = REGION.pingFreq; // defaults for ping + LMIC.ping.dr = REGION.pingDr; // ditto + LMIC.ping.intvExp = 8; // no ping interval ever sent up +#endif + LMIC.refChnl = REG_IS_FIX() ? 0 : os_getRndU1(); // fix uses randomized hoplist starting with block0 + LMIC.adrAckLimit = ADR_ACK_LIMIT; + LMIC.adrAckDelay = ADR_ACK_DELAY; + LMIC.adrAckReq = LINK_CHECK_INIT; + + iniRxdErr(); +} + +void LMIC_reset (void) { + LMIC_reset_ex(os_getRegion()); +} + +void LMIC_init (void) { + LMIC.opmode = OP_SHUTDOWN; +} + +void LMIC_clrTxData (void) { + LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND|OP_POLL); + LMIC.pendTxLen = 0; + if( (LMIC.opmode & (OP_JOINING|OP_SCAN)) != 0 ) // do not interfere with JOINING/SCANNING + return; + os_clearCallback(&LMIC.osjob); + os_radio(RADIO_STOP); + engineUpdate(); +} + + +void LMIC_setTxData (void) { + ASSERT((LMIC.opmode & OP_JOINING) == 0); + LMIC.opmode |= OP_TXDATA; + LMIC.txCnt = 0; // reset nbTrans counter + engineUpdate(); +} + + +// +int LMIC_setTxData2 (u8_t port, u8_t* data, u8_t dlen, u8_t confirmed) { + if( dlen > sizeof(LMIC.pendTxData) ) + return -2; + if( data != (u8_t*)0 ) + os_copyMem(LMIC.pendTxData, data, dlen); + LMIC.pendTxConf = confirmed; + LMIC.pendTxPort = port; + LMIC.pendTxLen = dlen; + LMIC_setTxData(); + return 0; +} + + +// Send a payload-less message to signal device is alive +void LMIC_sendAlive (void) { + opmodePoll(); + LMIC.pendTxConf = 0; + LMIC.pendTxLen = 0; + LMIC.txCnt = 0; // reset nbTrans counter + engineUpdate(); +} + + +// Check if other networks are around. +void LMIC_tryRejoin (void) { + LMIC.opmode |= OP_REJOIN; + engineUpdate(); +} + + +// Check if other networks are around. +void LMIC_setClassC (u8_t enabled) { + if( (LMIC.clmode & CLASS_C) == (enabled?CLASS_C:0) ) + return; // already in that mode + if( (LMIC.clmode & PEND_CLASS_C) ) + return; // change is already pending +#if !defined(DISABLE_CLASSB) + if( enabled ) + LMIC_stopPingable(); // stop class B +#endif +#if defined(CFG_lorawan11) + if( enabled != UNILATERAL_CLASS_C ) { + LMIC.clmode |= PEND_CLASS_C; + opmodePoll(); + incPollcnt(); + engineUpdate(); + return; + } +#endif + // LoRaWAN 1.0.2 - switch mode unilaterally + // Device is provisioned as class C + LMIC.clmode = CLASS_C; + engineUpdate(); +} + +//! \brief Setup given session keys +//! and put the MAC in a state as if +//! a join request/accept would have negotiated just these keys. +//! It is crucial that the combinations `devaddr/nwkkey` and `devaddr/artkey` +//! are unique within the network identified by `netid`. +//! NOTE: on Harvard architectures when session keys are in flash: +//! Caller has to fill in LMIC.{nwk,art}Key before and pass {nwk,art}Key are NULL +//! \param netid a 24 bit number describing the network id this device is using +//! \param devaddr the 32 bit session address of the device. It is strongly recommended +//! to ensure that different devices use different numbers with high probability. +//! \param nwkKey the 16 byte network session key used for message integrity. +//! If NULL the caller has copied the key into `LMIC.nwkKey` before. +#if defined(CFG_lorawan11) +//! \param nwkKeyDn the 16 byte network session key used for down-link message integrity. +//! If NULL the caller has copied the key into `LMIC.nwkKeyDn` before. +#endif +//! \param appKey the 16 byte application router session key used for message confidentiality. +//! If NULL the caller has copied the key into `LMIC.appKey` before. +void LMIC_setSession (u32_t netid, devaddr_t devaddr, const u8_t* nwkKey, +#if defined(CFG_lorawan11) + const u8_t* nwkKeyDn, +#endif + const u8_t* appKey) { + LMIC.netid = netid; + LMIC.devaddr = devaddr; + lce_loadSessionKeys(nwkKey, +#if defined(CFG_lorawan11) + nwkKeyDn, +#endif + appKey); + + initDefaultChannels(); + + stateJustJoined(); + setDrTxpow(DRCHG_SET, fastest125(), 0); + LMIC.dn2Dr = REGION.rx2Dr; + LMIC.dn1Dly = 1; + LMIC.dn1DrOffIdx = 0; +} + +int LMIC_setMultiCastSession (devaddr_t grpaddr, const u8_t* nwkKeyDn, const u8_t* appKey, u32_t seqnoADn) { + session_t* s; + for(s = LMIC.sessions; sgrpaddr!=0 && s->grpaddr!=grpaddr; s++); + if (s >= LMIC.sessions+MAX_MULTICAST_SESSIONS) + return 0; + + s->grpaddr = grpaddr; + s->seqnoADn = seqnoADn; + + if( nwkKeyDn != (u8_t*)0 ) { + os_copyMem(s->nwkKeyDn, nwkKeyDn, 16); + os_copyMem(&LMIC.lceCtx.mcgroup[LCE_MCGRP_0 + (s-LMIC.sessions)].nwkSKeyDn, nwkKeyDn, 16); + } + + if( appKey != (u8_t*)0 ) { + os_copyMem(s->appKey, appKey, 16); + os_copyMem(&LMIC.lceCtx.mcgroup[LCE_MCGRP_0 + (s-LMIC.sessions)].appSKey, appKey, 16); + } + return 1; +} + +// Enable/disable link check validation. +// LMIC sets the ADRACKREQ bit in UP frames if there were no DN frames +// for a while. It expects the network to provide a DN message to prove +// connectivity with a span of UP frames. If this no such prove is coming +// then the datarate is lowered and a LINK_DEAD event is generated. +// This mode can be disabled and no connectivity prove (ADRACKREQ) is requested +// nor is the datarate changed. +// This must be called only if a session is established (e.g. after EV_JOINED) +void LMIC_setLinkCheckMode (bit_t enabled) { + LMIC.adrAckReq = enabled ? LINK_CHECK_INIT : LINK_CHECK_OFF; +} + +void LMIC_setLinkCheck (u32_t limit, u32_t delay) { + LMIC.adrAckLimit = limit; + LMIC.adrAckDelay = delay; + LMIC.adrAckReq = LINK_CHECK_INIT; +} + +bit_t LMIC_setupChannel (u8_t chidx, freq_t freq, u16_t drmap) { + if (REG_IS_FIX()) { + (void)chidx; (void)freq; (void)drmap; // unused + return 0; + } else { +#ifdef REG_DYN + return setupChannel_dyn(chidx, freq, drmap); +#endif + } +} + +void LMIC_disableChannel (u8_t chidx) { + disableChannel(chidx); +} + +void LMIC_askForLinkCheck (void) { + LMIC.gwmargin = 255; + LMIC.gwcnt = 0; +} + + +// Fastest UP datarate +dr_t LMIC_fastestDr () { + dr_t dr = 1; // assuming DR=0 is always 125kHz + for(; dr < 16; dr++ ) { + rps_t rps = REGION.dr2rps[dr]; + if( rps == ILLEGAL_RPS || getNocrc(rps) ) // DN only DR + break; + } + return dr-1; +} + + +// Slowest UP datarate +dr_t LMIC_slowestDr () { + return 0; //XXX:AS923:TBD must be 2 under certain conditions +} + + +rps_t LMIC_updr2rps (u8_t dr) { + return updr2rps(dr); +} + +rps_t LMIC_dndr2rps (u8_t dr) { + return dndr2rps(dr); +} + +ostime_t LMIC_calcAirTime (rps_t rps, u8_t plen) { + return calcAirTime(rps, plen); +} + +u8_t LMIC_maxAppPayload () { + return REGION.dr2maxAppPload[LMIC.datarate]; +} + +ostime_t LMIC_nextTx (ostime_t now) { + return nextTx(now); +} + +// Remove duty cycle limitations +void LMIC_disableDC (void) { + LMIC.noDC = 1; +} + +#if defined(CFG_simul) +#include "addr2func.h" +#include "arr2len.h" +#endif + +#if defined(CFG_extapi) + +// Enable fast join (for testing only) +// Removes duty cycle limitations. +// Call directly after LMIC_startJoining() returns non-zero. +void LMIC_enableFastJoin (void) { + // XXX deprecated +} + +/// Used for regression testing +ostime_t LMIC_dr2hsym (dr_t dr, s8_t num) { + return dr2hsym(dr,num); +} + +void LMIC_updateTx (ostime_t now) { + updateTx(now); +} + +// Return scaled clock skew (RXDERR_SHIFT) and variation span in osticks. +void LMIC_getRxdErrInfo (s32_t* skew, u32_t* span) { + *skew = evalRxdErr(span); +} + +#endif + +#define __i(func,suffix) .func = func ## suffix +#ifdef REG_DYN +#define __dyn(func) __i(func,_dyn) +static const rfuncs_t RFUNCS_DYN = { + __dyn(disableChannel), + __dyn(initDefaultChannels), + __dyn(prepareDn), + __dyn(applyChannelMap), + __dyn(checkChannelMap), + __dyn(syncDatarate), + __dyn(updateTx), + __dyn(nextTx), +#if !defined(DISABLE_CLASSB) + __dyn(setBcnRxParams), +#endif +}; +#undef __dyn +#endif // REG_DYN +#ifdef REG_FIX +#define __fix(func) __i(func,_fix) +static const rfuncs_t RFUNCS_FIX = { + __fix(disableChannel), + __fix(initDefaultChannels), + __fix(prepareDn), + __fix(applyChannelMap), + __fix(checkChannelMap), + __fix(syncDatarate), + __fix(updateTx), + __fix(nextTx), +#if !defined(DISABLE_CLASSB) + __fix(setBcnRxParams), +#endif +}; +#undef __fix +#endif // REG_FIX +#undef __i diff --git a/src/BasicMAC/lmic/lmic.h b/src/BasicMAC/lmic/lmic.h new file mode 100644 index 0000000..d3cef9e --- /dev/null +++ b/src/BasicMAC/lmic/lmic.h @@ -0,0 +1,614 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +//! @file +//! @brief LMIC API + +#ifndef _lmic_h_ +#define _lmic_h_ + +#include "region.h" +#include "oslmic.h" +#include "lorabase.h" +#include "lce.h" + +#ifdef __cplusplus +extern "C"{ +#endif + +// LMIC version +#define LMIC_VERSION_MAJOR 2 +#define LMIC_VERSION_MINOR 1 + + +// ------------------------------------------------ +// BEGIN -- MULTI REGION + +// band +typedef struct { + freq_t lo, hi; + u16_t txcap; + s8_t txpow; +} band_t; + +#define MAX_BANDS 8 +#define BAND_MASK (MAX_BANDS-1) +#if (MAX_BANDS & BAND_MASK) != 0 +#error "MAX_BANDS must be a power of 2" +#endif + +enum { + CAP_NONE = 1, + CAP_DECI = 10, + CAP_CENTI = 100, + CAP_MILLI = 1000, +}; + +// region IDs +enum { +#ifdef CFG_eu868 + LMIC_REGION_EU868, +#endif +#ifdef CFG_as923 + LMIC_REGION_AS923, +#endif +#ifdef CFG_us915 + LMIC_REGION_US915, +#endif +#ifdef CFG_au915 + LMIC_REGION_AU915, +#endif +#ifdef CFG_cn470 + LMIC_REGION_CN470, +#endif +#ifdef CFG_in865 + LMIC_REGION_IN865, +#endif + REGIONS_COUNT +}; + +// region flags +enum { + REG_FIXED = (1 << 0), // fixed channel plan + REG_PSA = (1 << 1), // ETSI-style polite spectrum access +}; + +enum { UPCHSPACING_125kHz = 200000 }; //XXX:hack +enum { UPCHSPACING_500kHz = 1600000 }; //XXX:hack +enum { DNCHSPACING_500kHz = 600000 }; //XXX:hack +enum { DNCHSPACING_125kHz = 200000 }; //XXX:hack + +typedef struct { + void (*disableChannel) (u8_t chidx); + void (*initDefaultChannels) (void); + void (*prepareDn) (); + u8_t (*applyChannelMap) (u8_t chpage, u16_t chmap, u16_t* dest); + u8_t (*checkChannelMap) (u16_t* map); + void (*syncDatarate) (void); + void (*updateTx) (ostime_t txbeg); + ostime_t (*nextTx) (ostime_t now); + void (*setBcnRxParams) (void); +} rfuncs_t; + +// Immutable region definition +// TODO: reorder struct members to optimize padding +typedef struct { + u32_t flags; + union { + // dynamic channels + struct { + freq_t defaultCh[MIN_DYN_CHNLS]; // default channel frequencies + freq_t beaconFreq; // beacon frequency + u16_t chTxCap; // per-channel DC + u16_t ccaTime; // CCA time (ticks) + s8_t ccaThreshold; // CCA threshold + band_t bands[MAX_BANDS]; // band definitions + }; + + // fixed channels + struct { + freq_t baseFreq125; // base frequency for 125kHz channels + freq_t baseFreqFix; // base frequency for fixed-DR channels + freq_t baseFreqDn; // base frequency downlink channels + u8_t numChBlocks; // number of 8-channel blocks + u8_t numChDnBlocks; // number of 8-channel blocks for downlink + dr_t joinDr; // join channel data rate + dr_t fixDr; // fixed-DR channel data rate + }; + }; + + // Common + const rfuncs_t* rfuncs; + const u8_t* dr2rps; // pointer to DR table + freq_t minFreq, maxFreq; // legal frequency range + freq_t rx2Freq; // RX2 frequency + freq_t pingFreq; // ping frequency + dr_t rx2Dr; // RX2 data rate + dr_t pingDr; // ping data rate + dr_t beaconDr; // beacon data rate + u8_t beaconOffInfo; // offset beacon info field + u8_t beaconLen; // beacon length + ostime_t beaconAirtime; // beacon air time + eirp_t maxEirp; // max. EIRP (initial value) + s8_t rx1DrOff[8]; // RX1 data rate offsets + u8_t dr2maxAppPload[16]; // max application payload (assuming no repeater and no fopts) + u8_t regcode; // external region code + +} LMIC_REGION_t; + + +// END -- MULTI REGION +// ------------------------------------------------ + +enum { TXCONF_ATTEMPTS = 8 }; //!< Transmit attempts for confirmed frames +enum { MAX_MISSED_BCNS = 20 }; // threshold for triggering rejoin requests +enum { MAX_RXSYMS = 100 }; // stop tracking beacon beyond this + +#define RXDERR_NUM 5 +#define RXDERR_SHIFT 4 +#define RXDERR_SCALE (1< port + TXRX_DNW1 = 0x01, // received in 1st DN slot + TXRX_DNW2 = 0x02, // received in 2dn DN slot + TXRX_PING = 0x04, // received in a scheduled RX slot + TXRX_NOTX = 0x08, // set if frame could not be sent as requested +}; +// Event types for event callback +enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND, + EV_BEACON_MISSED, EV_BEACON_TRACKED, EV_JOINING, + EV_JOINED, EV_RFU1, EV_JOIN_FAILED, EV_REJOIN_FAILED, + EV_TXCOMPLETE, EV_LOST_TSYNC, EV_RESET, EV_RXCOMPLETE, + EV_LINK_DEAD, EV_LINK_ALIVE, EV_SCAN_FOUND, EV_TXSTART, + EV_TXDONE, EV_DATARATE, EV_START_SCAN, EV_ADR_BACKOFF }; +typedef enum _ev_t ev_t; + + +// Internal use values in lmic_t.opts, uses the unused upper nibble +// of option bitmap 1 (0xf0). +enum { + OPT_LORAWAN11 = 0x80, // Running LoRaWAN 1.1 + OPT_OPTNEG = 0x40, // Send ResetInd mac command +}; + +// Ignore nbTrans for next TX attempt +// Used by various services (streaming/fileupload/alcsync) to temporarily disable nbTrans +enum { + IGN_NBTRANS = 0x80 // to be ORed to nbTrans (auto-cleared after TX) +}; + +// data stored in clmode +enum { + CLASS_C = 0x01, // 0=class A, 1=class C + PEND_CLASS_C = 0x02, // 1=MCMD_DEVMD_IND sent (with !CLASS_C, waiting for MCMD_DEVMD_CONF) +}; +// parameters for LMIC_setClassC(..) +enum { + DISABLE_CLASS_C = 0, // disable class C - aka enable class A + ENABLE_CLASS_C = 1, // enable class C - stop class A + // do not notify network and do not wait for confirmation + // (device provisioned as class C) + UNILATERAL_CLASS_C = 2, +}; + +typedef struct { + devaddr_t grpaddr; // multicast group address + u8_t nwkKeyDn[16]; // network session key for down-link + u8_t appKey[16]; // application session key + u32_t seqnoADn; // down stream seqno (AFCntDown) +} session_t; + +// duty cycle/dwell time relative to baseAvail in sec. +// To avoid roll over this needs to be updated. +typedef u16_t avail_t; + +#define MAX_MULTICAST_SESSIONS LCE_MCGRP_MAX + +#define CHMAP_SZ (MAX_FIX_CHNLS+15)/16 + +struct lmic_t { + // Radio settings TX/RX (also accessed by HAL) + ostime_t txend; + ostime_t rxtime; // timestamp when frame was fully received + ostime_t rxtime0; // timestamp when preamble of frame was received (computed) + u32_t freq; + s8_t rssi; + s8_t snr; + rps_t rps; + rps_t custom_rps; // Used for CUSTOM_DR + u8_t rxsyms; + u8_t dndr; + s8_t txpow; // dBm -- needs to be combined with brdTxPowOff + + osjob_t osjob; + + osxtime_t baseAvail; // base time for availability + avail_t globalAvail; // next available DC (global) + u8_t noDC; // disable all duty cycle + + const LMIC_REGION_t* region; + union { +#ifdef REG_DYN + // ETSI-like (dynamic channels) + struct { + avail_t bandAvail[MAX_BANDS]; // next available DC (per band) + avail_t chAvail[MAX_DYN_CHNLS]; // next available DC (per channel) + freq_t chUpFreq[MAX_DYN_CHNLS];// uplink frequency + freq_t chDnFreq[MAX_DYN_CHNLS];// downlink frequency + drmap_t chDrMap[MAX_DYN_CHNLS]; // enabled data rates + + u16_t channelMap; // active channels + } dyn; +#endif +#ifdef REG_FIX + // FCC-like (fixed channels) + struct { + u16_t channelMap[CHMAP_SZ]; // enabled bits + u8_t hoplist[MAX_FIX_CHNLS_125]; // hoplist + } fix; +#endif + }; + + u8_t refChnl; // channel randomizer - search relative to this indicator + u8_t txChnl; // channel for next TX + u8_t globalDutyRate; // max rate: 1/2^k + ostime_t globalDutyAvail; // time device can send again -- XXX:PROBLEM if no TX for ~18h we have a rollover here!! --> avail_t?? + + u32_t netid; // current network id (~0 - none) + u16_t opmode; + u8_t clmode; // current/pending class A/B/C + u8_t pollcnt; // >0 waiting for an answer from network + u8_t nbTrans; // ADR controlled frame repetition + s8_t txPowAdj; // adjustment for txpow (ADR controlled) + s8_t brdTxPowOff; // board-specific power adjustment offset + dr_t datarate; // current data rate + cr_t errcr; // error coding rate (used for TX only) + u8_t rejoinCnt; // adjustment for rejoin datarate + s16_t drift; // last measured drift + s16_t lastDriftDiff; + s16_t maxDriftDiff; + osxtime_t gpsEpochOff; // gpstime = gpsEpochOff+getXTime(), 0=undefined + s32_t rxdErrs[RXDERR_NUM]; + u8_t rxdErrIdx; + + u8_t pendTxPort; + u8_t pendTxConf; // confirmed data + u8_t pendTxLen; // +0x80 = confirmed + u8_t pendTxData[MAX_LEN_PAYLOAD]; + u8_t pendTxNoRx; // don't listen for down data after tx + + u16_t devNonce; // last generated nonce + lce_ctx_t lceCtx; + devaddr_t devaddr; + u32_t seqnoDn; // device level down stream seqno +#if defined(CFG_lorawan11) + u32_t seqnoADn; // device level down stream seqno (AFCntDown) +#endif + u32_t seqnoUp; + + u8_t dnConf; // dn frame confirm pending: LORA::FCT_ACK or 0 + s32_t adrAckReq; // counter until we reset data rate (0x80000000=off) + u32_t adrAckLimit; // ADR_ACK_LIMIT + u32_t adrAckDelay; // ADR_ACK_DELAY + + u8_t margin; // bits 7/6:RFU, 0-5: SNR of last DevStatusReq frame, reported by DevStatusAns to network + u8_t gwmargin; // last reported by network via LinkCheckAns + u8_t gwcnt; // - ditto - + u8_t foptsUpLen; + u8_t foptsUp[64]; // pending FOpts in up direction - cleared after next send + bit_t devsAns; // device status answer pending + u8_t adrEnabled; + u8_t moreData; // NWK has more data pending + bit_t dutyCapAns; // have to ACK duty cycle settings + //XXX:old: u8_t snchAns; // answer set new channel + u8_t dn1Dly; // delay in secs to DNW1 + s8_t dn1DrOffIdx; // index into DR offset table (can be negative in some regions!) + // 2nd RX window (after up stream) + u8_t dn2Dr; + u32_t dn2Freq; + u8_t dn2Ans; // 0=no answer pend, 0x80+ACKs + u8_t dn1DlyAns; // 0=no answer pend, 0x80 send MCMD_RXTM_ANS + u8_t dnfqAns; // # of DNFQ in this down frame + u8_t dnfqAnsPend; // pending ACK bits (2 each) + u32_t dnfqAcks; // ack bit pending + + // multicast sessions + session_t sessions[MAX_MULTICAST_SESSIONS]; + +#if defined(CFG_lorawan11) + u8_t opts; // negotiated protocol options +#endif + +#if !defined(DISABLE_CLASSB) + // Class B state + u8_t missedBcns; // unable to track last N beacons + s8_t askForTime; // how often to ask for time + //XXX:old: u8_t pingSetAns; // answer set cmd and ACK bits + rxsched_t ping; // pingable setup +#endif + + // Public part of MAC state + u8_t txCnt; + u8_t txrxFlags; // transaction flags (TX-RX combo) + u8_t dataBeg; // 0 or start of data (dataBeg-1 is port) + u8_t dataLen; // 0 no data or zero length data, >0 byte count of data + u8_t frame[MAX_LEN_FRAME]; + +#if !defined(DISABLE_CLASSB) + u8_t bcnfAns; // mcmd beacon freq: bit7:pending, bit0:ACK/NACK + u8_t bcnChnl; + u32_t bcnFreq; // 0=default, !=0: specific BCN freq/no hopping + u8_t bcnRxsyms; // + ostime_t bcnRxtime; + bcninfo_t bcninfo; // Last received beacon info +#endif + + u8_t noRXIQinversion; + + // automatic sending of MAC uplinks without payload + osjob_t polljob; // job to schedule engineUpdate in poll mode + ostime_t polltime; // time when OP_POLL flag was set + ostime_t polltimeout; // timeout when frame will be sent even without payload (default 0) + + // radio power consumption + u32_t radioPwr_ua; // power consumption of current radio operation in uA + +#ifdef CFG_testpin + // Signal specific event via a GPIO pin. + // Test pin is routed to PPS pin of SX1301 to record time. + // Current events: + // 1=txdone, 2=rxend, 3=rxstart + u8_t testpinMode; +#endif +}; +//! \var struct lmic_t LMIC +//! The state of LMIC MAC layer is encapsulated in this variable. +DECLARE_LMIC; //!< \internal + +//! Construct a bit map of allowed datarates from drlo to drhi (both included). +#define DR_RANGE_MAP(drlo,drhi) ((drmap_t) ((0xFFFF<<(drlo)) & (0xFFFF>>(15-(drhi))))) + +bit_t LMIC_setupChannel (u8_t channel, freq_t freq, u16_t drmap); +void LMIC_disableChannel (u8_t channel); + +// Manually select the next channel for the next transmission. +// +// Warning: This does not do any checking. In particular, this bypasses +// duty cycle limits, allows selecting a channel that is not configured +// for the current datarate, and breaks when you select an invalid or +// disabled channel. +// +// The selected channel applies only to the next transmission. Call this +// *after* setting the datarate (if needed) with LMIC_setDrTxpow(), +// since that forces a new channel to be selected automatically. +void LMIC_selectChannel(u8_t channel); + +// Use a custom datrate and rps value. +// +// This causes the uplink to use the radio settings described by the +// given rps value, which can be any valid rps setting (even when the +// region does not normally enable it). The rps setting is used +// unmodified for uplink, and will have nocrc set for downlink. +// +// While the custom datarate is active, it will not be modified +// automatically (e.g. LinkADRReq is rejected and lowring DR for ADR is +// suspended), except when it is not enabled for any channel (in dynamic +// regions). +// +// However, if you call this function again to change the rps value for +// RX1 or RX2 (see below), it will also apply to subsequent uplinks, so +// you might need to set a new rps or standard datarate before the next +// uplink. +// +// This returns the old uplink DR, which can be later be passed to +// LMIC_setDrTxpow() to disable the custom datarate again, if needed. +// +// RX1 +// +// Normally, the RX1 datarate is derived from the uplink datarate. When +// using a custom datarate, it must be set explicitly using the dndr +// parameter to this function. This can be either a standard datarate +// value, or CUSTOM_DR to use the same custom rps value as the uplink. +// +// To use a custom rps for RX1 that is different from the uplink (or +// use a custom rps just for RX1), call this function (again) after the +// EV_TXSTART event (but before EV_TXDONE). +// +// +// RX2 +// +// To also use a custom datarate for the RX2 window, call this function +// and set `LMIC.dn2Dr` to CUSTOM_DR. This also causes RXParamSetupReq +// to be rejected, keeping dn2Dr unmodified. +// +// To use a custom rps for RX2 that is different from the uplink and/or +// RX1 (or use a custom rps just for RX2), call this function (again) +// after the EV_TXDONE event (but before RX2 starts). +// +// +// Channel selection as normal +// +// For fixed regions, any enabled (125kHz) channel will be used. +// +// For dynamic regions, any channel that supports CUSTOM_DR will be +// considered. LMIC_setupChannel() can be used normally, to create one +// or more channels enabled for CUSTOM_DR. Since the network can +// potentially disable or reconfigure channels, it is recommended to set +// up these channels again before every transmission. +// +// Disabling CUSTOM_DR +// +// To revert uplink and RX1 back to a normal datarate and allow ADR to +// work again (if enabled), call LMIC_setDrTxpow as normal, passing the +// DR to use. +// +// To revert RX2 back to a normal datarate, just set LMIC.dn2Dr to the +// appropriate datarate directly. +dr_t LMIC_setCustomDr (rps_t custom_rps, dr_t dndr); +void LMIC_setDrTxpow (dr_t dr, s8_t txpow); // set default/start DR/txpow +void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off) +bit_t LMIC_startJoining (void); + +void LMIC_shutdown (void); +void LMIC_init (void); +void LMIC_reset (void); +void LMIC_reset_ex (u8_t regionIdx); +int LMIC_regionIdx (u8_t regionCode); +u8_t LMIC_regionCode (u8_t regionIdx); +void LMIC_clrTxData (void); +void LMIC_setTxData (void); +int LMIC_setTxData2 (u8_t port, u8_t* data, u8_t dlen, u8_t confirmed); +void LMIC_sendAlive (void); + +#if !defined(DISABLE_CLASSB) +u8_t LMIC_enableTracking (u8_t tryBcnInfo); +void LMIC_disableTracking (void); +#endif + +void LMIC_setClassC (u8_t enabled); +#if !defined(DISABLE_CLASSB) +void LMIC_stopPingable (void); +u8_t LMIC_setPingable (u8_t intvExp); +#endif +void LMIC_tryRejoin (void); + +#if !defined(DISABLE_CLASSB) +int LMIC_scan (ostime_t timeout); +int LMIC_track (ostime_t when); +#endif +int LMIC_setMultiCastSession (devaddr_t grpaddr, const u8_t* nwkKeyDn, const u8_t* appKey, u32_t seqnoAdn); + +void LMIC_setSession (u32_t netid, devaddr_t devaddr, const u8_t* nwkKey, +#if defined(CFG_lorawan11) + const u8_t* nwkKeyDn, +#endif + const u8_t* appKey); +void LMIC_setLinkCheckMode (bit_t enabled); +void LMIC_setLinkCheck (u32_t limit, u32_t delay); +void LMIC_askForLinkCheck (void); + +dr_t LMIC_fastestDr (); // fastest UP datarate +dr_t LMIC_slowestDr (); // slowest UP datarate +rps_t LMIC_updr2rps (u8_t dr); +rps_t LMIC_dndr2rps (u8_t dr); +ostime_t LMIC_calcAirTime (rps_t rps, u8_t plen); +u8_t LMIC_maxAppPayload(); +ostime_t LMIC_nextTx (ostime_t now); +void LMIC_disableDC (void); + +// Simulation only APIs +#if defined(CFG_simul) +const char* LMIC_addr2func (void* addr); +int LMIC_arr2len (const char* name); +#endif + +// Declare onEvent() function, to make sure any definition will have the +// C conventions, even when in a C++ file. +DECL_ON_LMIC_EVENT; + +// Special APIs - for development or testing +// !!!See implementation for caveats!!! +#if defined(CFG_extapi) +void LMIC_enableFastJoin (void); +ostime_t LMIC_dr2hsym (dr_t dr, s8_t num); +void LMIC_updateTx (ostime_t now); +void LMIC_getRxdErrInfo (s32_t* skew, u32_t* span); +#endif + + +// Backtrace service support +#ifdef SVC_backtrace +#include "backtrace/backtrace.h" +#else +#define BACKTRACE() +#define TRACE_VAL(v) +#define TRACE_EV(e) +#define TRACE_ADDR(a) +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _lmic_h_ diff --git a/src/BasicMAC/lmic/lorabase.h b/src/BasicMAC/lmic/lorabase.h new file mode 100644 index 0000000..364c8a2 --- /dev/null +++ b/src/BasicMAC/lmic/lorabase.h @@ -0,0 +1,337 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#ifndef _lorabase_h_ +#define _lorabase_h_ + +#ifdef __cplusplus +extern "C"{ +#endif + +enum _cr_t { CR_4_5=0, CR_4_6, CR_4_7, CR_4_8 }; +enum _sf_t { FSK=0, SF7, SF8, SF9, SF10, SF11, SF12, SFrfu }; +enum _bw_t { BW125=0, BW250, BW500, BWrfu }; +typedef u8_t cr_t; +typedef u8_t sf_t; +typedef u8_t bw_t; +typedef u8_t dr_t; +// Radio parameter set (encodes SF/BW/CR/IH/NOCRC) +typedef u16_t rps_t; +typedef u16_t drmap_t; +typedef s32_t freq_t; +typedef s8_t eirp_t; + +enum { ILLEGAL_DR = 0xFF }; +enum { ILLEGAL_RPS = 0xFF }; +enum { ILLEGAL_MAS = 0x00 }; +// DR 15 is used in the LinkADRReq to indicate "no DR changes", so it +// will never be allocated as a standard DR. So it is used here for a +// custom DR. By using 15, rather than 0xFF or similar, it can be used +// in per-channel drmap as normal. +enum { CUSTOM_DR = 0xF }; + +// Global maximum frame length +enum { BCN_PREAMBLE_LEN = 10 }; // length in symbols - actual time depends on DR +enum { STD_PREAMBLE_LEN = 8 }; // -ditto- +enum { MAX_LEN_FRAME =255 }; // in bytes +enum { LEN_DEVNONCE = 2 }; // -ditto- +enum { LEN_JOINNONCE = 3 }; // -ditto- +enum { LEN_NETID = 3 }; // -ditto- +enum { DELAY_JACC1 = 5 }; // in secs +enum { DELAY_DNW1 = 1 }; // in secs down window #1 +enum { DELAY_EXTDNW2 = 1 }; // in secs +enum { DELAY_JACC2 = DELAY_JACC1+(int)DELAY_EXTDNW2 }; // in secs +enum { DELAY_DNW2 = DELAY_DNW1 +(int)DELAY_EXTDNW2 }; // in secs down window #1 +enum { BCN_INTV_exp = 7 }; +enum { BCN_INTV_sec = 1<> 3) & 0x3); } +inline rps_t setBw (rps_t params, bw_t cr) { return (rps_t)((params & ~0x18) | (cr<<3)); } +inline cr_t getCr (rps_t params) { return (cr_t)((params >> 5) & 0x3); } +inline rps_t setCr (rps_t params, cr_t cr) { return (rps_t)((params & ~0x60) | (cr<<5)); } +inline int getNocrc(rps_t params) { return ((params >> 7) & 0x1); } +inline rps_t setNocrc(rps_t params, int nocrc) { return (rps_t)((params & ~0x80) | (nocrc<<7)); } +inline int getIh (rps_t params) { return ((params >> 8) & 0xFF); } +inline rps_t setIh (rps_t params, int ih) { return (rps_t)((params & ~0xFF00) | (ih<<8)); } +inline sf_t isLora (rps_t params) { return getSf(params) >= SF7 && getSf(params) <= SF12; } +inline sf_t isFsk (rps_t params) { return getSf(params) == FSK; } + +// return 1 for low data rate optimize should be enabled (symbol time equal or above 16.384 ms) else 0 +// Must be enabled for: SF11/BW125, SF12/BW125, SF12/BW250 +inline int enDro (rps_t params) { return (int)getSf(params) - getBw(params) >= SF11; } + +// +// BEG: Keep in sync with lorabase.hpp +// ================================================================================ + + +// Convert between dBm values and power codes (MCMD_LADR_XdBm) +s8_t pow2dBm (u8_t mcmd_ladr_p1); +// Calculate airtime +ostime_t calcAirTime (rps_t rps, u8_t plen); +// Sensitivity at given SF/BW +int getSensitivity (rps_t rps); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _lorabase_h_ diff --git a/src/BasicMAC/lmic/oslmic.c b/src/BasicMAC/lmic/oslmic.c new file mode 100644 index 0000000..c24f271 --- /dev/null +++ b/src/BasicMAC/lmic/oslmic.c @@ -0,0 +1,247 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#include "lmic.h" +#include "aes.h" +#include "peripherals.h" + +// RUNTIME STATE +static struct { + osjob_t* scheduledjobs; + unsigned int exact; + union { + u32_t randwrds[4]; + u8_t randbuf[16]; + } /* anonymous */; +} OS; + +void rng_init (void); + +void os_init (void* bootarg) { + memset(&OS, 0x00, sizeof(OS)); + hal_init_ex(bootarg); +#ifndef CFG_noradio + radio_init(false); +#endif + rng_init(); + LMIC_init(); +} + +// return next random byte derived from seed buffer +// (buf[0] holds index of next byte to be returned 1-16) + +void rng_init (void) { +#ifdef PERIPH_TRNG + trng_next(OS.randwrds, 4); +#elif defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + radio_generate_random(OS.randwrds, 4); +#else + memcpy(OS.randbuf, __TIME__, 8); + os_getDevEui(OS.randbuf + 8); +#endif + OS.randbuf[0] = 16; +} + +u8_t os_getRndU1 (void) { + u8_t i = OS.randbuf[0]; + ASSERT(i != 0); + if (i == 16) { + os_aes(AES_ENC, OS.randbuf, 16); // encrypt seed with any key + i = 0; + } + u8_t v = OS.randbuf[i++]; + OS.randbuf[0] = i; + return v; +} + +bit_t os_cca (u16_t rps, u32_t freq) { //XXX:this belongs into os_radio module + (void) rps; (void)freq; // unused + return 0; // never grant access +} + +u8_t os_getBattLevel (void) { + return hal_getBattLevel(); +} + +ostime_t os_getTime () { + return hal_ticks(); +} + +osxtime_t os_getXTime () { + return hal_xticks(); +} + +osxtime_t os_time2XTime (ostime_t t, osxtime_t context) { + return context + ((t - (ostime_t) context)); +} + +// unlink job from queue, return 1 if removed +static int unlinkjob (osjob_t** pnext, osjob_t* job) { + for( ; *pnext; pnext = &((*pnext)->next)) { + if(*pnext == job) { // unlink + *pnext = job->next; + if ((job->flags & OSJOB_FLAG_APPROX) == 0) { + OS.exact -= 1; + } + return 1; + } + } + return 0; +} + +// NOTE: since the job queue might begin with jobs which already have a shortly expired deadline, we cannot use +// the maximum span of ostime to schedule the next job (otherwise it would be queued in first)! +#define XJOBTIME_MAX_DIFF (OSTIME_MAX_DIFF / 2) + +// update schedule of extended job +static void extendedjobcb (osxjob_t* xjob) { + hal_disableIRQs(); + osxtime_t now = os_getXTime(); + if (xjob->deadline - now > XJOBTIME_MAX_DIFF) { + // schedule intermediate callback + os_setTimedCallbackEx((osjob_t*) xjob, (ostime_t) (now + XJOBTIME_MAX_DIFF), (osjobcb_t) extendedjobcb, OSJOB_FLAG_APPROX); + } else { + // schedule final callback + os_setTimedCallbackEx((osjob_t*) xjob, (ostime_t) xjob->deadline, xjob->func, OSJOB_FLAG_APPROX); + } + hal_enableIRQs(); +} + +// schedule job far in the future (deadline may exceed max delta of ostime_t 2^31-1 ticks = 65535.99s = 18.2h) +void os_setExtendedTimedCallback (osxjob_t* xjob, osxtime_t xtime, osjobcb_t cb) { + hal_disableIRQs(); + unlinkjob(&OS.scheduledjobs, (osjob_t*) xjob); + xjob->func = cb; + xjob->deadline = xtime; + extendedjobcb(xjob); + hal_enableIRQs(); +#ifdef DEBUG_JOBS + debug_verbose_printf("Scheduled job %u, cb %u at %t\r\n", (unsigned)xjob, (unsigned)cb, xtime); +#endif // DEBUG_JOBS +} + +// clear scheduled job, return 1 if job was removed +int os_clearCallback (osjob_t* job) { + hal_disableIRQs(); + int r = unlinkjob(&OS.scheduledjobs, job); + hal_enableIRQs(); +#ifdef DEBUG_JOBS + if (r) + debug_verbose_printf("Cleared job %u\r\n", (unsigned)job); +#endif // DEBUG_JOBS + return r; +} + +// schedule timed job +void os_setTimedCallbackEx (osjob_t* job, ostime_t time, osjobcb_t cb, unsigned int flags) { + osjob_t** pnext; + hal_disableIRQs(); + // remove if job was already queued + unlinkjob(&OS.scheduledjobs, job); + // fill-in job + ostime_t now = os_getTime(); + if( flags & OSJOB_FLAG_NOW ) { + time = now; + } else if ( time - now <= 0 ) { + flags |= OSJOB_FLAG_NOW; + } + job->deadline = time; + job->func = cb; + job->next = NULL; + job->flags = flags; + if ((flags & OSJOB_FLAG_APPROX) == 0) { + OS.exact += 1; + } + // insert into schedule + for(pnext=&OS.scheduledjobs; *pnext; pnext=&((*pnext)->next)) { + if((*pnext)->deadline - time > 0) { // (cmp diff, not abs!) + // enqueue before next element and stop + job->next = *pnext; + break; + } + } + *pnext = job; + hal_enableIRQs(); +#ifdef DEBUG_JOBS + if (flags & OSJOB_FLAG_NOW) + debug_verbose_printf("Scheduled job %u, cb %u ASAP\r\n", (unsigned)job, (unsigned)cb); + else + debug_verbose_printf("Scheduled job %u, cb %u%s at %s%t\r\n", (unsigned)job, (unsigned)cb, flags & OSJOB_FLAG_IRQDISABLED ? " (irq disabled)" : "", flags & OSJOB_FLAG_APPROX ? "approx " : "", time); +#endif // DEBUG_JOBS +} + +// execute 1 job from timer or run queue, or sleep if nothing is pending +void os_runstep (void) { + osjob_t* j = NULL; + hal_disableIRQs(); + // check for runnable jobs + if (OS.scheduledjobs) { + //debug_verbose_printf("Sleeping until job %u, cb %u, deadline %t\r\n", (unsigned)OS.scheduledjobs, (unsigned)OS.scheduledjobs->func, (ostime_t)OS.scheduledjobs->deadline); + if (hal_sleep(OS.exact ? HAL_SLEEP_EXACT : HAL_SLEEP_APPROX, OS.scheduledjobs->deadline) == 0) { + j = OS.scheduledjobs; + OS.scheduledjobs = j->next; + if ((j->flags & OSJOB_FLAG_APPROX) == 0) { + OS.exact -= 1; + } + } + } else { // nothing pending + //debug_verbose_printf("Sleeping forever\r\n"); + hal_sleep(HAL_SLEEP_FOREVER, 0); + } + if( j == NULL || (j->flags & OSJOB_FLAG_IRQDISABLED) == 0) { + hal_enableIRQs(); + } + if (j) { // run job callback +#ifdef CFG_warnjobs + // warn about late execution of precisely timed jobs + ostime_t delta = 0; + if ( (j->flags & (OSJOB_FLAG_NOW | OSJOB_FLAG_APPROX) ) == 0) { + delta = os_getTime() - j->deadline; + } +#endif + // Only print when interrupts are enabled, some Arduino cores do + // not handle printing with IRQs disabled + if( (j->flags & OSJOB_FLAG_IRQDISABLED) == 0) { +#ifdef DEBUG_JOBS + debug_verbose_printf("Running job %u, cb %u, deadline %t\r\n", (unsigned)j, (unsigned)j->func, (ostime_t)j->deadline); +#endif // DEBUG_JOBS +#ifdef CFG_warnjobs + if ( delta > 1 ) { + debug_printf("WARNING: job 0x%08x (func 0x%08x) executed %d ticks late\r\n", j, j->func, delta); + } +#endif + } + hal_watchcount(30); // max 60 sec + j->func(j); + hal_watchcount(0); + // If we could not print before, at least print after + if( (j->flags & OSJOB_FLAG_IRQDISABLED) != 0) { +#ifdef DEBUG_JOBS + debug_verbose_printf("Ran job %u, cb %u, deadline %F\r\n", (unsigned)j, (unsigned)j->func, (ostime_t)j->deadline, 0); +#endif // DEBUG_JOBS +#ifdef CFG_warnjobs + if ( delta > 1 ) { + debug_printf("WARNING: job 0x%08x (func 0x%08x) executed %d ticks late\r\n", j, j->func, delta); + } +#endif + } + } +} + +// execute jobs from timer and from run queue +void os_runloop (void) { + while (1) { + os_runstep(); + } +} + +static u8_t evcatEn = 0xFF; + +void os_logEv (uint8_t evcat, uint8_t evid, uint32_t evparam) { + if( evcat >= EVCAT_MAX && evcat < sizeof(evcatEn)*8 && (evcatEn & (1< +#include +typedef uint8_t bit_t; +typedef uint8_t u8_t; +typedef int8_t s8_t; +typedef uint16_t u16_t; +typedef int16_t s16_t; +typedef uint32_t u32_t; +typedef int32_t s32_t; +typedef uint64_t u64_t; +typedef int64_t s64_t; +typedef unsigned int uint; +typedef const char* str_t; + +#include +#if !defined(CFG_simul) +#include "debug.h" +#endif +#if !defined(CFG_noassert) +#if defined(CFG_simul) && defined(CFG_DEBUG) +#include +#define ASSERT(cond) do { \ + if(!(cond)) { fprintf(stderr, "ASSERTION FAILED: %s at %s:%d\n", \ + #cond, __FILE__, __LINE__); hal_failed(); } } while (0) +#elif defined(CFG_DEBUG) +#define ASSERT(cond) do { if(!(cond)) { hal_enableIRQs(); debug_printf("%s:%d: assertion failed\r\n", __FILE__, __LINE__); hal_failed(); } } while (0) +#else +#define ASSERT(cond) do { if(!(cond)) hal_failed(); } while (0) +#endif +#else +#define ASSERT(cond) do { } while (0) +#endif + +#include +#if __cplusplus >= 201103L || defined(static_assert) +#define LMIC_STATIC_ASSERT static_assert +#else +#define LMIC_STATIC_ASSERT(expr, msg) +#endif + +#ifdef __cplusplus +extern "C"{ +#endif + +#define os_max(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define os_min(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) + +#define os_minmax(vmin,v,vmax) (os_max(vmin,os_min(v,vmax))) +#define os_clearMem(a,b) memset(a,0,b) +#define os_copyMem(a,b,c) memcpy(a,b,c) +#define os_moveMem(a,b,c) memmove(a,b,c) + +#define ON_LMIC_EVENT(ev) onLmicEvent(ev) +#define DECL_ON_LMIC_EVENT void onLmicEvent(ev_t e) + +#define ON_BUDHA_EVENT(ev) onBudhaEvent(ev) +#define DECL_ON_BUDHA_EVENT void onBudhaEvent(ev_t e) + +#if defined(CFG_bootloader) && defined(CFG_bootloader_aes) +extern uint32_t (*AESFUNC) (uint8_t mode, uint8_t* buf, uint16_t len, uint32_t* key, uint32_t* aux); +#endif +extern u32_t AESAUX[]; +extern u32_t AESKEY[]; +#define AESkey ((u8_t*)AESKEY) +#define AESaux ((u8_t*)AESAUX) +#define FUNC_ADDR(func) (&(func)) + +#if defined(CFG_simul) +#define DEFINE_LMIC +#define DECLARE_LMIC extern struct lmic_t* plmic +#define LMIC (*(plmic)) +#else +#define DEFINE_LMIC struct lmic_t LMIC +#define DECLARE_LMIC extern struct lmic_t LMIC +#endif + +#if defined(CFG_budha) +#if defined(CFG_simul) +#define DEFINE_BUDHA +#define DECLARE_BUDHA extern struct budha_t* pbudha +#define BUDHA (*(pbudha)) +#else +#define DEFINE_BUDHA struct lmic_t BUDHA +#define DECLARE_BUDHA extern struct budha_t BUDHA +#endif +#endif //defined(CFG_budha) + +#if defined(CFG_dse) +#if defined(CFG_simul) +#define DEFINE_DSE +#define DECLARE_DSE extern struct dse_t* pdse +#define DSE (*(pdse)) +#else +#define DEFINE_DSE struct dse_t DSE +#define DECLARE_DSE extern struct dse_t DSE +#endif +#endif //defined(CFG_dse) + +#if defined(CFG_rose) +#if defined(CFG_simul) +#define DEFINE_ROSE +#define DECLARE_ROSE extern struct rose_t* prose +#define ROSE (*(prose)) +#else +#define DEFINE_ROSE struct rose_t ROSE +#define DECLARE_ROSE extern struct rose_t ROSE +#endif +#endif //defined(CFG_rose) + +#if defined(CFG_simul) +#define DEFINE_APP(t) +#define DECLARE_APP(t) extern void* pappdata +#define APP(t) (*(struct t*)pappdata) +#else +#define DEFINE_APP(t) struct t APPDATA +#define DECLARE_APP(t) extern struct t APPDATA +#define APP(t) (APPDATA) +#endif + +#define LOGCHECK(lvl,block) do { \ + if( lvl <= log_lvl ) { \ + block; \ + } \ + } while(0) +#if defined(CFG_simul) +extern int log_lvl; +void LOGIT(int lvl, char* fmt, ...); +#else +#define LOGIT(lvl, fmt, ...) debug_printf(fmt, ## __VA_ARGS__) +#endif +enum { EVCAT_ANY, EVCAT_BUDHA, EVCAT_MAX }; +void os_logEv(uint8_t evcat, uint8_t evid, uint32_t evparam); + +void os_init (void* bootarg); +void os_runstep (void); +void os_runloop (void); +u8_t os_getRndU1 (void); + +//================================================================================ + +#ifndef RX_RAMPUP +#ifndef CFG_rxrampup +#if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) +#define RX_RAMPUP (us2osticks(5000)) +#elif defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) +#define RX_RAMPUP (us2osticksCeil(2800)) +#else +#define RX_RAMPUP (0) +#endif +#else +#define RX_RAMPUP (us2osticksCeil(CFG_rxrampup)) +#endif +#endif +#ifndef TX_RAMPUP +#ifndef CFG_txrampup +#define TX_RAMPUP (us2osticks(2000)) +#else +#define TX_RAMPUP (us2osticksCeil(CFG_txrampup)) +#endif +#endif + +#ifndef OSTICKS_PER_SEC +#define OSTICKS_PER_SEC 32768 +#elif OSTICKS_PER_SEC < 10000 || OSTICKS_PER_SEC > 64516 +#error Illegal OSTICKS_PER_SEC - must be in range [10000:64516]. One tick must be 15.5us .. 100us long. +#endif + +typedef s32_t ostime_t; +typedef s64_t osxtime_t; + +#define OSXTIME_MAX INT64_MAX +#define OSTIME_MAX_DIFF INT32_MAX + +#if !HAS_ostick_conv +#define us2osticks(us) ((ostime_t)( ((s64_t)(us) * OSTICKS_PER_SEC) / 1000000)) +#define ms2osticks(ms) ((ostime_t)( ((s64_t)(ms) * OSTICKS_PER_SEC) / 1000)) +#define sec2osticks(sec) ((ostime_t)( (s64_t)(sec) * OSTICKS_PER_SEC)) +#define osticks2sec(os) ((s32_t)(((os) ) / OSTICKS_PER_SEC)) +#define osticks2ms(os) ((s32_t)(((os)*(s64_t)1000 ) / OSTICKS_PER_SEC)) +#define osticks2us(os) ((s32_t)(((os)*(s64_t)1000000 ) / OSTICKS_PER_SEC)) +// Special versions +#define us2osticksCeil(us) ((ostime_t)( ((s64_t)(us) * OSTICKS_PER_SEC + 999999) / 1000000)) +#define us2osticksRound(us) ((ostime_t)( ((s64_t)(us) * OSTICKS_PER_SEC + 500000) / 1000000)) +#define ms2osticksCeil(ms) ((ostime_t)( ((s64_t)(ms) * OSTICKS_PER_SEC + 999) / 1000)) +#define ms2osticksRound(ms) ((ostime_t)( ((s64_t)(ms) * OSTICKS_PER_SEC + 500) / 1000)) +#define osticks2secCeil(os) ((s32_t)(((os) + (OSTICKS_PER_SEC - 1)) / OSTICKS_PER_SEC)) +// Extended versions +#define us2osxticks(us) ((osxtime_t)( ((s64_t)(us) * OSTICKS_PER_SEC) / 1000000)) +#define ms2osxticks(ms) ((osxtime_t)( ((s64_t)(ms) * OSTICKS_PER_SEC) / 1000)) +#define sec2osxticks(sec) ((osxtime_t)( (s64_t)(sec) * OSTICKS_PER_SEC)) +#endif + +struct osjob_t; // fwd decl +typedef void (*osjobcb_t) (struct osjob_t*); +typedef struct osjob_t { + struct osjob_t* next; + ostime_t deadline; + osjobcb_t func; + unsigned int flags; +#if defined(CFG_simul) + void* ctx; + int pqidx; +#endif +} osjob_t; + +// extended os job wrapper for future events exceeding max range of ostime_t +typedef struct osxjob_t osxjob_t; +struct osxjob_t { + osjob_t job; + osxtime_t deadline; + osjobcb_t func; +}; + +#include "hal.h" + +#ifndef HAS_os_calls + +#ifndef os_getNwkKey +void os_getNwkKey (u8_t* buf); +#endif +#ifndef os_getAppKey +void os_getAppKey (u8_t* buf); +#endif +#ifndef os_getJoinEui +void os_getJoinEui (u8_t* buf); +#endif +#ifndef os_getDevEui +void os_getDevEui (u8_t* buf); +#endif +#ifndef os_getRegion +u8_t os_getRegion (void); +#endif +#ifndef os_setTimedCallbackEx +enum { + OSJOB_FLAG_APPROX = (1 << 0), // actual time of job may be approximate + OSJOB_FLAG_IRQDISABLED = (1 << 1), // IRQs will be disabled when job is run -- THE JOB MUST RE-ENABLE IRQs BY CALLING hal_enableIRQs() !!! + OSJOB_FLAG_NOW = (1 << 2), // job is immediately runnable (time parameter is ignored) +}; +void os_setTimedCallbackEx (osjob_t* job, ostime_t time, osjobcb_t cb, unsigned int flags); +void os_setExtendedTimedCallback (osxjob_t* xjob, osxtime_t xtime, osjobcb_t cb); +// convenience functions (implemented as macros) +#define os_setCallback(job, cb) os_setTimedCallbackEx(job, 0, cb, OSJOB_FLAG_NOW) +#define os_setTimedCallback(job, time, cb) os_setTimedCallbackEx(job, time, cb, 0) +#define os_setApproxTimedCallback(job, time, cb) os_setTimedCallbackEx(job, time, cb, OSJOB_FLAG_APPROX) +#define os_setProtectedTimedCallback(job, time, cb) os_setTimedCallbackEx(job, time, cb, OSJOB_FLAG_IRQDISABLED) +#endif +#ifndef os_clearCallback +int os_clearCallback (osjob_t* job); +#endif +#ifndef os_getTime +ostime_t os_getTime (void); +#endif +#ifndef os_getXTime +osxtime_t os_getXTime (void); +#endif +#ifndef os_time2XTime +osxtime_t os_time2XTime (ostime_t t, osxtime_t context); +#endif +#ifndef os_getTimeSecs +uint os_getTimeSecs (void); +#endif +#ifndef os_radio +void os_radio (u8_t mode); +#endif +#ifndef os_getBattLevel +u8_t os_getBattLevel (void); +#endif + +#ifndef os_rlsbf4 +//! Read 32-bit quantity from given pointer in little endian byte order. +u32_t os_rlsbf4 (const u8_t* buf); +#endif +#ifndef os_wlsbf4 +//! Write 32-bit quantity into buffer in little endian byte order. +void os_wlsbf4 (u8_t* buf, u32_t value); +#endif +#ifndef os_rmsbf4 +//! Read 32-bit quantity from given pointer in big endian byte order. +u32_t os_rmsbf4 (const u8_t* buf); +#endif +#ifndef os_wmsbf4 +//! Write 32-bit quantity into buffer in big endian byte order. +void os_wmsbf4 (u8_t* buf, u32_t value); +#endif +#ifndef os_rlsbf2 +//! Read 16-bit quantity from given pointer in little endian byte order. +u16_t os_rlsbf2 (const u8_t* buf); +#endif +#ifndef os_wlsbf2 +//! Write 16-bit quantity into buffer in little endian byte order. +void os_wlsbf2 (u8_t* buf, u16_t value); +#endif +#ifndef os_rmsbf2 +//! Read 16-bit quantity from given pointer in big endian byte order. +u16_t os_rmsbf2 (const u8_t* buf); +#endif +#ifndef os_wmsbf2 +//! Write 16-bit quantity into buffer in big endian byte order. +void os_wmsbf2 (u8_t* buf, u16_t value); +#endif +#ifndef os_wlsbf3 +//! Write 24-bit quantity into buffer in little endian byte order. +void os_wlsbf3 (u8_t* buf, u32_t value); +#endif + +//! Get random number (default impl for u16_t). +#ifndef os_getRndU2 +#define os_getRndU2() ((u16_t)((os_getRndU1()<<8)|os_getRndU1())) +#endif +#ifndef os_crc16 +u16_t os_crc16 (u8_t* d, uint len); +#endif + +#if defined(CFG_budha) // XXX:obsoleted by budha2 (service) +// HAL support required by BUDHA: +u8_t* os_getWSleepParams(void); +void os_setWSleepParams(u8_t* params); +int os_checkSignature(const u8_t* buf, u8_t len); +u32_t os_getFWCRC(void); +int os_usedWakeNonce(u16_t nonce); +void os_commitWakeNonce(u16_t nonce); +enum { BOOT_WSLEEP=0, BOOT_OS }; +void os_setBootMode(u8_t mode); +void os_boot(void); +s16_t os_fwChunk(u8_t* p, u8_t len); +#endif // defined(CFG_budha) + +#endif // !HAS_os_calls + +// public radio functions +void radio_irq_handler (u8_t dio, ostime_t ticks); // (used by EXTI_IRQHandler) +void radio_init (bool calibrate); // (used by os_init()) +void radio_writeBuf (u8_t addr, u8_t* buf, u8_t len); // (used by perso) +void radio_readBuf (u8_t addr, u8_t* buf, u8_t len); // (used by perso) +void radio_set_irq_timeout (ostime_t timeout); + +// radio-specific functions +bool radio_irq_process (ostime_t irqtime, u8_t diomask); +void radio_starttx (bool txcontinuous); +void radio_startrx (bool rxcontinuous); +void radio_sleep (void); +void radio_cca (void); +void radio_cad (void); +void radio_cw (void); +void radio_generate_random (u32_t *words, u8_t len); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _oslmic_h_ diff --git a/src/BasicMAC/lmic/peripherals.h b/src/BasicMAC/lmic/peripherals.h new file mode 100644 index 0000000..38fecff --- /dev/null +++ b/src/BasicMAC/lmic/peripherals.h @@ -0,0 +1,121 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#ifndef _peripherals_h_ +#define _peripherals_h_ + +#include "hw.h" // provided by HAL + + +#ifdef PERIPH_EEPROM +// ------------------------------------------------ +// EEPROM + +void eeprom_write (void* dest, unsigned int val); +void eeprom_copy (void* dest, const void* src, int len); + +#endif + +#ifdef PERIPH_FLASH +// ------------------------------------------------ +// Flash + +void flash_write (void* dst, const void* src, unsigned int nwords, bool erase); + +#endif + +#ifdef PERIPH_USART +// ------------------------------------------------ +// USART + +enum { + USART_CONTINUE = 0, + USART_DONE = -1, + USART_ERROR = -1, +}; + +typedef int (*usart_rx_func) (int ch, void* arg); +typedef int (*usart_tx_func) (int status, void* arg); +void usart_cfg (unsigned int br); +void usart_recv (usart_rx_func rx, void* arg); +void usart_send (usart_tx_func tx, void* arg); +void usart_abort_recv (void); + +#endif + + +#ifdef PERIPH_PIO +// ------------------------------------------------ +// Programmable I/O + +enum { + PIO_INP_HIZ = -1, // ..111b + PIO_INP_PUP = -2, // ..110b + PIO_INP_PDN = -3, // ..101b + PIO_INP_ANA = -5, // ..011b +}; + +void pio_set (unsigned int pin, int value); +int pio_get (unsigned int pin); + +#endif + + +#ifdef PERIPH_CRC +// ------------------------------------------------ +// CRC engine (32bit aligned words only) + +unsigned int crc32 (void* ptr, int nwords); + +#endif + +#ifdef PERIPH_SHA256 +// ------------------------------------------------ +// SHA-256 engine + +void sha256 (uint32_t* hash, const uint8_t* msg, uint32_t len); + +#endif + + +#ifdef PERIPH_TRNG +// ------------------------------------------------ +// True RNG engine + +void trng_next (uint32_t* dest, int count); + +#endif + + +#ifdef PERIPH_I2C +// ------------------------------------------------ +// I²C perpipheral + +enum { + I2C_BUSY = 1, + I2C_OK = 0, + I2C_NAK = -1, + I2C_ABORT = -2, +}; + +typedef void (*i2c_cb) (int status); +void i2c_xfer (unsigned int addr, unsigned char* buf, unsigned int wlen, unsigned int rlen, + i2c_cb cb, ostime_t timeout); +void i2c_xfer_ex (unsigned int addr, unsigned char* buf, unsigned int wlen, unsigned int rlen, + ostime_t timeout, osjob_t* job, osjobcb_t cb, int* pstatus); +void i2c_abort (void); + +#endif + + +#ifdef PERIPH_ADC +// ------------------------------------------------ +// Analog-to-Digital Converter + +unsigned int adc_read (unsigned int chnl, unsigned int rate); + +#endif + +#endif diff --git a/src/BasicMAC/lmic/radio-sx126x.c b/src/BasicMAC/lmic/radio-sx126x.c new file mode 100644 index 0000000..b62d064 --- /dev/null +++ b/src/BasicMAC/lmic/radio-sx126x.c @@ -0,0 +1,906 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#include "board.h" +#include "hw.h" +#include "lmic.h" + +#if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + +// ---------------------------------------- +// Commands Selecting the Operating Modes of the Radio +#define CMD_SETSLEEP 0x84 +#define CMD_SETSTANDBY 0x80 +#define CMD_SETFS 0xC1 +#define CMD_SETTX 0x83 +#define CMD_SETRX 0x82 +#define CMD_STOPTIMERONPREAMBLE 0x9F +#define CMD_SETRXDUTYCYCLE 0x94 +#define CMD_SETCAD 0xC5 +#define CMD_SETTXCONTINUOUSWAVE 0xD1 +#define CMD_SETTXINFINITEPREAMBLE 0xD2 +#define CMD_SETREGULATORMODE 0x96 +#define CMD_CALIBRATE 0x89 +#define CMD_CALIBRATEIMAGE 0x98 +#define CMD_SETPACONFIG 0x95 +#define CMD_SETRXTXFALLBACKMODE 0x93 + +// Commands to Access the Radio Registers and FIFO Buffer +#define CMD_WRITEREGISTER 0x0D +#define CMD_READREGISTER 0x1D +#define CMD_WRITEBUFFER 0x0E +#define CMD_READBUFFER 0x1E + +// Commands Controlling the Radio IRQs and DIOs +#define CMD_SETDIOIRQPARAMS 0x08 +#define CMD_GETIRQSTATUS 0x12 +#define CMD_CLEARIRQSTATUS 0x02 +#define CMD_SETDIO2ASRFSWITCHCTRL 0x9D +#define CMD_SETDIO3ASTCXOCTRL 0x97 + +// Commands Controlling the RF and Packets Settings +#define CMD_SETRFFREQUENCY 0x86 +#define CMD_SETPACKETTYPE 0x8A +#define CMD_GETPACKETTYPE 0x11 +#define CMD_SETTXPARAMS 0x8E +#define CMD_SETMODULATIONPARAMS 0x8B +#define CMD_SETPACKETPARAMS 0x8C +#define CMD_SETCADPARAMS 0x88 +#define CMD_SETBUFFERBASEADDRESS 0x8F +#define CMD_SETLORASYMBNUMTIMEOUT 0xA0 + +// Commands Returning the Radio Status +#define CMD_GETSTATUS 0xC0 +#define CMD_GETRSSIINST 0x15 +#define CMD_GETRXBUFFERSTATUS 0x13 +#define CMD_GETPACKETSTATUS 0x14 +#define CMD_GETDEVICEERRORS 0x17 +#define CMD_CLEARDEVICEERRORS 0x07 +#define CMD_GETSTATS 0x10 +#define CMD_RESETSTATS 0x00 + + +// ---------------------------------------- +// List of Registers + +#define REG_WHITENINGMSB 0x06B8 +#define REG_WHITENINGLSB 0x06B9 +#define REG_CRCINITVALMSB 0x06BC +#define REG_CRCINITVALLSB 0x06BD +#define REG_CRCPOLYVALMSB 0x06BE +#define REG_CRCPOLYVALLSB 0x06BF +#define REG_SYNCWORD0 0x06C0 +#define REG_SYNCWORD1 0x06C1 +#define REG_SYNCWORD2 0x06C2 +#define REG_SYNCWORD3 0x06C3 +#define REG_SYNCWORD4 0x06C4 +#define REG_SYNCWORD5 0x06C5 +#define REG_SYNCWORD6 0x06C6 +#define REG_SYNCWORD7 0x06C7 +#define REG_NODEADDRESS 0x06CD +#define REG_BROADCASTADDR 0x06CE +#define REG_LORASYNCWORDMSB 0x0740 +#define REG_LORASYNCWORDLSB 0x0741 +#define REG_RANDOMNUMBERGEN0 0x0819 +#define REG_RANDOMNUMBERGEN1 0x081A +#define REG_RANDOMNUMBERGEN2 0x081B +#define REG_RANDOMNUMBERGEN3 0x081C +#define REG_RXGAIN 0x08AC +#define REG_OCPCONFIG 0x08E7 +#define REG_XTATRIM 0x0911 +#define REG_XTBTRIM 0x0912 + +// sleep modes +#define SLEEP_COLD 0x00 // (no rtc timeout) +#define SLEEP_WARM 0x04 // (no rtc timeout) + +// standby modes +#define STDBY_RC 0x00 +#define STDBY_XOSC 0x01 + +// regulator modes +#define REGMODE_LDO 0x00 +#define REGMODE_DCDC 0x01 + +// packet types +#define PACKET_TYPE_FSK 0x00 +#define PACKET_TYPE_LORA 0x01 + +// crc types +#define CRC_OFF 0x01 +#define CRC_1_BYTE 0x00 +#define CRC_2_BYTE 0x02 +#define CRC_1_BYTE_INV 0x04 +#define CRC_2_BYTE_INV 0x06 + +// irq types +#define IRQ_TXDONE (1 << 0) +#define IRQ_RXDONE (1 << 1) +#define IRQ_PREAMBLEDETECTED (1 << 2) +#define IRQ_SYNCWORDVALID (1 << 3) +#define IRQ_HEADERVALID (1 << 4) +#define IRQ_HEADERERR (1 << 5) +#define IRQ_CRCERR (1 << 6) +#define IRQ_CADDONE (1 << 7) +#define IRQ_CADDETECTED (1 << 8) +#define IRQ_TIMEOUT (1 << 9) +#define IRQ_ALL 0x3FF + +// TCXO voltages (limited to VDD - 200mV) +#define TCXO_VOLTAGE1_6V 0x00 +#define TCXO_VOLTAGE1_7V 0x01 +#define TCXO_VOLTAGE1_8V 0x02 +#define TCXO_VOLTAGE2_2V 0x03 +#define TCXO_VOLTAGE2_4V 0x04 +#define TCXO_VOLTAGE2_7V 0x05 +#define TCXO_VOLTAGE3_0V 0x06 +#define TCXO_VOLTAGE3_3V 0x07 + +// XXX: These should probably be configurable +// XXX: The startup time delays TX/RX by 320*15.625=5ms, maybe switch on +// TCXO early? +#define TCXO_VOLTAGE TCXO_VOLTAGE1_7V +#define TCXO_STARTUP_TIME 320 // In multiples of 15.625μs + +#define LORA_TXDONE_FIXUP us2osticks(269) // determined by lwtestapp using device pin wired to sx1301 pps... +#define FSK_TXDONE_FIXUP us2osticks(0) // XXX +#define FSK_RXDONE_FIXUP us2osticks(0) // XXX + +// XXX +static const u16_t LORA_RXDONE_FIXUP_125[] = { + [FSK] = us2osticks(0), + [SF7] = us2osticks(0), + [SF8] = us2osticks(1648), + [SF9] = us2osticks(3265), + [SF10] = us2osticks(7049), + [SF11] = us2osticks(13641), + [SF12] = us2osticks(31189), +}; + +static const u16_t LORA_RXDONE_FIXUP_500[] = { + [FSK] = us2osticks( 0), + [SF7] = us2osticks( 0), + [SF8] = us2osticks( 0), + [SF9] = us2osticks( 0), + [SF10] = us2osticks( 0), + [SF11] = us2osticks( 0), + [SF12] = us2osticks( 0), +}; + +// radio state +static struct { + unsigned int sleeping:1; +} state; + +// ---------------------------------------- + +static void writecmd (uint8_t cmd, const uint8_t* data, uint8_t len) { + hal_spi_select(1); + hal_pin_busy_wait(); + state.sleeping = 0; + hal_spi(cmd); + for (u8_t i = 0; i < len; i++) { + hal_spi(data[i]); + } + hal_spi_select(0); + // busy line will go high after max 600ns + // eventually during a subsequent hal_spi_select(1)... +} + +static void WriteRegs (uint16_t addr, const uint8_t* data, uint8_t len) { + hal_spi_select(1); + hal_pin_busy_wait(); + state.sleeping = 0; + hal_spi(CMD_WRITEREGISTER); + hal_spi(addr >> 8); + hal_spi(addr); + for (uint8_t i = 0; i < len; i++) { + hal_spi(data[i]); + } + hal_spi_select(0); +} + +static void WriteReg (uint16_t addr, uint8_t val) __attribute__((__unused__)); // Ok if this is unused +static void WriteReg (uint16_t addr, uint8_t val) { + WriteRegs(addr, &val, 1); +} + +static void WriteBuffer (uint8_t off, const uint8_t* data, uint8_t len) { + hal_spi_select(1); + hal_pin_busy_wait(); + state.sleeping = 0; + hal_spi(CMD_WRITEBUFFER); + hal_spi(off); + for (uint8_t i = 0; i < len; i++) { + hal_spi(data[i]); + } + hal_spi_select(0); +} + +static uint8_t readcmd (uint8_t cmd, uint8_t* data, uint8_t len) { + hal_spi_select(1); + hal_pin_busy_wait(); + state.sleeping = 0; + hal_spi(cmd); + uint8_t stat = hal_spi(0x00); + for (u8_t i = 0; i < len; i++) { + data[i] = hal_spi(0x00); + } + hal_spi_select(0); + return stat; +} + +static void ReadRegs (uint16_t addr, uint8_t* data, uint8_t len) { + hal_spi_select(1); + hal_pin_busy_wait(); + state.sleeping = 0; + hal_spi(CMD_READREGISTER); + hal_spi(addr >> 8); + hal_spi(addr); + hal_spi(0x00); // NOP + for (uint8_t i = 0; i < len; i++) { + data[i] = hal_spi(0x00); + } + hal_spi_select(0); +} + +static uint8_t ReadReg (uint16_t addr) { + uint8_t val; + ReadRegs(addr, &val, 1); + return val; +} + +static void ReadBuffer (uint8_t off, uint8_t* data, uint8_t len) { + hal_spi_select(1); + hal_pin_busy_wait(); + state.sleeping = 0; + hal_spi(CMD_READBUFFER); + hal_spi(off); + hal_spi(0x00); // NOP + for (uint8_t i = 0; i < len; i++) { + data[i] = hal_spi(0x00); + } + hal_spi_select(0); +} + +// set sleep mode SLEEP_COLD or SLEEP_WARM (from standby mode) +static void SetSleep (uint8_t cfg) { + writecmd(CMD_SETSLEEP, &cfg, 1); +} + +// set standby mode STDBY_RC or STANDBY_XOSC +static void SetStandby (uint8_t cfg) { + writecmd(CMD_SETSTANDBY, &cfg, 1); +} + +// set regulator mode REGMODE_LDO or REGMODE_DCDC +static void SetRegulatorMode (uint8_t mode) { + writecmd(CMD_SETREGULATORMODE, &mode, 1); +} + +// use DIO2 to drive antenna rf switch +static void SetDIO2AsRfSwitchCtrl (uint8_t enable) { + writecmd(CMD_SETDIO2ASRFSWITCHCTRL, &enable, 1); +} + +// use DIO3 to drive crystal enable switch +static void SetDIO3AsTcxoCtrl () { + uint32_t timeout = TCXO_STARTUP_TIME; + uint8_t voltage = TCXO_VOLTAGE1_7V; + uint8_t data[] = {voltage, (timeout >> 16) & 0xff, (timeout >> 8) & 0xff, timeout & 0xff }; + + writecmd(CMD_SETDIO3ASTCXOCTRL, data, sizeof(data)); +} + +// write payload to fifo buffer at offset 0 +static void WriteFifo (uint8_t *buf, uint8_t len) { + static const uint8_t txrxbase[] = { 0, 0 }; + writecmd(CMD_SETBUFFERBASEADDRESS, txrxbase, 2); + + WriteBuffer(0, buf, len); +} + +// read payload from fifo, return length +static uint8_t ReadFifo (uint8_t *buf) { + // get buffer status + uint8_t status[2]; + readcmd(CMD_GETRXBUFFERSTATUS, status, 2); + + // read buffer + uint8_t len = status[0]; + uint8_t off = status[1]; + ReadBuffer(off, buf, len); + + // return length + return len; +} + +// set radio in transmit mode (abort after timeout [1/64ms]) +static void SetTx (uint32_t timeout64ms) { + uint8_t timeout[3] = { timeout64ms >> 16, timeout64ms >> 8, timeout64ms }; + writecmd(CMD_SETTX, timeout, 3); +} + +// generate continuous (indefinite) wave +static void SetTxContinuousWave (void) { + writecmd(CMD_SETTXCONTINUOUSWAVE, NULL, 0); +} + +// set radio in receive mode (abort after timeout [1/64ms], or with timeout=0 after frame received, or continuous with timeout=FFFFFF) +static void SetRx (uint32_t timeout64ms) { + uint8_t timeout[3] = { timeout64ms >> 16, timeout64ms >> 8, timeout64ms }; + writecmd(CMD_SETRX, timeout, 3); +} + +// set radio in frequency synthesis mode +static void SetFs (void) { + writecmd(CMD_SETFS, NULL, 0); +} + +// set radio to PACKET_TYPE_LORA or PACKET_TYPE_FSK mode +static void SetPacketType (uint8_t type) { + writecmd(CMD_SETPACKETTYPE, &type, 1); +} + +// calibrate the image rejection +static void CalibrateImage (uint32_t freq) { + static const struct { + uint32_t min; + uint32_t max; + uint8_t freq[2]; + } bands[] = { + { 430000000, 440000000, { 0x6B, 0x6F } }, + { 470000000, 510000000, { 0x75, 0x81 } }, + { 779000000, 787000000, { 0xC1, 0xC5 } }, + { 863000000, 870000000, { 0xD7, 0xDB } }, + { 902000000, 928000000, { 0xE1, 0xE9 } }, + }; + for (size_t i = 0; i < sizeof(bands) / sizeof(bands[0]); i++) { + if (freq >= bands[i].min && freq <= bands[i].max) { + writecmd(CMD_CALIBRATEIMAGE, bands[i].freq, 2); + } + } +} + +// set rf frequency (in Hz) +static void SetRfFrequency (uint32_t freq) { + // set frequency + uint8_t buf[4]; + os_wmsbf4(buf, (uint32_t) (((uint64_t) freq << 25) / 32000000)); + writecmd(CMD_SETRFFREQUENCY, buf, 4); +} + +// configure modulation parameters for LoRa +static void SetModulationParamsLora (u16_t rps) { + uint8_t param[4]; + param[0] = getSf(rps) - SF7 + 7; // SF (sf7 -> 7) + param[1] = getBw(rps) - BW125 + 4; // BW (bw125 -> 4) + param[2] = getCr(rps) - CR_4_5 + 1; // CR (cr45 -> 1) + param[3] = enDro(rps); // low-data-rate-opt (symbol time equal or above 16.38 ms) + writecmd(CMD_SETMODULATIONPARAMS, param, 4); +} + +// configure modulation parameters for FSK +static void SetModulationParamsFsk (void) { + uint8_t param[8]; + param[0] = 0x00; // bitrate 50kbps (32 * fxtal / bitrate = 32 * 32000000 / 50000 = 0x005000) + param[1] = 0x50; + param[2] = 0x00; + param[3] = 0x09; // TX pulse shape filter gaussian BT 0.5 + param[4] = 0x0B; // RX bandwidth 117.3kHz DSB + param[5] = 0x00; // TX frequency deviation 25kHz (deviation * 2^25 / fxtal = 25000 * 2^25 / 32000000 = 0x006666) + param[6] = 0x66; + param[7] = 0x66; + writecmd(CMD_SETMODULATIONPARAMS, param, 8); +} + +// configure packet handling for LoRa +static void SetPacketParamsLora (u16_t rps, int len, int inv) { + uint8_t param[6]; + param[0] = 0x00; // 8 symbols preamble + param[1] = 0x08; + param[2] = getIh(rps); // implicit header + param[3] = len; + param[4] = !getNocrc(rps); + param[5] = inv; // I/Q inversion + writecmd(CMD_SETPACKETPARAMS, param, 6); +} + +// configure packet handling for FSK +static void SetPacketParamsFsk (u16_t rps, int len) { + uint8_t param[9]; + param[0] = 0x00; // TX preamble length 40 bits / 5 bytes + param[1] = 0x28; + param[2] = 0x05; // RX preamble detector length 16 bits + param[3] = 0x18; // sync word length 24 bits / 3 bytes + param[4] = 0x00; // node address filtering disabled + param[5] = 0x01; // variable size packets, length is included + param[6] = len; // payload length + param[7] = getNocrc(rps) ? CRC_OFF : CRC_2_BYTE_INV; // off or CCITT + param[8] = 0x01; // whitening enabled + writecmd(CMD_SETPACKETPARAMS, param, 9); +} + +// clear irq register +static void ClearIrqStatus (uint16_t mask) { + uint8_t buf[2] = { mask >> 8, mask & 0xFF }; + writecmd(CMD_CLEARIRQSTATUS, buf, 2); +} + +// stop timer on preamble detection or header/syncword detection +static void StopTimerOnPreamble (uint8_t enable) { + writecmd(CMD_STOPTIMERONPREAMBLE, &enable, 1); +} + +// set number of symbols for reception +static void SetLoRaSymbNumTimeout (uint8_t nsym) { + writecmd(CMD_SETLORASYMBNUMTIMEOUT, &nsym, 1); +} + +// return irq register +static uint16_t GetIrqStatus (void) { + uint8_t buf[2]; + readcmd(CMD_GETIRQSTATUS, buf, 2); + return (buf[0] << 8) | buf[1]; +} + +// get signal quality of received packet for LoRa +static void GetPacketStatusLora (s8_t *rssi, s8_t *snr) { + uint8_t buf[3]; + readcmd(CMD_GETPACKETSTATUS, buf, 3); + *rssi = -buf[0] / 2 + RSSI_OFF; + *snr = buf[1] * SNR_SCALEUP / 4; +} + +// get signal quality of received packet for FSK +static s8_t GetPacketStatusFsk (void) { + uint8_t buf[3]; + readcmd(CMD_GETPACKETSTATUS, buf, 3); + return -buf[2] / 2 + RSSI_OFF; // RssiAvg +} + +// set and enable irq mask for dio1 +static void SetDioIrqParams (uint16_t mask) { + uint8_t param[] = { mask >> 8, mask & 0xFF, mask >> 8, mask & 0xFF, 0x00, 0x00, 0x00, 0x00 }; + writecmd(CMD_SETDIOIRQPARAMS, param, 8); +} + +// set tx power (in dBm) +static void SetTxPower (int pw) { +#if defined(BRD_sx1261_radio) + // low power PA: -17 ... +14 dBm + if (pw > 14) pw = 14; + if (pw < -17) pw = -17; + // set PA config (and reset OCP to 60mA) + writecmd(CMD_SETPACONFIG, (const uint8_t[]) { 0x04, 0x00, 0x01, 0x01 }, 4); +#elif defined(BRD_sx1262_radio) + // high power PA: -9 ... +22 dBm + if (pw > 22) pw = 22; + if (pw < -9) pw = -9; + // set PA config (and reset OCP to 140mA) + writecmd(CMD_SETPACONFIG, (const uint8_t[]) { 0x04, 0x07, 0x00, 0x01 }, 4); +#endif + // set tx params + uint8_t txparam[2]; + txparam[0] = (uint8_t) pw; + txparam[1] = 0x04; // ramp time 200us + writecmd(CMD_SETTXPARAMS, txparam, 2); +} + +// set sync word for LoRa +static void SetSyncWordLora (uint16_t syncword) { + uint8_t buf[2] = { syncword >> 8, syncword & 0xFF }; + WriteRegs(REG_LORASYNCWORDMSB, buf, 2); +} + +// set sync word for FSK +static void SetSyncWordFsk (uint32_t syncword) { + uint8_t buf[3] = { syncword >> 16, syncword >> 8, syncword & 0xFF }; + WriteRegs(REG_SYNCWORD0, buf, 3); +} + +// set seed for FSK data whitening +static void SetWhiteningSeed (uint16_t seed) { + uint8_t buf[2]; + buf[0] = (ReadReg(REG_WHITENINGMSB) & 0xFE) | ((seed >> 8) & 0x01); // don't modify the top-most 7 bits! + buf[1] = seed; + WriteRegs(REG_WHITENINGMSB, buf, 2); +} + +// set CRC seed and polynomial for FSK +static void SetCrc16 (uint16_t seed, uint16_t polynomial) { + // set seed + uint8_t buf[2] = { seed >> 8, seed & 0xFF }; + WriteRegs(REG_CRCINITVALMSB, buf, 2); + // set polynomial + buf[0] = polynomial >> 8; + buf[1] = polynomial; + WriteRegs(REG_CRCPOLYVALMSB, buf, 2); +} + +void radio_sleep (void) { + // cache sleep state to avoid unneccessary wakeup (waking up from cold sleep takes about 4ms) + if (state.sleeping == 0) { + SetSleep(SLEEP_COLD); + state.sleeping = 1; + } +} + +// Do config common to all RF modes +static void CommonSetup (void) { + SetRegulatorMode(REGMODE_DCDC); + if (hal_dio2_controls_rxtx()) + SetDIO2AsRfSwitchCtrl(1); + if (hal_dio3_controls_tcxo()) + SetDIO3AsTcxoCtrl(); +} + +static uint32_t GetRandom (void) __attribute__((__unused__)); // Ok if unused +static uint32_t GetRandom (void) { + uint32_t value; + + // Set up oscillator and rx/tx + CommonSetup(); + + // continuous rx + SetRx(0xFFFFFF); + // wait 1ms + hal_waitUntil(os_getTime() + ms2osticks(1)); + // read random register + ReadRegs(REG_RANDOMNUMBERGEN0, (uint8_t*)&value, sizeof(value)); + // standby + SetStandby(STDBY_RC); + return value; +} + +static void txlora (void) { + CommonSetup(); + SetStandby(STDBY_RC); + SetPacketType(PACKET_TYPE_LORA); + SetRfFrequency(LMIC.freq); + SetModulationParamsLora(LMIC.rps); + SetPacketParamsLora(LMIC.rps, LMIC.dataLen, 0); + SetTxPower(LMIC.txpow + LMIC.brdTxPowOff); + SetSyncWordLora(0x3444); + WriteFifo(LMIC.frame, LMIC.dataLen); + ClearIrqStatus(IRQ_ALL); + SetDioIrqParams(IRQ_TXDONE | IRQ_TIMEOUT); + + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO1); + + // antenna switch / power accounting + hal_ant_switch(HAL_ANTSW_TX); + + // now we actually start the transmission + BACKTRACE(); + SetTx(640000); // timeout 10s (should not happen, TXDONE irq will be raised) +} + +static void txfsk (void) { + CommonSetup(); + SetStandby(STDBY_RC); + SetPacketType(PACKET_TYPE_FSK); + SetRfFrequency(LMIC.freq); + SetModulationParamsFsk(); + SetPacketParamsFsk(LMIC.rps, LMIC.dataLen); + SetCrc16(0x1D0F, 0x1021); // CCITT + SetWhiteningSeed(0x01FF); + SetSyncWordFsk(0xC194C1); + SetTxPower(LMIC.txpow + LMIC.brdTxPowOff); + WriteFifo(LMIC.frame, LMIC.dataLen); + ClearIrqStatus(IRQ_ALL); + SetDioIrqParams(IRQ_TXDONE | IRQ_TIMEOUT); + + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO1); + + // antenna switch / power accounting + hal_ant_switch(HAL_ANTSW_TX); + + // now we actually start the transmission + BACKTRACE(); + SetTx(64000); // timeout 1s (should not happen, TXDONE irq will be raised) +} + +void radio_cw (void) { + CommonSetup(); + SetStandby(STDBY_RC); + SetRfFrequency(LMIC.freq); + SetTxPower(LMIC.txpow + LMIC.brdTxPowOff); + ClearIrqStatus(IRQ_ALL); + + // antenna switch / power accounting + hal_ant_switch(HAL_ANTSW_TX); + + // start tx of wave (indefinitely, ended by RADIO_STOP) + BACKTRACE(); + SetTxContinuousWave(); +} + +void radio_starttx (bool txcontinuous) { + if (txcontinuous) { + // XXX: This is probably not right. In 2.2, Semtech changed the + // 127x driver to rename txsw to radio_cw, but + // radio_starttx(true) now uses txfsk/txlora in continuous mode + // (which is apparently different from radio_cw), so that needs + // to be impliemented here as well + radio_cw(); + } else { + if (isFsk(LMIC.rps)) { // FSK modem + txfsk(); + } else { // LoRa modem + txlora(); + } + // the radio will go back to STANDBY mode as soon as the TX is finished + // the corresponding IRQ will inform us about completion. + } +} + +static void rxfsk (bool rxcontinuous) { + // configure radio (needs rampup time) + ostime_t t0 = os_getTime(); + CommonSetup(); + SetStandby(STDBY_RC); + SetPacketType(PACKET_TYPE_FSK); + SetRfFrequency(LMIC.freq); + SetModulationParamsFsk(); + SetPacketParamsFsk(LMIC.rps, 255); + SetCrc16(0x1D0F, 0x1021); // CCITT + SetWhiteningSeed(0x01FF); + SetSyncWordFsk(0xC194C1); + StopTimerOnPreamble(0); + // FSK interrupts: TXDONE, RXDONE, PREAMBLEDETECTED, SYNCWORDVALID, CRCERR, TIMEOUT + SetDioIrqParams(IRQ_RXDONE | IRQ_TIMEOUT); + ClearIrqStatus(IRQ_ALL); + + // enter frequency synthesis mode (become ready for immediate rx) + SetFs(); + + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO1); + + ostime_t now = os_getTime(); + if (!rxcontinuous && LMIC.rxtime - now < 0) { + debug_printf("WARNING: rxtime is %ld ticks in the past! (ramp-up time %ld ms / %ld ticks)\r\n", + now - LMIC.rxtime, osticks2ms(now - t0), now - t0); + } + + // now receive (lock interrupts only for final fine tuned rx timing...) + hal_disableIRQs(); + if (rxcontinuous) { // continous rx + BACKTRACE(); + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // rx infinitely (no timeout, until rxdone, will be restarted) + SetRx(0); + } else { // single rx + BACKTRACE(); + // busy wait until exact rx time + hal_waitUntil(LMIC.rxtime); + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // rx for max LMIC.rxsyms symbols (rxsyms = nbytes for FSK) + SetRx((LMIC.rxsyms << 9) / 50); // nbytes * 8 * 64 * 1000 / 50000 + } + hal_enableIRQs(); +} + +static void rxlora (bool rxcontinuous) { + // configure radio (needs rampup time) + ostime_t t0 = os_getTime(); + CommonSetup(); + SetStandby(STDBY_RC); + SetPacketType(PACKET_TYPE_LORA); + SetRfFrequency(LMIC.freq); + SetModulationParamsLora(LMIC.rps); + SetPacketParamsLora(LMIC.rps, 255, !LMIC.noRXIQinversion); + SetSyncWordLora(0x3444); + StopTimerOnPreamble(0); + SetLoRaSymbNumTimeout(LMIC.rxsyms); + SetDioIrqParams(IRQ_RXDONE | IRQ_TIMEOUT); + + ClearIrqStatus(IRQ_ALL); + + // enter frequency synthesis mode (become ready for immediate rx) + SetFs(); + + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO1); + + ostime_t now = os_getTime(); + if (!rxcontinuous && LMIC.rxtime - now < 0) { + // Print before disabling IRQs, to work around deadlock on some + // Arduino cores that doe not really support printing without IRQs + debug_printf("WARNING: rxtime is %ld ticks in the past! (ramp-up time %ld ms / %ld ticks)\r\n", + now - LMIC.rxtime, osticks2ms(now - t0), now - t0); + } + + // now receive (lock interrupts only for final fine tuned rx timing...) + hal_disableIRQs(); + if (rxcontinuous) { // continous rx + BACKTRACE(); + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // rx infinitely (no timeout, until rxdone, will be restarted) + SetRx(0); + } else { // single rx + BACKTRACE(); + // busy wait until exact rx time + hal_waitUntil(LMIC.rxtime); + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // rx for max LMIC.rxsyms symbols + SetRx(0); // (infinite, timeout set via SetLoRaSymbNumTimeout) + } + hal_enableIRQs(); +} + +void radio_cca () { + LMIC.rssi = -127; //XXX:TBD +} + +void radio_cad (void) { + // not yet... + ASSERT(0); +} + +void radio_startrx (bool rxcontinuous) { + if (isFsk(LMIC.rps)) { // FSK modem + rxfsk(rxcontinuous); + } else { // LoRa modem + rxlora(rxcontinuous); + } +} + +// reset radio +static void radio_reset (void) { + // drive RST pin low + bool has_reset = hal_pin_rst(0); + + // If reset is not connected, just continue and hope for the best + if (!has_reset) + return; + + // wait > 100us + hal_waitUntil(os_getTime() + ms2osticks(1)); + + // configure RST pin floating + hal_pin_rst(2); + + // wait 1ms? + hal_waitUntil(os_getTime() + ms2osticks(1)); + + // check reset value + ASSERT( ReadReg(REG_LORASYNCWORDLSB) == 0x24 ); + + // initialize state + state.sleeping = 0; +} + +void radio_init (bool calibrate) { + hal_disableIRQs(); + + // reset radio (FSK/STANDBY) + radio_reset(); + + // check reset value + ASSERT( ReadReg(REG_LORASYNCWORDLSB) == 0x24 ); + + if (calibrate) { + CalibrateImage(LMIC.freq); + } + + // go to SLEEP mode + radio_sleep(); + + hal_enableIRQs(); +} + +void radio_generate_random(u32_t *words, u8_t len) { + while (len--) + *words++ = GetRandom (); +} + +// (run by irqjob) +bool radio_irq_process (ostime_t irqtime, u8_t diomask) { + (void)diomask; // unused + + uint16_t irqflags = GetIrqStatus(); + + // dispatch modem + if (isFsk(LMIC.rps)) { // FSK modem + if (irqflags & IRQ_TXDONE) { // TXDONE + BACKTRACE(); + // save exact tx time + LMIC.txend = irqtime - FSK_TXDONE_FIXUP; + + } else if (irqflags & IRQ_RXDONE) { // RXDONE + BACKTRACE(); + + // read rx quality parameters + LMIC.rssi = GetPacketStatusFsk(); + LMIC.snr = 0; // N/A + + // read FIFO + LMIC.dataLen = ReadFifo(LMIC.frame); + + // save exact rx timestamps + LMIC.rxtime = irqtime - FSK_RXDONE_FIXUP; // end of frame timestamp + LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp +#ifdef DEBUG_RX + debug_printf("RX[rssi=%d,len=%d]: %h\r\n", + LMIC.rssi - RSSI_OFF, LMIC.dataLen, LMIC.frame, LMIC.dataLen); +#endif + } else if (irqflags & IRQ_TIMEOUT) { // TIMEOUT + BACKTRACE(); + // indicate timeout + LMIC.dataLen = 0; +#ifdef DEBUG_RX + debug_printf("RX: TIMEOUT\r\n"); +#endif + } else { + // unexpected irq + debug_printf("UNEXPECTED RADIO IRQ %04x (after %ld ticks, %.1Fms)\r\n", irqflags, irqtime - LMIC.rxtime, osticks2us(irqtime - LMIC.rxtime), 3); + TRACE_VAL(irqflags); + ASSERT(0); + } + } else { // LORA modem + if (irqflags & IRQ_TXDONE) { // TXDONE + BACKTRACE(); + + // save exact tx time + LMIC.txend = irqtime - LORA_TXDONE_FIXUP; + + } else if (irqflags & IRQ_RXDONE) { // RXDONE + BACKTRACE(); + + // read rx quality parameters + GetPacketStatusLora(&LMIC.rssi, &LMIC.snr); + + // read FIFO + LMIC.dataLen = ReadFifo(LMIC.frame); + + // save exact rx timestamps + LMIC.rxtime = irqtime; // end of frame timestamp + if (getBw(LMIC.rps) == BW125) { + LMIC.rxtime -= LORA_RXDONE_FIXUP_125[getSf(LMIC.rps)]; + } + else if (getBw(LMIC.rps) == BW500) { + LMIC.rxtime -= LORA_RXDONE_FIXUP_500[getSf(LMIC.rps)]; + } + LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp +#ifdef DEBUG_RX + debug_printf("RX[rssi=%d,snr=%.2F,len=%d]: %h\r\n", + LMIC.rssi - RSSI_OFF, (s32_t)(LMIC.snr * 100 / SNR_SCALEUP), 2, + LMIC.dataLen, LMIC.frame, LMIC.dataLen); +#endif + } else if (irqflags & IRQ_TIMEOUT) { // TIMEOUT + BACKTRACE(); + // indicate timeout + LMIC.dataLen = 0; +#ifdef DEBUG_RX + debug_printf("RX: TIMEOUT\r\n"); +#endif + } else { + // unexpected irq + debug_printf("UNEXPECTED RADIO IRQ %04x\r\n", irqflags); + TRACE_VAL(irqflags); + ASSERT(0); + } + } + + // mask all IRQs + SetDioIrqParams(0); + + // clear IRQ flags + ClearIrqStatus(IRQ_ALL); + + // radio operation completed + return true; +} + +#endif diff --git a/src/BasicMAC/lmic/radio-sx127x.c b/src/BasicMAC/lmic/radio-sx127x.c new file mode 100644 index 0000000..3e745b8 --- /dev/null +++ b/src/BasicMAC/lmic/radio-sx127x.c @@ -0,0 +1,1314 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#include "board.h" +#include "hw.h" +#include "lmic.h" + +#if defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) + +// ---------------------------------------- +// Registers Mapping +#define RegFifo 0x00 // common +#define RegOpMode 0x01 // common +#define FSKRegBitrateMsb 0x02 +#define FSKRegBitrateLsb 0x03 +#define FSKRegFdevMsb 0x04 +#define FSKRegFdevLsb 0x05 +#define RegFrfMsb 0x06 // common +#define RegFrfMid 0x07 // common +#define RegFrfLsb 0x08 // common +#define RegPaConfig 0x09 // common +#define RegPaRamp 0x0A // common +#define RegOcp 0x0B // common +#define RegLna 0x0C // common +#define FSKRegRxConfig 0x0D +#define LORARegFifoAddrPtr 0x0D +#define FSKRegRssiConfig 0x0E +#define LORARegFifoTxBaseAddr 0x0E +#define FSKRegRssiCollision 0x0F +#define LORARegFifoRxBaseAddr 0x0F +#define FSKRegRssiThresh 0x10 +#define LORARegFifoRxCurrentAddr 0x10 +#define FSKRegRssiValue 0x11 +#define LORARegIrqFlagsMask 0x11 +#define FSKRegRxBw 0x12 +#define LORARegIrqFlags 0x12 +#define FSKRegAfcBw 0x13 +#define LORARegRxNbBytes 0x13 +#define FSKRegOokPeak 0x14 +#define LORARegRxHeaderCntValueMsb 0x14 +#define FSKRegOokFix 0x15 +#define LORARegRxHeaderCntValueLsb 0x15 +#define FSKRegOokAvg 0x16 +#define LORARegRxPacketCntValueMsb 0x16 +#define LORARegRxpacketCntValueLsb 0x17 +#define LORARegModemStat 0x18 +#define LORARegPktSnrValue 0x19 +#define FSKRegAfcFei 0x1A +#define LORARegPktRssiValue 0x1A +#define FSKRegAfcMsb 0x1B +#define LORARegRssiValue 0x1B +#define FSKRegAfcLsb 0x1C +#define LORARegHopChannel 0x1C +#define FSKRegFeiMsb 0x1D +#define LORARegModemConfig1 0x1D +#define FSKRegFeiLsb 0x1E +#define LORARegModemConfig2 0x1E +#define FSKRegPreambleDetect 0x1F +#define LORARegSymbTimeoutLsb 0x1F +#define FSKRegRxTimeout1 0x20 +#define LORARegPreambleMsb 0x20 +#define FSKRegRxTimeout2 0x21 +#define LORARegPreambleLsb 0x21 +#define FSKRegRxTimeout3 0x22 +#define LORARegPayloadLength 0x22 +#define FSKRegRxDelay 0x23 +#define LORARegPayloadMaxLength 0x23 +#define FSKRegOsc 0x24 +#define LORARegHopPeriod 0x24 +#define FSKRegPreambleMsb 0x25 +#define LORARegFifoRxByteAddr 0x25 +#define LORARegModemConfig3 0x26 +#define FSKRegPreambleLsb 0x26 +#define FSKRegSyncConfig 0x27 +#define LORARegFeiMsb 0x28 +#define FSKRegSyncValue1 0x28 +#define LORAFeiMib 0x29 +#define FSKRegSyncValue2 0x29 +#define LORARegFeiLsb 0x2A +#define FSKRegSyncValue3 0x2A +#define FSKRegSyncValue4 0x2B +#define LORARegRssiWideband 0x2C +#define FSKRegSyncValue5 0x2C +#define FSKRegSyncValue6 0x2D +#define FSKRegSyncValue7 0x2E +#define FSKRegSyncValue8 0x2F +#define FSKRegPacketConfig1 0x30 +#define FSKRegPacketConfig2 0x31 +#define LORARegDetectOptimize 0x31 +#define FSKRegPayloadLength 0x32 +#define FSKRegNodeAdrs 0x33 +#define LORARegInvertIQ 0x33 +#define FSKRegBroadcastAdrs 0x34 +#define FSKRegFifoThresh 0x35 +#define FSKRegSeqConfig1 0x36 +#define FSKRegSeqConfig2 0x37 +#define LORARegDetectionThreshold 0x37 +#define FSKRegTimerResol 0x38 +#define FSKRegTimer1Coef 0x39 +#define LORARegSyncWord 0x39 +#define FSKRegTimer2Coef 0x3A +#define FSKRegImageCal 0x3B +#define LORARegInvertIQ2 0x3B +#define FSKRegTemp 0x3C +#define FSKRegLowBat 0x3D +#define FSKRegIrqFlags1 0x3E +#define FSKRegIrqFlags2 0x3F +#define RegDioMapping1 0x40 // common +#define RegDioMapping2 0x41 // common +#define RegVersion 0x42 // common +// #define RegAgcRef 0x43 // common +// #define RegAgcThresh1 0x44 // common +// #define RegAgcThresh2 0x45 // common +// #define RegAgcThresh3 0x46 // common +// #define RegPllHop 0x4B // common +#define SX1272_RegTcxo 0x58 // common +#define SX1276_RegTcxo 0x4B // common +#define SX1272_RegPaDac 0x5A // common +#define SX1276_RegPaDac 0x4D // common +// #define RegPll 0x5C // common +// #define RegPllLowPn 0x5E // common +// #define RegFormerTemp 0x6C // common +// #define RegBitRateFrac 0x70 // common + +// ---------------------------------------- +// SX1272 RegModemConfig1 settings +#define SX1272_MC1_CR_4_5 0x08 +#define SX1272_MC1_CR_4_6 0x10 +#define SX1272_MC1_CR_4_7 0x18 +#define SX1272_MC1_CR_4_8 0x20 +#define SX1272_MC1_IMPLICIT_HEADER_MODE_ON 0x04 // required for receive +#define SX1272_MC1_RX_PAYLOAD_CRCON 0x02 +#define SX1272_MC1_LOW_DATA_RATE_OPTIMIZE 0x01 + +// SX1272 RegModemConfig2 settings +#define SX1272_MC2_AGCAUTO 0x04 + +// opmodes +#define OPMODE_MASK 0x07 +#define OPMODE_SLEEP 0 +#define OPMODE_STANDBY 1 +#define OPMODE_FSTX 2 +#define OPMODE_TX 3 +#define OPMODE_FSRX 4 +#define OPMODE_RX 5 +#define OPMODE_RX_SINGLE 6 +#define OPMODE_CAD 7 + +// LoRa opmode bits: +#define OPMODE_LORA 0x80 +// SX1272: bit7=1 (LoRa), bit6=0 (AccessSharedReg=LoRa), bit5+4+3=000 (unused), bit2+1+0=mode +// SX1276: bit7=1 (LoRa), bit6=0 (AccessSharedReg=LoRa), bit5+4=00 (reserved), bit3=0 (access HF test regs), bit2+1+0=mode +#define OPMODE_LORA_SLEEP (0b10000000+OPMODE_SLEEP) +#define OPMODE_LORA_STANDBY (0b10000000+OPMODE_STANDBY) +#define OPMODE_LORA_FSTX (0b10000000+OPMODE_FSTX) +#define OPMODE_LORA_TX (0b10000000+OPMODE_TX) +#define OPMODE_LORA_FSRX (0b10000000+OPMODE_FSRX) +#define OPMODE_LORA_RX (0b10000000+OPMODE_RX) +#define OPMODE_LORA_RX_SINGLE (0b10000000+OPMODE_RX_SINGLE) +#define OPMODE_LORA_CAD (0b10000000+OPMODE_CAD) + +// FSK opmode bits: +#ifdef BRD_sx1272_radio +// SX1272: bit7=0 (FSK), bit6+5=00 (modulation=FSK), bit4+3=00 (no shaping), bits2+1+0=mode +#define OPMODE_FSK_SLEEP (0b00000000+OPMODE_SLEEP) +#define OPMODE_FSK_STANDBY (0b00000000+OPMODE_STANDBY) // (reset value of RegOpMode) +#define OPMODE_FSK_FSTX (0b00000000+OPMODE_FSTX) +#define OPMODE_FSK_TX (0b00000000+OPMODE_TX) +#define OPMODE_FSK_FSRX (0b00000000+OPMODE_FSRX) +#define OPMODE_FSK_RX (0b00000000+OPMODE_RX) +#endif + +#ifdef BRD_sx1276_radio +// SX1276: bit7=0 (FSK), bit6+5=00 (modulation=FSK), bit4=0 (reserved), bit3=1 (access LF test regs), bits2+1+0=mode +#define OPMODE_FSK_SLEEP (0b00001000+OPMODE_SLEEP) +#define OPMODE_FSK_STANDBY (0b00001000+OPMODE_STANDBY) // (reset value of RegOpMode) +#define OPMODE_FSK_FSTX (0b00001000+OPMODE_FSTX) +#define OPMODE_FSK_TX (0b00001000+OPMODE_TX) +#define OPMODE_FSK_FSRX (0b00001000+OPMODE_FSRX) +#define OPMODE_FSK_RX (0b00001000+OPMODE_RX) +#endif + +// ---------------------------------------- +// Bits masking the corresponding IRQs from the radio +#define IRQ_LORA_RXTOUT_MASK 0x80 +#define IRQ_LORA_RXDONE_MASK 0x40 +#define IRQ_LORA_CRCERR_MASK 0x20 +#define IRQ_LORA_HEADER_MASK 0x10 +#define IRQ_LORA_TXDONE_MASK 0x08 +#define IRQ_LORA_CDDONE_MASK 0x04 +#define IRQ_LORA_FHSSCH_MASK 0x02 +#define IRQ_LORA_CDDETD_MASK 0x01 + +// interrupt flags when large packet successfully: rcvd sent +#define IRQ_FSK1_MODEREADY_MASK 0x80 // 1 1 +#define IRQ_FSK1_RXREADY_MASK 0x40 // 1 0 +#define IRQ_FSK1_TXREADY_MASK 0x20 // 0 1 +#define IRQ_FSK1_PLLLOCK_MASK 0x10 // 1 1 +#define IRQ_FSK1_RSSI_MASK 0x08 // 1 0 +#define IRQ_FSK1_TIMEOUT_MASK 0x04 // 0 0 +#define IRQ_FSK1_PREAMBLEDETECT_MASK 0x02 // 1 0 +#define IRQ_FSK1_SYNCADDRESSMATCH_MASK 0x01 // 1 0 +#define IRQ_FSK2_FIFOFULL_MASK 0x80 // 0 0 +#define IRQ_FSK2_FIFOEMPTY_MASK 0x40 // 0 1 +#define IRQ_FSK2_FIFOLEVEL_MASK 0x20 // 0 0 +#define IRQ_FSK2_FIFOOVERRUN_MASK 0x10 // 0 0 +#define IRQ_FSK2_PACKETSENT_MASK 0x08 // 0 1 +#define IRQ_FSK2_PAYLOADREADY_MASK 0x04 // 1 0 +#define IRQ_FSK2_CRCOK_MASK 0x02 // 1 0 +#define IRQ_FSK2_LOWBAT_MASK 0x01 // 0 0 + +// ---------------------------------------- +// DIO function mappings MAP1:D0D1D2D3 +#define MAP1_LORA_DIO0_RXDONE 0x00 // 00------ +#define MAP1_LORA_DIO0_TXDONE 0x40 // 01------ +#define MAP1_LORA_DIO0_NOP 0xC0 // 11------ +#define MAP1_LORA_DIO1_RXTOUT 0x00 // --00---- +#define MAP1_LORA_DIO1_NOP 0x30 // --11---- +#define MAP1_LORA_DIO2_NOP 0x0C // ----11-- +#define MAP1_LORA_DIO3_CDDONE 0x00 // ------00 +#define MAP1_LORA_DIO3_NOP 0x03 // ------11 +// MAP2:D4D5XXXX +#define MAP2_LORA_DIO4_NOP 0xC0 // 11------ +#define MAP2_LORA_DIO5_NOP 0x30 // --11---- +#define MAP2_LORA_RFU 0x00 // ----000- +#define MAP2_LORA_IRQ_PREAMBLE 0x01 // -------1 + +// MAP1:D0D1D2D3 +#define MAP1_FSK_DIO0_RXDONE 0x00 // 00------ +#define MAP1_FSK_DIO0_TXDONE 0x00 // 00------ +#define MAP1_FSK_DIO1_LEVEL 0x00 // --00---- +#define MAP1_FSK_DIO1_EMPTY 0x10 // --01---- +#define MAP1_FSK_DIO1_FULL 0x20 // --10---- +#define MAP1_FSK_DIO1_NOP 0x30 // --11---- +#define MAP1_FSK_DIO2_TXNOP 0x04 // ----01-- +#define MAP1_FSK_DIO2_RXTOUT 0x08 // ----10-- + +// FSK ImageCal defines +#define RF_IMAGECAL_IMAGECAL_START 0x40 +#define RF_IMAGECAL_IMAGECAL_RUNNING 0x20 + +// IQ Inversion +#define IQRXNORMAL 0x27 // (see AN1200.24 SX1276 settings for LoRaWAN) +#define IQ2RXNORMAL 0x1D +#define IQRXINVERT 0x67 +#define IQ2RXINVERT 0x19 + +// operating mode transition times +#define TS_OSC us2osticksCeil(250) // (sleep to standby) + +// radio-specific settings +#if defined(BRD_sx1276_radio) +#define RADIO_VERSION 0x12 +#define RST_PIN_RESET_STATE 0 +#define RSSI_HF_CONST 157 +#define RegPaDac SX1276_RegPaDac +#define RegTcxo SX1276_RegTcxo +#define LORA_TXDONE_FIXUP us2osticksRound(67) // determined by timestamping DIO0 with SX1301 (mku/20190315) +#define LORA_RXSTART_FIXUP us2osticksRound(101) // determined by osc measurement GPIO vs with DIO5 (mode-ready) (mku/20190315) +#define FSK_TXDONE_FIXUP us2osticks(0) // XXX +#define FSK_RXDONE_FIXUP us2osticks(0) // XXX +#define PARAMP50 0b00001000 // unused=000, reserved=0, PaRamp=1000 + +static const u16_t LORA_RXDONE_FIXUP_125[] = { + [FSK] = us2osticks(0), + [SF7] = us2osticks(0), + [SF8] = us2osticks(1648), + [SF9] = us2osticks(3265), + [SF10] = us2osticks(7049), + [SF11] = us2osticks(13641), + [SF12] = us2osticks(31189), +}; + +static const u16_t LORA_RXDONE_FIXUP_500[] = { + [FSK] = us2osticks( 0), + [SF7] = us2osticks( 0), + [SF8] = us2osticks( 0), + [SF9] = us2osticks( 0), + [SF10] = us2osticks( 0), + [SF11] = us2osticks( 0), + [SF12] = us2osticks( 0), +}; + +#elif defined(BRD_sx1272_radio) +#define RADIO_VERSION 0x22 +#define RST_PIN_RESET_STATE 1 +#define RSSI_HF_CONST 139 +#define RegPaDac SX1272_RegPaDac +#define RegTcxo SX1272_RegTcxo +#define LORA_TXDONE_FIXUP us2osticks(43) // XXX +#define LORA_RXSTART_FIXUP us2osticksRound(101) // XXX +#define FSK_TXDONE_FIXUP us2osticks(0) // XXX +#define FSK_RXDONE_FIXUP us2osticks(0) // XXX +#define PARAMP50 0b00011000 // unused=000, LowPnTxPllOff=1, PaRamp=1000 + +static const u16_t LORA_RXDONE_FIXUP_125[] = { + [FSK] = us2osticksRound( 0), + [SF7] = us2osticksRound( 749), + [SF8] = us2osticksRound( 1343), + [SF9] = us2osticksRound( 3265), + [SF10] = us2osticksRound( 7049), + [SF11] = us2osticksRound(13641), + [SF12] = us2osticksRound(31189), +}; + +// Based Nucleo board regr tests rxlatency-regr +static const u16_t LORA_RXDONE_FIXUP_500[] = { + [FSK] = us2osticksRound( 0), + [SF7] = us2osticksRound( 193), + [SF8] = us2osticksRound( 344), + [SF9] = us2osticksRound( 737), + [SF10] = us2osticksRound(1521), + [SF11] = us2osticksRound(3240), + [SF12] = us2osticksRound(6972), +}; + +#endif + +#define FIFOTHRESH 32 + +// state +static struct { + // large packet handling + unsigned char* fifoptr; + int fifolen; +} state; + +// ---------------------------------------- +static void writeReg (u8_t addr, u8_t data) { + hal_spi_select(1); + hal_spi(addr | 0x80); + hal_spi(data); + hal_spi_select(0); +} + +static u8_t readReg (u8_t addr) { + hal_spi_select(1); + hal_spi(addr & 0x7F); + u8_t val = hal_spi(0x00); + hal_spi_select(0); + return val; +} + +// (used by perso) +void radio_writeBuf (u8_t addr, u8_t* buf, u8_t len) { + hal_spi_select(1); + hal_spi(addr | 0x80); + for (u8_t i = 0; i < len; i++) { + hal_spi(buf[i]); + } + hal_spi_select(0); +} + +// (used by perso) +void radio_readBuf (u8_t addr, u8_t* buf, u8_t len) { + hal_spi_select(1); + hal_spi(addr & 0x7F); + for (u8_t i = 0; i < len; i++) { + buf[i] = hal_spi(0x00); + } + hal_spi_select(0); +} + +void radio_sleep (void) { + writeReg(RegOpMode, OPMODE_LORA_SLEEP); // LoRa/FSK bit is ignored when not in SLEEP mode +} + +// set and wait for opmode (nsornin 2019-09-26) +static void setopmode (u8_t opmode) { + writeReg(RegOpMode, opmode); + ostime_t t0 = os_getTime(); + while (readReg(RegOpMode) != opmode) { + if (os_getTime() - t0 > ms2osticks(20)) { + // panic when opmode is not reached within 20ms + debug_printf("FAILED TO SET OPMODE %02x within 20ms\r\n", opmode); + ASSERT(0); + } + } +} + +// fill fifo when empty +static void LoadFifo (void) { + if (state.fifolen > 0) { + int n = (state.fifolen > FIFOTHRESH) ? FIFOTHRESH : state.fifolen; + radio_writeBuf(RegFifo, state.fifoptr, n); + state.fifoptr += n; + state.fifolen -= n; + } +} + +// read fifo when level or ready +static void UnloadFifo (void) { + if (state.fifolen < 0) { // first byte + state.fifolen = 0; + radio_readBuf(RegFifo, &LMIC.dataLen, 1); + } + int n = (LMIC.dataLen - state.fifolen > (FIFOTHRESH-1)) ? (FIFOTHRESH-1) : (LMIC.dataLen - state.fifolen); // errata: unload one byte less + if (n) { + radio_readBuf(RegFifo, state.fifoptr, n); + state.fifoptr += n; + state.fifolen += n; + } +} + +// configure LoRa modem +static void configLoraModem (bool txcont) { +#if defined(BRD_sx1276_radio) + // set ModemConfig1 'bbbbccch' (bw=xxxx, cr=xxx, implicitheader=x) + writeReg(LORARegModemConfig1, + ((getBw(LMIC.rps) - BW125 + 7) << 4) | // BW125 --> 7 + ((getCr(LMIC.rps) - CR_4_5 + 1) << 1) | // CR_4_5 --> 1 + (getIh(LMIC.rps) != 0)); // implicit header + + // set ModemConfig2 'sssstcmm' (sf=xxxx, txcont=x, rxpayloadcrc=x, symtimeoutmsb=00) + writeReg(LORARegModemConfig2, + ((getSf(LMIC.rps)-SF7+7) << 4) | // SF7 --> 7 + (txcont ? 0x08 : 0x00) | // txcont: 0x08 + ((getNocrc(LMIC.rps) == 0) << 2)); // rxcrc + + // set ModemConfig3 'uuuuoarr' (unused=0000, lowdatarateoptimize=x, agcauto=1, reserved=00) + writeReg(LORARegModemConfig3, + (enDro(LMIC.rps) << 3) | // symtime >= 16ms + (1 << 2)); // autoagc + + // SX1276 Errata: 2.1 Sensitivity Optimization with a 500kHz Bandwith + if (getBw(LMIC.rps) == BW500) { + writeReg(0x36, 0x02); + writeReg(0x3A, 0x64); + } else { + writeReg(0x36, 0x03); + // no need to reset register 0x3a + } +#elif defined(BRD_sx1272_radio) + // set ModemConfig1 'bbccchco' (bw=xx, cr=xxx, implicitheader=x, rxpayloadcrc=x, lowdatarateoptimize=x) + writeReg(LORARegModemConfig1, + ((getBw(LMIC.rps) - BW125) << 6) | // BW125 --> 0 + ((getCr(LMIC.rps) - CR_4_5 + 1) << 3) | // CR_4_5 --> 1 + ((getIh(LMIC.rps) != 0) << 2) | // implicit header + ((getNocrc(LMIC.rps) == 0) << 1) | // rxcrc + enDro(LMIC.rps)); // symtime >= 16ms + + // set ModemConfig2 'sssstamm' (sf=xxxx, txcont=x, agcauto=1 symtimeoutmsb=00) + writeReg(LORARegModemConfig2, + ((getSf(LMIC.rps)-SF7+7) << 4) | // SF7 --> 7 + (txcont ? 0x08 : 0x00) | // txcont: 0x08 + (1 << 2)); // autoagc +#endif // BRD_sx1272_radio + + if (getIh(LMIC.rps)) { + writeReg(LORARegPayloadLength, getIh(LMIC.rps)); // required length + } +} + +static void configChannel (void) { + // set frequency: FQ = (FRF * 32 Mhz) / (2 ^ 19) + u32_t frf = ((u64_t)LMIC.freq << 19) / 32000000; + writeReg(RegFrfMsb, frf >> 16); + writeReg(RegFrfMid, frf >> 8); + writeReg(RegFrfLsb, frf >> 0); +} + +static void setRadioConsumption_ua (bool boost, u8_t pow) { + u32_t ua; +#if defined(BRD_sx1276_radio) + static const u16_t BOOSTPOW[19] = { /* 2-20 */ + 35140 >> 1, + 36770 >> 1, + 38770 >> 1, + 40140 >> 1, + 41960 >> 1, + 44080 >> 1, + 46500 >> 1, + 48970 >> 1, + 51830 >> 1, + 55050 >> 1, + 58910 >> 1, + 63120 >> 1, + 67810 >> 1, + 73850 >> 1, + 81240 >> 1, + 89610 >> 1, + 95740 >> 1, + 103370 >> 1, + 111360 >> 1, + }; + + static const u16_t RFOPOW[16] = { /* 0-15 */ + 15910, + 16760, + 17570, + 18530, + 19660, + 20850, + 22010, + 23180, + 24260, + 25260, + 26360, + 27500, + 29000, + 30410, + 32080, + 34200, + }; + if( boost ) { + pow -= 2; + ASSERT(pow < 19); + ua = BOOSTPOW[pow] << 1; + } else { + ASSERT(pow < 16); + ua = RFOPOW[pow]; + } +#else + (void)boost; (void)pow; // unused + ua = 120000; // something better than nothing? +#endif + LMIC.radioPwr_ua = ua; +} + +// board-specific macro to determine which PA is to be used based on frequency and output power +#ifndef BRD_PABOOSTSEL +#define BRD_PABOOSTSEL(f,p) true +#endif + +// board-specific macro to determine which antenna switch port is to be used based on frequency and output power +#ifndef BRD_TXANTSWSEL +#define BRD_TXANTSWSEL(f,p) HAL_ANTSW_TX +#endif + +// PaDac 'rrrrrddd' (reserved=10000, dacdefault=100 dachigh=111) +// Ocp 'uuottttt' (unused=00, Ocp=x, trim=xxxxx) +// +// SX1276: +// PaCfg 'bmmmpppp' (PaSelect=x, MaxPower=xxx, OutputPower=xxxx) +// OutputPower = pw-2 if PaSelect = 1 (PA_BOOST pin 2..17dBm or 5..20dBm) +// OutputPower = pw if PaSelect = 0 (RFO pin and MaxPower=111 0..15dBm) +// OutputPower = pw+4 if PaSelect = 0 (RFO pin and MaxPower=000 -4..11dBm) +// +// SX1272: +// PaCfg 'buuupppp' (PaSelect=x, unused=000, OutputPower=xxxx) +// OutputPower = pw-2 if PaSelect = 1 (PA_BOOST pin 2..17dBm or 5..20dBm) +// OutputPower = pw+1 if PaSelect = 0 (RFO pin -1..14dBm) +// +// power-on: PaDac PaCfg Ocp +// SX1272: 0x84 0x0F 0x2B +// SX1276: 0x84 0x4F 0x2B + +static void configPower (int pw) { +#if (defined(CFG_wailmer_board) || defined(CFG_wailord_board)) && defined(CFG_us915) + // XXX - TODO - externalize this somehow + // wailmer/wailord can only use 17dBm at DR4 (US) + if (getBw(LMIC.rps) == BW500 && pw > 17) { + pw = 17; + } +#endif + + if (BRD_PABOOSTSEL(LMIC.freq, pw)) { // use PA_BOOST + if (pw > 17) { // use high-power +20dBm option + if (pw > 20) { + pw = 20; + } + writeReg(RegPaDac, 0x87); // high power + writeReg(RegPaConfig, 0x80 | (pw - 5)); // BOOST (5..20dBm) + } else { + if (pw < 2) { + pw = 2; + } + writeReg(RegPaDac, 0x84); // normal power + writeReg(RegPaConfig, 0x80 | (pw - 2)); // BOOST (2..17dBm) + } + setRadioConsumption_ua(true, pw); + } else { // use PA_RFO +#if defined(BRD_sx1276_radio) + if (pw > 0) { + if (pw > 15) { + pw = 15; + } + writeReg(RegPaConfig, 0x70 | pw); // RFO, maxpower=111 (0..15dBm) + } else { + if (pw < -4) { + pw = -4; + } + writeReg(RegPaConfig, pw + 4); // RFO, maxpower=000 (-4..11dBm) + } + writeReg(RegPaDac, 0x84); // normal power +#elif defined(BRD_sx1272_radio) + if (pw < -1) { + pw = -1; + } else if (pw > 14) { + pw = 14; + } + writeReg(RegPaConfig, pw + 1); // RFO (-1..14dBm) + writeReg(RegPaDac, 0x84); // normal power +#endif + setRadioConsumption_ua(false, (pw < 0) ? 0 : pw); + } + + // set 50us PA ramp-up time + writeReg(RegPaRamp, PARAMP50); +} + +static void power_tcxo (void) { + // power-up TCXO and set tcxo as input + if ( hal_pin_tcxo(1) ) { + writeReg(RegTcxo, 0b00011001); // reserved=000, tcxo=1, reserved=1001 + // delay to allow TCXO to wake up + hal_waitUntil(os_getTime() + ms2osticks(1)); + } +} + +// continuous wave +void radio_cw (void) { + // select FSK modem (from sleep mode) + setopmode(OPMODE_FSK_SLEEP); + + // power-up tcxo + power_tcxo(); + + // enter standby mode + setopmode(OPMODE_FSK_STANDBY); + + // set frequency deviation + writeReg(FSKRegFdevMsb, 0x00); + writeReg(FSKRegFdevLsb, 0x00); + + // configure frequency + configChannel(); + + // configure output power + int pw = LMIC.txpow + LMIC.brdTxPowOff; + configPower(pw); + + // set continuous mode + writeReg(FSKRegPacketConfig2, 0x00); + + // initialize the payload size and address pointers + writeReg(FSKRegPayloadLength, 1); + writeReg(RegFifo, 0); + + // enable antenna switch for TX + hal_ant_switch(BRD_TXANTSWSEL(LMIC.freq, pw)); + + // now we actually start the transmission + writeReg(RegOpMode, OPMODE_FSK_TX); +} + +static void txfsk (bool txcont) { + // select FSK modem (from sleep mode) + setopmode(OPMODE_FSK_SLEEP); + + // power-up tcxo + power_tcxo(); + + // enter standby mode + setopmode(OPMODE_FSK_STANDBY); + + // set bitrate 50kbps + writeReg(FSKRegBitrateMsb, 0x02); // 32000000 / 50000 = 640 = 0x0280 + writeReg(FSKRegBitrateLsb, 0x80); + + // set frequency deviation +/-25kHz + writeReg(FSKRegFdevMsb, 0x01); + writeReg(FSKRegFdevLsb, 0x99); + + // frame and packet handler settings + writeReg(FSKRegPreambleMsb, 0x00); // 5 bytes preamble + writeReg(FSKRegPreambleLsb, 0x05); + writeReg(FSKRegSyncConfig, 0x12); // 3 bytes sync word 0xC194C1 + writeReg(FSKRegSyncValue1, 0xC1); + writeReg(FSKRegSyncValue2, 0x94); + writeReg(FSKRegSyncValue3, 0xC1); + writeReg(FSKRegPacketConfig1, 0xD0); // varlen + whitening + crc + noaddr + writeReg(FSKRegPacketConfig2, (txcont) ? 0x00 : 0x40); // continuous mode or packet mode + + // configure frequency + configChannel(); + + // configure output power + int pw = LMIC.txpow + LMIC.brdTxPowOff; + configPower(pw); + + // set the IRQ mapping DIO0=PacketSent DIO1=FifoEmpty DIO2=NOP + writeReg(RegDioMapping1, MAP1_FSK_DIO0_TXDONE | MAP1_FSK_DIO1_EMPTY | MAP1_FSK_DIO2_TXNOP); + + // setup FIFO + writeReg(FSKRegFifoThresh, 0x80); // TxStartCondition !FifoEmpty + // write length byte + writeReg(RegFifo, LMIC.dataLen); + // write payload (full or partial) + state.fifoptr = LMIC.frame; + state.fifolen = LMIC.dataLen; + LoadFifo(); + + if (!txcont) { + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO0 | HAL_IRQMASK_DIO1); + + // set tx timeout + radio_set_irq_timeout(os_getTime() + us2osticks((u32_t)(FIFOTHRESH+10)*8*1000/50)); + } + + // enable antenna switch for TX + hal_ant_switch(BRD_TXANTSWSEL(LMIC.freq, pw)); + + // now we actually start the transmission + writeReg(RegOpMode, OPMODE_FSK_TX); +} + +static void txlora (bool txcontinuous) { + // select LoRa modem (from sleep mode) + setopmode(OPMODE_LORA_SLEEP); + + // power-up tcxo + power_tcxo(); + + // enter standby mode + setopmode(OPMODE_LORA_STANDBY); + + // configure LoRa modem + configLoraModem(txcontinuous); + + // configure frequency + configChannel(); + + // configure output power + int pw = LMIC.txpow + LMIC.brdTxPowOff; + configPower(pw); + + // set sync word + writeReg(LORARegSyncWord, 0x34); + + // set IQ inversion mode + writeReg(LORARegInvertIQ, IQRXNORMAL); + writeReg(LORARegInvertIQ2, IQ2RXNORMAL); + + // set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP DIO3=NOP DIO4=NOP DIO5=NOP + writeReg(RegDioMapping1, MAP1_LORA_DIO0_TXDONE | MAP1_LORA_DIO1_NOP | MAP1_LORA_DIO2_NOP | MAP1_LORA_DIO3_NOP); + writeReg(RegDioMapping2, MAP2_LORA_DIO4_NOP | MAP2_LORA_DIO5_NOP); + + // clear all radio IRQ flags + writeReg(LORARegIrqFlags, 0xFF); + + // mask all IRQs but TxDone + writeReg(LORARegIrqFlagsMask, ~IRQ_LORA_TXDONE_MASK); + + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO0); + + // initialize the payload size and address pointers + writeReg(LORARegFifoTxBaseAddr, 0x00); + writeReg(LORARegFifoAddrPtr, 0x00); + writeReg(LORARegPayloadLength, LMIC.dataLen); + + // download buffer to the radio FIFO + radio_writeBuf(RegFifo, LMIC.frame, LMIC.dataLen); + + // enable antenna switch for TX + hal_ant_switch(BRD_TXANTSWSEL(LMIC.freq, pw)); + + // now we actually start the transmission + BACKTRACE(); + writeReg(RegOpMode, OPMODE_LORA_TX); +} + +static void setuprxlora (void) { + // select LoRa modem (from sleep mode) + setopmode(OPMODE_LORA_SLEEP); + + // power-up tcxo + power_tcxo(); + + // enter standby mode + setopmode(OPMODE_LORA_STANDBY); + + // configure LoRa modem (cfg1, cfg2, cfg3) + configLoraModem(false); + + // configure frequency + configChannel(); + + // set LNA gain 'gggbbrbb' (LnaGain=001 (max), LnaBoostLf=00 (default), reserved=0, LnaBoostHf=11 (150%)) + writeReg(RegLna, 0b00100011); + + // set max payload size + writeReg(LORARegPayloadMaxLength, MAX_LEN_FRAME); + + // set IQ inversion mode + writeReg(LORARegInvertIQ, (LMIC.noRXIQinversion) ? IQRXNORMAL : IQRXINVERT); + writeReg(LORARegInvertIQ2, (LMIC.noRXIQinversion) ? IQ2RXNORMAL : IQ2RXINVERT); + + // set max preamble length 8 + writeReg(LORARegPreambleMsb, 0x00); + writeReg(LORARegPreambleLsb, 0x08); + + // set symbol timeout (for single rx) + writeReg(LORARegSymbTimeoutLsb, LMIC.rxsyms); + + // set sync word + writeReg(LORARegSyncWord, 0x34); +} + + +// workaround to improve likelihood of preamble detection when receiver started at symbol boundary +// +// - use 7 symbols as symbtimeout (see lmic.c: MINRX_SYMS=7) +// --> aim for center of symbol, not beginning of symbol +// --> allow for 4 out of 6 identical peaks in sliding window of 6 +// (receiver is stopped exactly after SymbTimeout, but there is processing overhead, so it's one symbol less) +// +// - shift start time of receiver randomly by up to 500us +// +static ostime_t bugfix_rxtime (ostime_t rxtime) { + // Note: in ticks, the resolution is 30.5 us @ 32.768 kHz + // use ceil() to ensure a delay of at least 1 tick + if( getSf(LMIC.rps) >= SF9 ) { + // SX127x bug workaround: random delay 1-512 us + rxtime += us2osticksCeil(1 + (os_getRndU2() & 0x1ff)); + } + return rxtime; +} + +static void rxlorasingle (void) { + ostime_t t0 = os_getTime(); + + // select modem, setup TCXO, freq, modulation + setuprxlora(); + + // configure DIO mapping DIO0=RxDone DIO1=Timeout DIO2=NOP DIO3=NOP DIO4=NOP DIO5=NOP + writeReg(RegDioMapping1, (MAP1_LORA_DIO0_RXDONE | MAP1_LORA_DIO1_RXTOUT | MAP1_LORA_DIO2_NOP | MAP1_LORA_DIO3_NOP)); + writeReg(RegDioMapping2, MAP2_LORA_DIO4_NOP | MAP2_LORA_DIO5_NOP); + + // clear all radio IRQ flags + writeReg(LORARegIrqFlags, 0xFF); + + // enable required radio IRQs + writeReg(LORARegIrqFlagsMask, (uint8_t) ~(IRQ_LORA_RXDONE_MASK | IRQ_LORA_RXTOUT_MASK)); + + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO0 | HAL_IRQMASK_DIO1); + + // now instruct the radio to receive + // (lock interrupts only for final fine tuned rx timing...) + hal_disableIRQs(); + BACKTRACE(); + // busy wait until exact rx time + ostime_t rxtime = LMIC.rxtime - LORA_RXSTART_FIXUP; + // SX127x bug fix: move exact RX time away from symbol boundary + rxtime = bugfix_rxtime(rxtime); + // wait for it... + ostime_t now = os_getTime(); + hal_waitUntil(rxtime); + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // rx now... + writeReg(RegOpMode, OPMODE_LORA_RX_SINGLE); + // re-enable interrupts + hal_enableIRQs(); + // warn about delayed rx + if( rxtime - now < 0 ) { + debug_printf("WARNING: rxtime is %ld ticks in the past! (ramp-up time %ld ms / %ld ticks)\r\n", + now - rxtime, osticks2ms(now - t0), now - t0); + } +} + +static void rxloracont (void) { + // select modem, setup TCXO, freq, modulation + setuprxlora(); + + // configure DIO mapping DIO0=RxDone DIO1=NOP DIO2=NOP DIO3=NOP DIO4=NOP DIO5=NOP + writeReg(RegDioMapping1, MAP1_LORA_DIO0_RXDONE | MAP1_LORA_DIO1_NOP | MAP1_LORA_DIO2_NOP | MAP1_LORA_DIO3_NOP); + writeReg(RegDioMapping2, MAP2_LORA_DIO4_NOP | MAP2_LORA_DIO5_NOP); + + // clear all radio IRQ flags + writeReg(LORARegIrqFlags, 0xFF); + + // enable required radio IRQs + writeReg(LORARegIrqFlagsMask, ~IRQ_LORA_RXDONE_MASK); + + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO0); + + // now instruct the radio to receive + BACKTRACE(); + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // rx now... + writeReg(RegOpMode, OPMODE_LORA_RX); +} + +static void rxloracad (void) { + // select modem, setup TCXO, freq, modulation + setuprxlora(); + + // configure DIO mapping DIO0=RxDone DIO1=RxTout DIO2=NOP DIO3=CadDone DIO4=NOP DIO5=NOP + writeReg(RegDioMapping1, MAP1_LORA_DIO0_RXDONE | MAP1_LORA_DIO1_RXTOUT | MAP1_LORA_DIO2_NOP | MAP1_LORA_DIO3_CDDONE); + writeReg(RegDioMapping2, MAP2_LORA_DIO4_NOP | MAP2_LORA_DIO5_NOP); + + // clear all radio IRQ flags + writeReg(LORARegIrqFlags, 0xFF); + + // enable required radio IRQs + writeReg(LORARegIrqFlagsMask, (uint8_t) ~(IRQ_LORA_CDDONE_MASK | IRQ_LORA_CDDETD_MASK | IRQ_LORA_RXDONE_MASK | IRQ_LORA_RXTOUT_MASK)); + + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO0 | HAL_IRQMASK_DIO1 | HAL_IRQMASK_DIO3); + + // now instruct the radio to receive + BACKTRACE(); + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + // start CAD... + writeReg(RegOpMode, OPMODE_LORA_CAD); +} + +static void rxfsk (bool rxcontinuous) { + // configure radio (needs rampup time) + ostime_t t0 = os_getTime(); + + // select FSK modem (from sleep mode) + setopmode(OPMODE_FSK_SLEEP); + + // power-up tcxo + power_tcxo(); + + // enter standby mode + setopmode(OPMODE_FSK_STANDBY); + + // configure frequency + configChannel(); + + // set bitrate 50kbps + writeReg(FSKRegBitrateMsb, 0x02); // 32000000 / 50000 = 640 = 0x0280 + writeReg(FSKRegBitrateLsb, 0x80); + + // set LNA gain + writeReg(RegLna, 0b00100011); // highest gain, boost enable + + // configure receiver + writeReg(FSKRegRxConfig, 0b00011110); // no restart, auto afc, auto agc, trigger on preamble + + // set receiver bandwidth + writeReg(FSKRegRxBw, 0b00001011); // 50kHz SSB + + // set AFC bandwidth + writeReg(FSKRegAfcBw, 0b00010010); // 83.3kHz SSB + + // set preamble detection + writeReg(FSKRegPreambleDetect, 0b10101010); // enable, 2 bytes, 10 chip errors + + // set sync config + writeReg(FSKRegSyncConfig, 0b00010010); // no auto restart, preamble 0xaa, sync addr enable, fill fifo, 3 bytes sync word + + // set sync word + writeReg(FSKRegSyncValue1, 0xC1); + writeReg(FSKRegSyncValue2, 0x94); + writeReg(FSKRegSyncValue3, 0xC1); + + // set packet config + writeReg(FSKRegPacketConfig1, 0b11011000); // var-length, whitening, crc, no auto-clear irq, no adr filter, ccitt crc + writeReg(FSKRegPacketConfig2, 0b01000000); // packet mode + + // set max payload length + writeReg(FSKRegPayloadLength, MAX_LEN_FRAME); + + // set fifo threshold + writeReg(FSKRegFifoThresh, FIFOTHRESH); + + state.fifolen = -1; + state.fifoptr = LMIC.frame; + + // configure DIO mapping DIO0=RxPayloadReady DIO1=FifoLevel DIO2=RxTimeOut + writeReg(RegDioMapping1, MAP1_FSK_DIO0_RXDONE | MAP1_FSK_DIO1_LEVEL | MAP1_FSK_DIO2_RXTOUT); + + // enable IRQs in HAL + hal_irqmask_set(HAL_IRQMASK_DIO0 | HAL_IRQMASK_DIO1 | HAL_IRQMASK_DIO2); + + // now instruct the radio to receive + hal_disableIRQs(); + + if (rxcontinuous) { + BACKTRACE(); + // XXX not suppported - receiver does not automatically restart + radio_set_irq_timeout(os_getTime() + sec2osticks(5)); // time out after 5 sec + } else { + BACKTRACE(); + // set preamble timeout + writeReg(FSKRegRxTimeout2, (LMIC.rxsyms + 1) / 2); // (TimeoutRxPreamble * 16 * Tbit) + // set rx timeout + radio_set_irq_timeout(LMIC.rxtime + us2osticks((u32_t)(2*FIFOTHRESH)*8*1000/50)); + // busy wait until exact rx time + ostime_t now = os_getTime(); + if (LMIC.rxtime - now < 0) { + debug_printf("WARNING: rxtime is %ld ticks in the past! (ramp-up time %ld ms / %ld ticks)\r\n", + now - LMIC.rxtime, osticks2ms(now - t0), now - t0); + } + hal_waitUntil(LMIC.rxtime); + } + + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + + // rx + writeReg(RegOpMode, OPMODE_FSK_RX); + hal_enableIRQs(); +} + +void radio_startrx (bool rxcontinuous) { + ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); + + // set power consumption for statistics + LMIC.radioPwr_ua = 11500; + + if (isFsk(LMIC.rps)) { // FSK modem + rxfsk(rxcontinuous); + } else { // LoRa modem + if (rxcontinuous) { + rxloracont(); + } else { + rxlorasingle(); + } + } +} + +void radio_cad (void) { + ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); + + rxloracad(); +} + +void radio_starttx (bool txcontinuous) { + ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); + if (isFsk(LMIC.rps)) { // FSK modem + txfsk(txcontinuous); + } else { // LoRa modem + txlora(txcontinuous); + } +} + +// LMIC.rssi = max_rssi(threshold=LMIC.rssi, duration=LMIC.rxtime, freq=LMIC.freq, bw=LMIC.rps) +void radio_cca (void) { + BACKTRACE(); + // select FSK modem (from sleep mode) + ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); + setopmode(OPMODE_FSK_SLEEP); + + // power-up tcxo + power_tcxo(); + + // enter standby mode + setopmode(OPMODE_FSK_STANDBY); + + // configure frequency + configChannel(); + + // set LNA gain + writeReg(RegLna, 0b00100011); // highest gain, boost enable + + // set receiver bandwidth (SSB) + writeReg(FSKRegRxBw, (isFsk(LMIC.rps)) ? 0x0B /* 50kHz SSB */ : + 3 - getBw(LMIC.rps)); // 62.5/125/250kHz SSB (RxBwMant=0, RxBwExp 3/2/1) + + // set power consumption for statistics + LMIC.radioPwr_ua = 11500; + + // enable antenna switch for RX (and account power consumption) + hal_ant_switch(HAL_ANTSW_RX); + + // start receiver, don't receive frames + setopmode(OPMODE_FSK_RX); + + // initialize threshold + int rssi; + int rssi_th = LMIC.rssi; + int rssi_max = -128 + RSSI_OFF; + ostime_t t0 = os_getTime(); + + // sample rssi values + do { + rssi = -readReg(FSKRegRssiValue) / 2 + RSSI_OFF; + if (rssi > rssi_max) { + rssi_max = rssi; + } + } while (rssi < rssi_th && os_getTime() - t0 < LMIC.rxtime); + + // return max observed rssi value + LMIC.rssi = rssi_max; + + // shutdown receiver + radio_sleep(); + + // disable antenna switch + hal_ant_switch(HAL_ANTSW_OFF); + + // power-down TCXO + hal_pin_tcxo(0); +} + +// reset radio +static void radio_reset (void) { + // drive RST pin + bool has_reset = hal_pin_rst(RST_PIN_RESET_STATE); + + // power-down TCXO + hal_pin_tcxo(0); + + // If reset is not connected, just continue and hope for the best + if (!has_reset) + return; + + // wait > 100us + hal_waitUntil(os_getTime() + ms2osticks(1)); + + // configure RST pin floating + hal_pin_rst(2); + + // wait > 5ms + hal_waitUntil(os_getTime() + ms2osticks(10)); + + // check opmode + ASSERT( readReg(RegOpMode) == OPMODE_FSK_STANDBY ); +} + +void radio_init (bool calibrate) { + BACKTRACE(); + hal_disableIRQs(); + + // power-up tcxo + power_tcxo(); + + // reset radio (FSK/STANDBY) + radio_reset(); + + // sanity check, read version number + ASSERT( readReg(RegVersion) == RADIO_VERSION ); + + // disable automatic image rejection calibration + writeReg(FSKRegImageCal, 0x00); + + // optionally perform receiver chain calibration in FSK/STANDBY mode + if (calibrate) { + // set band/frequency + configChannel(); + + // run receiver chain calibration + writeReg(FSKRegImageCal, RF_IMAGECAL_IMAGECAL_START); // (clear auto-cal) + while ( readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_RUNNING ); + } + + // go to SLEEP mode + setopmode(OPMODE_FSK_SLEEP); + + // power-down TCXO + hal_pin_tcxo(0); + + hal_enableIRQs(); +} + +// (run by irqjob) +bool radio_irq_process (ostime_t irqtime, u8_t diomask) { + (void)diomask; //unused + + // dispatch modem + if (isFsk(LMIC.rps)) { // FSK modem + u8_t irqflags1 = readReg(FSKRegIrqFlags1); + u8_t irqflags2 = readReg(FSKRegIrqFlags2); + + if (irqflags2 & IRQ_FSK2_PACKETSENT_MASK) { // TXDONE + BACKTRACE(); + + // save exact tx time + LMIC.txend = irqtime - FSK_TXDONE_FIXUP; + + } else if (irqflags2 & IRQ_FSK2_PAYLOADREADY_MASK) { // RXDONE + BACKTRACE(); + + // read rx quality parameters (at end of packet, not optimal since energy might already be gone) + // (unfortunately in SX1272/SX1276 no averaged RSSI available in FSK mode, better in SX1261) + LMIC.rssi = -readReg(FSKRegRssiValue) / 2 + RSSI_OFF; + LMIC.snr = 0; // N/A + + // read FIFO + UnloadFifo(); + + // save exact rx timestamps + LMIC.rxtime = irqtime - FSK_RXDONE_FIXUP; // end of frame timestamp + LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp +#ifdef DEBUG_RX + debug_printf("RX[rssi=%d,len=%d]: %h\r\n", + LMIC.rssi - RSSI_OFF, LMIC.dataLen, LMIC.frame, LMIC.dataLen); +#endif + } else if (irqflags1 & IRQ_FSK1_TIMEOUT_MASK) { // TIMEOUT + BACKTRACE(); + // indicate timeout + LMIC.dataLen = 0; +#ifdef DEBUG_RX + debug_printf("RX: TIMEOUT (%d us)\r\n", osticks2us(irqtime - LMIC.rxtime)); +#endif + } else if( irqflags2 & IRQ_FSK2_FIFOEMPTY_MASK ) { // FIFOEMPTY (TX) + BACKTRACE(); + + // fill FIFO buffer + LoadFifo(); + + // update tx timeout + radio_set_irq_timeout(irqtime + us2osticks((u32_t)(FIFOTHRESH+10)*8*1000/50)); + + // keep waiting for FifoEmpty or PacketSent interrupt + return false; + + } else if( irqflags2 & IRQ_FSK2_FIFOLEVEL_MASK ) { // FIFOLEVEL (RX) + BACKTRACE(); + + // read FIFO buffer + UnloadFifo(); + + // update rx timeout + radio_set_irq_timeout(irqtime + us2osticks((u32_t)(FIFOTHRESH+10)*8*1000/50)); + + // keep waiting for FifoLevel or PayloadReady interrupt + return false; + + } else { + // unexpected irq + debug_printf("UNEXPECTED FSK IRQ %02x %02x\r\n", irqflags1, irqflags2); + ASSERT(0); + } + + // clear FSK IRQ flags + writeReg(FSKRegIrqFlags1, 0xFF); + writeReg(FSKRegIrqFlags2, 0xFF); + + } else { // LORA modem + u8_t irqflags = readReg(LORARegIrqFlags); + + if (irqflags & IRQ_LORA_TXDONE_MASK) { // TXDONE + BACKTRACE(); + + // save exact tx time + LMIC.txend = irqtime - LORA_TXDONE_FIXUP; + + } else if (irqflags & IRQ_LORA_RXDONE_MASK) { // RXDONE (rx or scan) + BACKTRACE(); + + // read rx quality parameters (averaged over packet) + LMIC.snr = readReg(LORARegPktSnrValue); // SNR [dB] * 4 + LMIC.rssi = readReg(LORARegPktRssiValue); // final values -128..127 correspond to -196...+63 dBm (subtract RSSI_OFF) + if (LMIC.snr < 0) { + LMIC.rssi = -RSSI_HF_CONST + LMIC.rssi + LMIC.snr/4 + RSSI_OFF; + } else { + LMIC.rssi = -RSSI_HF_CONST + LMIC.rssi * 16/15 + RSSI_OFF; + } + + // get PDU length + LMIC.dataLen = readReg(LORARegRxNbBytes); + + // save exact rx timestamps + LMIC.rxtime = irqtime; // end of frame timestamp + if (getBw(LMIC.rps) == BW125) { + LMIC.rxtime -= LORA_RXDONE_FIXUP_125[getSf(LMIC.rps)]; + } + else if (getBw(LMIC.rps) == BW500) { + LMIC.rxtime -= LORA_RXDONE_FIXUP_500[getSf(LMIC.rps)]; + } + LMIC.rxtime0 = LMIC.rxtime - calcAirTime(LMIC.rps, LMIC.dataLen); // beginning of frame timestamp + + // set FIFO read address pointer (to address of last packet received) + writeReg(LORARegFifoAddrPtr, readReg(LORARegFifoRxCurrentAddr)); + + // read FIFO + radio_readBuf(RegFifo, LMIC.frame, LMIC.dataLen); +#ifdef DEBUG_RX + debug_printf("RX[rssi=%d,snr=%.2F,len=%d]: %.80h\r\n", + LMIC.rssi - RSSI_OFF, (s32_t)(LMIC.snr * 100 / SNR_SCALEUP), 2, + LMIC.dataLen, LMIC.frame, LMIC.dataLen); +#endif + } else if (irqflags & IRQ_LORA_RXTOUT_MASK) { // RXTOUT + BACKTRACE(); + // indicate timeout + LMIC.dataLen = 0; +#ifdef DEBUG_RX + debug_printf("RX: TIMEOUT (%d us)\r\n"); +#endif + } else if (irqflags & IRQ_LORA_CDDONE_MASK) { // CDDONE + BACKTRACE(); + // check if preamble symbol was detected + if (irqflags & IRQ_LORA_CDDETD_MASK) { + // switch to receiving (continuous) + writeReg(RegOpMode, OPMODE_LORA_RX); + // continue waiting + return false; + } else { + // indicate timeout + LMIC.dataLen = 0; + } + } else { + // unexpected irq + ASSERT(0); + } + + // mask all LoRa IRQs + writeReg(LORARegIrqFlagsMask, 0xFF); + + // clear LoRa IRQ flags + writeReg(LORARegIrqFlags, 0xFF); + } + + // radio operation completed + return true; +} + +#endif diff --git a/src/BasicMAC/lmic/radio.c b/src/BasicMAC/lmic/radio.c new file mode 100644 index 0000000..5c7baae --- /dev/null +++ b/src/BasicMAC/lmic/radio.c @@ -0,0 +1,209 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// Copyright (C) 2014-2016 IBM Corporation. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +//#define DEBUG_TX +//#define DEBUG_RX + +#include "board.h" +#include "lmic.h" + +// ---------------------------------------- +// RADIO STATE +static struct { + ostime_t irqtime; + osjob_t irqjob; + u8_t diomask; + u8_t txmode; +} state; + +// stop radio, disarm interrupts, cancel jobs +static void radio_stop (void) { + hal_disableIRQs(); + // put radio to sleep + radio_sleep(); + // disable antenna switch + hal_ant_switch(HAL_ANTSW_OFF); +#if defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) + // power-down TCXO + hal_pin_tcxo(0); +#endif // defined(BRD_sx1272_radio) || defined(BRD_sx1276_radio) + // disable antenna switch + // disable IRQs in HAL + hal_irqmask_set(0); + // cancel radio job + os_clearCallback(&state.irqjob); + // clear state + state.diomask = 0; + hal_enableIRQs(); +} + +// guard timeout in case no completion interrupt is generated by radio +// protected job - runs with irqs disabled! +static void radio_irq_timeout (osjob_t* j) { + BACKTRACE(); + (void)j; // unused + + // stop everything (antenna switch, hal irqs, sleep, irq job) + radio_stop(); + + // re-initialize radio if tx operation timed out + if (state.txmode) { + radio_init(true); + } + + // enable IRQs! + hal_enableIRQs(); + + debug_printf("WARNING: radio irq timeout!\r\n"); + + // indicate timeout + LMIC.dataLen = 0; + + // run os job (use preset func ptr) + os_setCallback(&LMIC.osjob, LMIC.osjob.func); +} + +void radio_set_irq_timeout (ostime_t timeout) { + // schedule irq-protected timeout function + os_setProtectedTimedCallback(&state.irqjob, timeout, radio_irq_timeout); +} + +// (run by irqjob) +static void radio_irq_func (osjob_t* j) { + (void)j; // unused + // call radio-specific processing function + if( radio_irq_process(state.irqtime, state.diomask) ) { + // current radio operation has completed + radio_stop(); // (disable antenna switch and HAL irqs, make radio sleep) + + // run LMIC job (use preset func ptr) + os_setCallback(&LMIC.osjob, LMIC.osjob.func); + } + + // clear irq state (job has been run) + state.diomask = 0; +} + +// called by hal exti IRQ handler +// (all radio operations are performed on radio job!) +void radio_irq_handler (u8_t diomask, ostime_t ticks) { + BACKTRACE(); + + // make sure previous job has been run + ASSERT( state.diomask == 0 ); + + // save interrupt source and time + state.irqtime = ticks; + state.diomask = diomask; + + // schedule irq job + // (timeout job will be replaced, intermediate interrupts must rewind timeout!) + os_setCallback(&state.irqjob, radio_irq_func); +} + +void os_radio (u8_t mode) { + switch (mode) { + case RADIO_STOP: + radio_stop(); + break; + + case RADIO_TX: + radio_stop(); +#ifdef DEBUG_TX + if( isFsk(LMIC.rps) ) { + debug_printf("TX[mod=FSK,nocrc=%d", getNocrc(LMIC.rps)); + } else { + ASSERT(isLora(LMIC.rps)); + debug_printf("TX[mod=LoRa,sf=%d,bw=%d,cr=4/%d,nocrc=%d,ih=%d", + getSf(LMIC.rps) - SF7 + 7, 125 << (getBw(LMIC.rps) - BW125), + getCr(LMIC.rps) - CR_4_5 + 5, getNocrc(LMIC.rps), getIh(LMIC.rps)); + } + debug_printf_continue(",fcnt=%lu,freq=%.1F,pow=%d,len=%d%s]: %.80h\r\n", + (LMIC.seqnoUp ? LMIC.seqnoUp - 1 : 0), + LMIC.freq, 6, + LMIC.txpow, LMIC.dataLen, + (LMIC.pendTxPort != 0 && (LMIC.frame[OFF_DAT_FCT] & FCT_ADRARQ)) ? ",ADRARQ" : "", + LMIC.frame, LMIC.dataLen); +#endif + // transmit frame now (wait for completion interrupt) + radio_starttx(false); + // set timeout for tx operation (should not happen) + state.txmode = 1; + radio_set_irq_timeout(os_getTime() + ms2osticks(20) + LMIC_calcAirTime(LMIC.rps, LMIC.dataLen) * 110 / 100); + break; + + case RADIO_RX: + radio_stop(); +#ifdef DEBUG_RX + if( isFsk(LMIC.rps) ) { + debug_printf("RX_MODE[mod=FSK,nocrc=%d", getNocrc(LMIC.rps)); + } else { + ASSERT(isLora(LMIC.rps)); + debug_printf("RX_MODE[mod=LoRa,sf=%d,bw=%d,cr=4/%d,nocrc=%d,ih=%d", + getSf(LMIC.rps) - SF7 + 7, 125 << (getBw(LMIC.rps) - BW125), + getCr(LMIC.rps) - CR_4_5 + 5, getNocrc(LMIC.rps), getIh(LMIC.rps)); + } + debug_printf_continue(",freq=%.1F,rxtime=%.0F]\r\n", + LMIC.freq, 6, + LMIC.rxtime, 0); +#endif + // receive frame at rxtime/now (wait for completion interrupt) + radio_startrx(false); + // set timeout for rx operation (should not happen, might be updated by radio driver) + state.txmode = 0; + radio_set_irq_timeout(LMIC.rxtime + ms2osticks(5) + LMIC_calcAirTime(LMIC.rps, 255) * 110 / 100); + break; + + case RADIO_RXON: + radio_stop(); +#ifdef DEBUG_RX + if( isFsk(LMIC.rps) ) { + debug_printf("RXON_MODE[mod=FSK,nocrc=%d", getNocrc(LMIC.rps)); + } else { + ASSERT(isLora(LMIC.rps)); + debug_printf("RXON_MODE[mod=LoRa,sf=%d,bw=%d,cr=4/%d,nocrc=%d,ih=%d", + getSf(LMIC.rps) - SF7 + 7, 125 << (getBw(LMIC.rps) - BW125), + getCr(LMIC.rps) - CR_4_5 + 5, getNocrc(LMIC.rps), getIh(LMIC.rps)); + } + debug_printf_continue(",freq=%.1F]\r\n", LMIC.freq, 6); +#endif + // start scanning for frame now (wait for completion interrupt) + state.txmode = 0; + radio_startrx(true); + break; + + case RADIO_TXCW: + radio_stop(); + // transmit continuous wave (until abort) + radio_cw(); + break; + + case RADIO_CCA: + radio_stop(); + // clear channel assessment + radio_cca(); + break; + + case RADIO_INIT: + // reset and calibrate radio (uses LMIC.freq) + radio_init(true); + break; + + case RADIO_TXCONT: + radio_stop(); + radio_starttx(true); + break; + + case RADIO_CAD: + radio_stop(); + // set timeout for cad/rx operation (should not happen, might be updated by radio driver) + state.txmode = 0; + radio_set_irq_timeout(os_getTime() + ms2osticks(10) + LMIC_calcAirTime(LMIC.rps, 255) * 110 / 100); + // channel activity detection and rx if preamble symbol found + radio_cad(); + break; + } +} diff --git a/src/BasicMAC/lmic/region.h b/src/BasicMAC/lmic/region.h new file mode 100644 index 0000000..cbce9d6 --- /dev/null +++ b/src/BasicMAC/lmic/region.h @@ -0,0 +1,147 @@ +// Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. +// +// This file is subject to the terms and conditions defined in file 'LICENSE', +// which is part of this source code package. + +#ifndef _LMIC_REGION_h_ +#define _LMIC_REGION_h_ + +#include "board.h" + +// public region codes - DO NOT CHANGE! +enum { + REGCODE_UNDEF = 0, + REGCODE_EU868 = 1, + REGCODE_AS923 = 2, + REGCODE_US915 = 3, + REGCODE_AU915 = 4, + REGCODE_CN470 = 5, + REGCODE_IN865 = 6, +}; + +// ------------------------------------------------ +// EU868 +#ifdef CFG_eu868 + +#define REG_DYN +#define REG_DRTABLE_EU + +#endif + + +// ------------------------------------------------ +// AS923 +#ifdef CFG_as923 + +#define REG_DYN +#define REG_DRTABLE_EU + +#endif + + +// ------------------------------------------------ +// IL915 +#ifdef CFG_il915 + +#define REG_DYN +#define REG_DRTABLE_EU + +#endif + + +// ------------------------------------------------ +// KR920 +#ifdef CFG_kr920 + +#define REG_DYN +#define REG_DRTABLE_125kHz + +#endif + + +// ------------------------------------------------ +// US915 +#ifdef CFG_us915 + +#define REG_FIX +#define REG_DRTABLE_US + +#if MAX_FIX_CHNLS_125 < 64 +#undef MAX_FIX_CHNLS_125 +#define MAX_FIX_CHNLS_125 64 +#endif + +#if MAX_FIX_CHNLS_500 < 8 +#undef MAX_FIX_CHNLS_500 +#define MAX_FIX_CHNLS_500 8 +#endif + +#endif + +// ------------------------------------------------ +// AU915 +#ifdef CFG_au915 + +#define REG_FIX +#define REG_DRTABLE_AU + +#if MAX_FIX_CHNLS_125 < 64 +#undef MAX_FIX_CHNLS_125 +#define MAX_FIX_CHNLS_125 64 +#endif + +#if MAX_FIX_CHNLS_500 < 8 +#undef MAX_FIX_CHNLS_500 +#define MAX_FIX_CHNLS_500 8 +#endif + +#endif + + +// ------------------------------------------------ +// CN470 +#ifdef CFG_cn470 + +#define REG_FIX +#define REG_DRTABLE_125kHz + +#if MAX_FIX_CHNLS_125 < 96 +#undef MAX_FIX_CHNLS_125 +#define MAX_FIX_CHNLS_125 96 +#endif + +#endif + + +// ------------------------------------------------ +// IN865 +#ifdef CFG_in865 + +#define REG_DYN +#define REG_DRTABLE_IN + +#endif + + +// ------------------------------------------------ +// Sanity checks + +#if !defined(REG_DYN) && !defined(REG_FIX) +#error "No regions defined" +#endif + + +// ------------------------------------------------ +// Derived values + +#if defined(REG_FIX) + +#ifndef MAX_FIX_CHNLS_500 +#define MAX_FIX_CHNLS_500 0 +#endif + +#define MAX_FIX_CHNLS (MAX_FIX_CHNLS_125 + MAX_FIX_CHNLS_500) + +#endif + +#endif diff --git a/src/ByteArrayUtils.h b/src/ByteArrayUtils.h new file mode 100644 index 0000000..6a02d2c --- /dev/null +++ b/src/ByteArrayUtils.h @@ -0,0 +1,100 @@ +#ifndef _BYTE_ARRAY_UTILS_H +#define _BYTE_ARRAY_UTILS_H + +class ByteArrayUtils +{ +public: + static bool hexStrToBin(const char* hex, uint8_t* buf, int len) + { + const char* ptr = hex; + for (int i = 0; i < len; i++) + { + int val = hexTupleToByte(ptr); + if (val < 0) + { + return false; + } + buf[i] = val; + ptr += 2; + } + return true; + } + + static int hexTupleToByte(const char* hex) + { + int nibble1 = hexDigitToVal(hex[0]); + if (nibble1 < 0) + { + return -1; + } + int nibble2 = hexDigitToVal(hex[1]); + if (nibble2 < 0) + { + return -1; + } + return (nibble1 << 4) | nibble2; + } + + static int hexDigitToVal(char ch) + { + if (ch >= '0' && ch <= '9') + { + return ch - '0'; + } + if (ch >= 'A' && ch <= 'F') + { + return ch + 10 - 'A'; + } + if (ch >= 'a' && ch <= 'f') + { + return ch + 10 - 'a'; + } + return -1; + } + + static void binToHexStr(const uint8_t* buf, int len, char* hex) + { + for (int i = 0; i < len; i++) + { + uint8_t b = buf[i]; + *hex = valToHexDigit((b & 0xf0) >> 4); + hex++; + *hex = valToHexDigit(b & 0x0f); + hex++; + } + } + + static char valToHexDigit(int val) { return "0123456789ABCDEF"[val]; } + + static void swapBytes(uint8_t* buf, int len) + { + uint8_t* p1 = buf; + uint8_t* p2 = buf + len - 1; + while (p1 < p2) + { + uint8_t t = *p1; + *p1 = *p2; + *p2 = t; + p1++; + p2--; + } + } + + static bool isAllZeros(const uint8_t* buf, int len) + { + for (int i = 0; i < len; i++) + { + if (buf[i] != 0) + { + return false; + } + } + return true; + } + +private: + ByteArrayUtils(); + ~ByteArrayUtils(); +}; + +#endif /* _BYTE_ARRAY_UTILS_H */ \ No newline at end of file diff --git a/src/EzLoRaWAN.cpp b/src/EzLoRaWAN.cpp new file mode 100644 index 0000000..36b4dc2 --- /dev/null +++ b/src/EzLoRaWAN.cpp @@ -0,0 +1,920 @@ +// +// +// +#include "EzLoRaWAN.h" +#include "oslmic_types.h" +#include "BasicMAC/hal/hal.h" +#include "ByteArrayUtils.h" +#include +#include "esp_log.h" +#include "esp_mac.h" +#include "helper.h" + +#define LOOP_LoRaWan_MS 1 + +/// The last sent sequence number we know, stored in RTC memory +RTC_DATA_ATTR uint32_t rtc_sequenceNumberUp = 0; +RTC_DATA_ATTR uint8_t rtc_app_session_key[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +RTC_DATA_ATTR uint8_t rtc_net_session_key[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +RTC_DATA_ATTR uint8_t rtc_dev_adr[4] = { 0, 0, 0, 0 }; +RTC_DATA_ATTR bool rtc_session = false; +//#define CFG_DEBUG + +// static osjobcb_t sendMsg; +EzLoRaWAN* EzLoRaWAN::_instance = 0; +Preferences prefs; + +// This EUI must be in little-endian format, so least-significant-byte first. +// When copying an EUI from ttnctl output, this means to reverse the bytes. +// For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 0x70. +// The order is swapped in provisioning_decode_keys(). +void os_getJoinEui(uint8_t* buf) { + EzLoRaWAN* ttn = EzLoRaWAN::getInstance(); + memcpy(buf, ttn->app_eui, 8); + +} + +// This should also be in little endian format, see above. +void os_getDevEui(u1_t* buf) +{ + EzLoRaWAN* ttn = EzLoRaWAN::getInstance(); + std::copy(ttn->dev_eui, ttn->dev_eui + 8, buf); +} + + + +// This key should be in big endian format (or, since it is not really a number +// but a block of memory, endianness does not really apply). In practice, a key +// taken from ttnctl can be copied as-is. +void os_getNwkKey(uint8_t* buf) { + EzLoRaWAN* ttn = EzLoRaWAN::getInstance(); + memcpy(buf, ttn->app_key, 16); + +} +uint8_t os_getRegion(void) { + EzLoRaWAN* ttn = EzLoRaWAN::getInstance(); + return LMIC_regionCode(0); +} + + +/************ + * Public + ************/ + +EzLoRaWAN* EzLoRaWAN::getInstance() +{ + return _instance; +} + +// --- Constructor +EzLoRaWAN::EzLoRaWAN() : + dev_eui{ 0, 0, 0, 0, 0, 0, 0, 0 }, + app_eui{ 0, 0, 0, 0, 0, 0, 0, 0 }, + app_key{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + _joined{ false }, + _provisioned{ false }, + _session{ false }, + LoRaWan_task_Handle{ NULL }, + _message{ 0 }, + _length{ 1 }, + _port{ 1 }, + _confirm{ 0 }, + txInterval{ 0 }, + cyclique{ false } +{ + _instance = this; +} + +void EzLoRaWAN::setPins(uint8_t nss, uint8_t rx, uint8_t tx, uint8_t rst, uint8_t dio0, uint8_t dio1, uint8_t dio2, uint8_t busy, uint8_t tcxo) +{ + + +} + +bool EzLoRaWAN::begin() +{ + // record self in a static so that we can dispatch events + // ASSERT(EzLoRaWAN::pLoRaWAN == this ||EzLoRaWAN::pLoRaWAN == NULL); + // pLoRaWAN = this; + return begin(&lmic_pins); +} + +bool EzLoRaWAN::begin(uint8_t nss, uint8_t rx, uint8_t tx, uint8_t rst, uint8_t dio0, uint8_t dio1, uint8_t dio2,uint8_t busy,uint8_t tcxo) +{ + +} + +bool EzLoRaWAN::begin( const lmic_pinmap* pPinmap) +{ + os_init((void*) &pPinmap); + + // Reset the MAC state. _session and pending data transfers will be discarded. + LMIC_reset(); + + return true; +} + +bool EzLoRaWAN::provision(const char* appEui, const char* appKey) +{ + uint8_t mac[6]; + esp_err_t err = esp_efuse_mac_get_default(mac); + ESP_ERROR_CHECK(err); + dev_eui[7] = mac[0]; + dev_eui[6] = mac[1]; + dev_eui[5] = mac[2]; + dev_eui[4] = 0xff; + dev_eui[3] = 0xfe; + dev_eui[2] = mac[3]; + dev_eui[1] = mac[4]; + dev_eui[0] = mac[5]; +#ifdef CFG_DEBUG + Serial.print("dev EUI: "); + for (size_t i = 0; i < 8; i++) + { + Serial.printf("%02X", dev_eui[7 - i]); + } + Serial.println(); +#endif // CFG_DEBUG + + if (decode(false, nullptr, appEui, appKey)) + { + return saveKeys(); + } + return false; +} + +bool EzLoRaWAN::provision(const char* devEui, const char* appEui, const char* appKey) +{ + if (decode(true, devEui, appEui, appKey)) + { + return saveKeys(); + } + return false; +} + +bool EzLoRaWAN::provisionABP(const char* devAddr, const char* nwkSKey, const char* appSKey) +{ + ByteArrayUtils::hexStrToBin(nwkSKey, rtc_net_session_key, 16); + ByteArrayUtils::hexStrToBin(appSKey, rtc_app_session_key, 16); + ByteArrayUtils::hexStrToBin(devAddr, rtc_dev_adr, 4); + return saveKeys(); +} + +bool EzLoRaWAN::join() +{ + bool success = false; + + if (!_provisioned && !_session) + { + restoreKeys(); + } + // Check if this is a cold boot + if (_session && rtc_sequenceNumberUp != 0) + { + Serial.println(F("Using stored _session to join")); + devaddr_t dev_addr = rtc_dev_adr[0] << 24 | rtc_dev_adr[1] << 16 | rtc_dev_adr[2] << 8 | rtc_dev_adr[3]; + personalize(0x13, dev_addr, rtc_net_session_key, rtc_app_session_key); + success = true; + } + else if (_provisioned) + { + Serial.println(F("Using stored keys to join")); + //LMIC_setClockError(MAX_CLOCK_ERROR * 7 / 100); + //LMIC_unjoin(); + LMIC_startJoining(); + xTaskCreatePinnedToCore(loopStack, "ttnTask", 2048, (void*)1, (5 | portPRIVILEGE_BIT), &LoRaWan_task_Handle, 1); + success = true; + } + else + { + ESP_LOGW(TAG, "Device EUI, App EUI and/or App key have not been provided"); + Serial.println(F("Cannot join. No keys provided")); + } + return success; +} + +bool EzLoRaWAN::join(const char* appEui, const char* appKey, int8_t retries, uint32_t retryDelay) +{ + bool force = (getAppEui() != appEui) || (getAppKey() != appKey); + if (force || !_provisioned) + { + provision(appEui, appKey); + } + return join(); +} + +bool EzLoRaWAN::join(const char* devEui, const char* appEui, const char* appKey, int8_t retries, uint32_t retryDelay) +{ + bool force = (getAppEui() != appEui) || (getDevEui() != devEui) || (getAppKey() != appKey); + if (force || !_provisioned) + { + ESP_LOGI(TAG, "provisionning !"); + provision(devEui, appEui, appKey); + } + return join(); +} + +bool EzLoRaWAN::personalize() +{ + bool success; + // restoreKeys(false); + if (!ByteArrayUtils::isAllZeros(rtc_dev_adr, sizeof(rtc_dev_adr)) + && !ByteArrayUtils::isAllZeros(rtc_net_session_key, sizeof(rtc_net_session_key)) + && !ByteArrayUtils::isAllZeros(rtc_app_session_key, sizeof(rtc_app_session_key))) + { + devaddr_t dev_addr = rtc_dev_adr[0] << 24 | rtc_dev_adr[1] << 16 | rtc_dev_adr[2] << 8 | rtc_dev_adr[3]; + personalize(0x13, dev_addr, rtc_net_session_key, rtc_app_session_key); + success = true; + } + else + { + success = false; + } + return success; +} + +bool EzLoRaWAN::personalize(const char* devAddr, const char* nwkSKey, const char* appSKey) +{ + ByteArrayUtils::hexStrToBin(nwkSKey, rtc_net_session_key, 16); + ByteArrayUtils::hexStrToBin(appSKey, rtc_app_session_key, 16); + ByteArrayUtils::hexStrToBin(devAddr, rtc_dev_adr, 4); + devaddr_t dev_addr = rtc_dev_adr[0] << 24 | rtc_dev_adr[1] << 16 | rtc_dev_adr[2] << 8 | rtc_dev_adr[3]; +#ifdef CFG_DEBUG + ESP_LOGI(TAG, "Dev adr str: %s", devAddr); + ESP_LOGI(TAG, "Dev adr int: %X", dev_addr); +#endif // CFG_DEBUG + personalize(0x13, dev_addr, rtc_net_session_key, rtc_app_session_key); + return true; +} + +bool EzLoRaWAN::sendBytes(uint8_t* payload, size_t length, uint8_t port, uint8_t confirm) +{ + cyclique = false; + return txBytes(payload, length, port, confirm); +} + +void EzLoRaWAN::sendBytesAtInterval(uint8_t* payload, size_t length, uint32_t interval, uint8_t port, uint8_t confirm) +{ + cyclique = (interval != 0) ? true : false; + txInterval = interval; + txBytes(payload, length, port, confirm); +} + +bool EzLoRaWAN::poll(uint8_t port, uint8_t confirm) +{ + return sendBytes(0, 1, port, confirm); +} + +bool EzLoRaWAN::stop() +{ + ESP_LOGI(TAG, "LoRaWan_task=%d", LoRaWan_task_Handle); + if (LoRaWan_task_Handle != NULL) + { + ESP_LOGI(TAG, "delete ttn task"); + vTaskDelete(LoRaWan_task_Handle); + LoRaWan_task_Handle = NULL; + } + return true; +} + +bool EzLoRaWAN::isRunning(void) +{ + if (LoRaWan_task_Handle == NULL) + { + return false; + } + return true; +} + + +void EzLoRaWAN::onMessage(void(*callback)(const uint8_t* payload, size_t size, int rssi)) +{ + messageCallback = callback; +} + +void EzLoRaWAN::onMessage(void(*callback)(const uint8_t* payload, size_t size, uint8_t port, int rssi)) +{ + messageCallbackPort = callback; +} + +void EzLoRaWAN::onConfirm(void(*callback)()) +{ + confirmCallback = callback; +} + + +void EzLoRaWAN::onEvent(void(*callback)(const ev_t event)) +{ + eventCallback = callback; +} + +bool EzLoRaWAN::isJoined() +{ + return _joined; +} + +bool EzLoRaWAN::isTransceiving() +{ + return LMIC.opmode & OP_TXRXPEND; +} + +uint32_t EzLoRaWAN::waitForPendingTransactions() +{ + uint32_t waited = 0; + while (isTransceiving()) + { + waited += 100; + delay(100); + } + return waited; +} + +bool EzLoRaWAN::hasSession() +{ + return _session; +} + +bool EzLoRaWAN::isProvisioned() +{ + return _provisioned; +} + +bool EzLoRaWAN::saveKeys() +{ + bool success = false; + if (prefs.begin(NVS_FLASH_PARTITION, false, NVS_FLASH_PARTITION)) + { + if (prefs.putBytes(NVS_FLASH_KEY_DEV_EUI, dev_eui, sizeof(dev_eui)) + && prefs.putBytes(NVS_FLASH_KEY_APP_EUI, app_eui, sizeof(app_eui)) + && prefs.putBytes(NVS_FLASH_KEY_APP_KEY, app_key, sizeof(app_key))) + { + success = true; + } + prefs.end(); + } + + return success; +} + +bool EzLoRaWAN::restoreKeys(bool silent) +{ + if (prefs.begin(NVS_FLASH_PARTITION, true, NVS_FLASH_PARTITION)) { + uint8_t buf_dev_eui[8]; + uint8_t buf_app_eui[8]; + uint8_t buf_app_key[16]; + if (prefs.getBytes(NVS_FLASH_KEY_DEV_EUI, buf_dev_eui, sizeof(dev_eui)) + && prefs.getBytes(NVS_FLASH_KEY_APP_EUI, buf_app_eui, sizeof(app_eui)) + && prefs.getBytes(NVS_FLASH_KEY_APP_KEY, buf_app_key, sizeof(app_key))) + { + std::copy(buf_dev_eui, buf_dev_eui + 8, dev_eui); + std::copy(buf_app_eui, buf_app_eui + 8, app_eui); + std::copy(buf_app_key, buf_app_key + 16, app_key); + + checkKeys(); + + if (_provisioned) + { + ESP_LOGI(TAG, "Dev and app EUI and app key have been restored from NVS storage"); + } + else + { + ESP_LOGW(TAG, "Dev and app EUI and app key are invalid (zeroes only)"); + } + } + else + { + _provisioned = false; + } + prefs.end(); + } + return _provisioned; +} + +bool EzLoRaWAN::eraseKeys() +{ + bool success = false; + uint8_t emptyBuf[16] = { 0 }; + if (prefs.begin(NVS_FLASH_PARTITION, false, NVS_FLASH_PARTITION)) + { + if(prefs.remove(NVS_FLASH_KEY_DEV_EUI) + && prefs.remove(NVS_FLASH_KEY_APP_EUI) + && prefs.remove(NVS_FLASH_KEY_APP_KEY) + ) + { + success = true; + ESP_LOGI(TAG, "Dev EUI, app EUI and app key erased in NVS storage"); + } + prefs.end(); + } + return success; +} + + +void EzLoRaWAN::showStatus() +{ + if (poll()) + { + char buffer[64]; + Serial.println("---------------Status--------------"); + Serial.println("Device EUI: " + getDevEui()); + Serial.println("Application EUI: " + getAppEui()); + Serial.print("netid: "); + Serial.println(LMIC.netid, HEX); + Serial.print("devaddr: "); + Serial.println(LMIC.devaddr, HEX); + Serial.print("NwkSKey: "); + for (size_t i = 0; i < sizeof(LMIC.lceCtx.nwkSKey); ++i) + { + Serial.printf("%02X",LMIC.lceCtx.nwkSKey[i]); + + } + Serial.print("\nAppSKey: "); + for (size_t i = 0; i < sizeof(LMIC.lceCtx.appSKey); ++i) + { + Serial.printf("%02X", LMIC.lceCtx.appSKey[i]); + } + Serial.printf("\ndata rate: %d\n", LMIC.datarate); + Serial.printf("tx power: %ddB\n", LMIC.txpow); + Serial.printf("freq: %dHz\n", LMIC.freq); + Serial.println("-----------------------------------"); + } +} + +uint8_t EzLoRaWAN::getDatarate() +{ + return LMIC.datarate; +} + +bool EzLoRaWAN::setDataRate(uint8_t rate) +{ + LMIC.datarate = rate; + return true; +} + +void EzLoRaWAN::setTXInterval(uint32_t interval = 60) +{ + /*_interval=INTERVAL;*/ + txInterval = interval; +} + +size_t EzLoRaWAN::getAppEui(byte* buf) +{ + memcpy(buf, app_eui, 8); + ByteArrayUtils::swapBytes(buf, 8); + return 8; +} + +size_t EzLoRaWAN::getAppKey(byte* buf) +{ + memcpy(buf, app_key, 16); + return 16; +} +String EzLoRaWAN::getAppKey() +{ + char hexbuf[33] = { 0 }; + for (size_t i = 0; i < 16; i++) + { + sprintf(hexbuf + (2 * i), "%02X", app_key[i]); + } + return String(hexbuf); +} +String EzLoRaWAN::getAppEui() +{ + char hexbuf[17] = { 0 }; + for (size_t i = 0; i < 8; i++) + { + sprintf(hexbuf + (2 * i), "%02X", app_eui[7 - i]); + } + return String(hexbuf); +} + + +size_t EzLoRaWAN::getDevEui(byte* buf, bool hardwareEUI) +{ + if (hardwareEUI) + { + uint8_t mac[6]; + esp_err_t err = esp_efuse_mac_get_default(mac); + ESP_ERROR_CHECK(err); + buf[7] = mac[0]; + buf[6] = mac[1]; + buf[5] = mac[2]; + buf[4] = 0xff; + buf[3] = 0xfe; + buf[2] = mac[3]; + buf[1] = mac[4]; + buf[0] = mac[5]; + } + else { + memcpy(buf, dev_eui, 8); + ByteArrayUtils::swapBytes(buf, 8); + } + return 8; +} + +String EzLoRaWAN::getDevEui(bool hardwareEUI) +{ + char hexbuf[17] = { 0 }; + if (hardwareEUI) + { + uint8_t mac[6]; + + esp_err_t err = esp_efuse_mac_get_default(mac); + ESP_ERROR_CHECK(err); + ByteArrayUtils::binToHexStr(mac, 6, hexbuf); + for (size_t i = 0; i < 6; i++) + { + hexbuf[15 - i] = hexbuf[11 - i]; + } + hexbuf[9] = 'E'; + hexbuf[8] = 'F'; + hexbuf[7] = 'F'; + hexbuf[6] = 'F'; + } + else + { + for (size_t i = 0; i < 8; i++) + { + sprintf(hexbuf + (2 * i), "%02X", dev_eui[7 - i]); + } + } + return String(hexbuf); +} + +String EzLoRaWAN::getMac() +{ + uint8_t mac[6]; + esp_err_t err = esp_efuse_mac_get_default(mac); + ESP_ERROR_CHECK(err); + char buf[20]; + for (size_t i = 0; i < 5; i++) + { + sprintf(buf + (3 * i), "%02X:", mac[i]); + } + sprintf(buf + (3 * 5), "%02X", mac[5]); + + return String(buf); +} + +uint8_t EzLoRaWAN::getPort() +{ + return _port; +} + +uint32_t EzLoRaWAN::getFrequency() +{ + return LMIC.freq; +} + +int8_t EzLoRaWAN::getTXPower() +{ + return LMIC.txpow; +} + +bool EzLoRaWAN::setDevEui(byte* value) +{ + memcpy(dev_eui, value, 8); + return true; +} + +bool EzLoRaWAN::setAppEui(byte* value) +{ + memcpy(app_eui, value, 8); + return true; +} + +bool EzLoRaWAN::setAppKey(byte* value) +{ + memcpy(app_key, value, 16); + return true; +} + + + + +/************ + * Private + ************/ + +bool EzLoRaWAN::txBytes(uint8_t* payload, size_t length, uint8_t port, uint8_t confirm) +{ + _message = payload; + _length = length; + _port = port; + _confirm = confirm; + + txMessage(&sendjob); + if ((LMIC.opmode & OP_TXDATA) == 0) + { + return false; + } + + return true; +} + +void EzLoRaWAN::txMessage(osjob_t* job) +{ + if (_joined) + { + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) + { +#ifdef CFG_DEBUG + Serial.println(F("Pending transaction, not sending")); +#endif // CFG_DEBUG + } + else + { + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(_port, _message, _length, _confirm); +#ifdef CFG_DEBUG + Serial.println(F("Packet queued")); +#endif // CFG_DEBUG + } + // Next TX is scheduled after TX_COMPLETE event. + } +#ifdef CFG_DEBUG + else + { + Serial.println(F("Not connected/joined to TTN")); + } +#endif // CFG_DEBUG + } + +void EzLoRaWAN::personalize(u4_t netID, u4_t DevAddr, uint8_t* NwkSKey, uint8_t* AppSKey) +{ +// LMIC_setClockError(MAX_CLOCK_ERROR * 7 / 100); + LMIC_reset(); + LMIC_setSession(netID, DevAddr, NwkSKey, AppSKey); + enum { BAND_MILLI = 0, BAND_CENTI = 1, BAND_DECI = 2, BAND_AUX = 3 }; +#if defined(CFG_eu868) + // Set up the channels used by the Things Network, which corresponds + // to the defaults of most gateways. Without this, only three base + // channels from the LoRaWAN specification are used, which certainly + // works, so it is good for debugging, but can overload those + // frequencies, so be sure to configure the full frequency range of + // your network here (unless your network autoconfigures them). + // Setting up channels should happen after LMIC_setSession, as that + // configures the minimal channel set. + // NA-US channels 0-71 are configured automatically + enum { + DR_SF12 = 0, + DR_SF11 = 1, + DR_SF10 = 2, + DR_SF9 = 3, + DR_SF8 = 4, + DR_SF7 = 5, + DR_SF7_BW250 = 6, + DR_FSK = 7, + }; + LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7)); // g-band + LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7_BW250)); // g-band + LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7)); // g-band + LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7)); // g-band + LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7)); // g-band + LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7)); // g-band + LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7)); // g-band + LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7)); // g-band + LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK)); // g2-band +#endif + // Disable link check validation + LMIC_setLinkCheckMode(0); + + // TTN uses SF9 for its RX2 window. + LMIC.dn2Dr = DR_SF9; + + // Set data rate and transmit power for uplink + LMIC_setDrTxpow(DR_SF7, 14); + + // Set the sequence number of the packet + LMIC.seqnoUp=(rtc_sequenceNumberUp); + + // Start job + _joined = true; + xTaskCreatePinnedToCore(loopStack, "ttnTask", 2048, (void*)1, (5 | portPRIVILEGE_BIT), &LoRaWan_task_Handle, 1); +} + +bool EzLoRaWAN::decode(bool includeDevEui, const char* devEui, const char* appEui, const char* appKey) +{ + uint8_t buf_dev_eui[8]; + uint8_t buf_app_eui[8]; + uint8_t buf_app_key[16]; + + if (includeDevEui && (strlen(devEui) != 16 || !ByteArrayUtils::hexStrToBin(devEui, buf_dev_eui, 8))) + { + ESP_LOGW(TAG, "Invalid device EUI: %s", devEui); + return false; + } + + if (includeDevEui) + { + ByteArrayUtils::swapBytes(buf_dev_eui, 8); + } + + if (strlen(appEui) != 16 || !ByteArrayUtils::hexStrToBin(appEui, buf_app_eui, 8)) + { + ESP_LOGW(TAG, "Invalid application EUI: %s", appEui); + return false; + } + + ByteArrayUtils::swapBytes(buf_app_eui, 8); + + if (strlen(appKey) != 32 || !ByteArrayUtils::hexStrToBin(appKey, buf_app_key, 16)) + { + ESP_LOGW(TAG, "Invalid application key: %s", appKey); + return false; + } + + if (includeDevEui) + { + std::copy(buf_dev_eui, buf_dev_eui + 8, dev_eui); + } + std::copy(buf_app_eui, buf_app_eui + 8, app_eui); + std::copy(buf_app_key, buf_app_key + 16, app_key); + + checkKeys(); + + return true; +} + +void EzLoRaWAN::checkKeys() +{ + _provisioned = !ByteArrayUtils::isAllZeros(dev_eui, sizeof(dev_eui)) + //&& !ByteArrayUtils::isAllZeros(app_eui, sizeof(app_eui)) + && !ByteArrayUtils::isAllZeros(app_key, sizeof(app_key)); + _session = !ByteArrayUtils::isAllZeros(rtc_dev_adr, sizeof(rtc_dev_adr)) + && !ByteArrayUtils::isAllZeros(rtc_app_session_key, sizeof(rtc_app_session_key)) + && !ByteArrayUtils::isAllZeros(rtc_net_session_key, sizeof(rtc_net_session_key)) && rtc_sequenceNumberUp != 0x00; + +#ifdef CFG_DEBUG + Serial.print(F("[checkKeys] ")); + if (_provisioned) + { + Serial.print(F("_provisioned, ")); + } + else + { + Serial.print(F("unprovisioned, ")); + } + if (_session) + { + Serial.println(F("_session ")); +} + else + { + Serial.println(F("no _session ")); + } +#endif + } + +void EzLoRaWAN::loopStack(void* parameter) +{ + for (;;) + { + os_runstep(); + vTaskDelay(LOOP_LoRaWan_MS / portTICK_PERIOD_MS); + } +} +// this macro can be used to initalize a normal table of event strings +#define LMIC_EVENT_NAME_TABLE__INIT \ + "<>", \ + "EV_SCAN_TIMEOUT", "EV_BEACON_FOUND", \ + "EV_BEACON_MISSED", "EV_BEACON_TRACKED", "EV_JOINING", \ + "EV_JOINED", "EV_RFU1", "EV_JOIN_FAILED", "EV_REJOIN_FAILED", \ + "EV_TXCOMPLETE", "EV_LOST_TSYNC", "EV_RESET", \ + "EV_RXCOMPLETE", "EV_LINK_DEAD", "EV_LINK_ALIVE", "EV_SCAN_FOUND", \ + "EV_TXSTART", "EV_TXCANCELED", "EV_RXSTART", "EV_JOIN_TXCOMPLETE" +static const char* const eventNames[] = { LMIC_EVENT_NAME_TABLE__INIT }; + +void onLmicEvent(ev_t event) +{ + EzLoRaWAN* ttn = EzLoRaWAN::getInstance(); +#ifdef CFG_DEBUG + Serial.print(os_getTime()); + Serial.print(": "); + // get event message + if (event < sizeof(eventNames) / sizeof(eventNames[0])) + { + Serial.print("[Event] "); + Serial.println((eventNames[event] + 3)); // +3 to strip "EV_" + } + else + { + Serial.print("[Event] Unknown: "); + Serial.println(event); + } +#endif // CFG_DEBUG + switch (event) + { + case EV_JOINING: + ttn->_joined = false; + break; + case EV_JOINED: + { + u4_t netid = LMIC.netid; + devaddr_t devaddr = LMIC.devaddr; + memcpy(rtc_app_session_key, LMIC.lceCtx.appSKey, sizeof(LMIC.lceCtx.appSKey)); + memcpy(rtc_net_session_key, LMIC.lceCtx.nwkSKey, sizeof(LMIC.lceCtx.nwkSKey)); + lce_loadSessionKeys(rtc_net_session_key, rtc_app_session_key); + rtc_dev_adr[0] = (devaddr >> 24) & 0xFF; + rtc_dev_adr[1] = (devaddr >> 16) & 0xFF; + rtc_dev_adr[2] = (devaddr >> 8) & 0xFF; + rtc_dev_adr[3] = devaddr & 0xFF; + +#ifdef CFG_DEBUG + Serial.println(F("EV_JOINED")); + Serial.print("netid: "); + Serial.println(netid, DEC); + Serial.print("devAdr: "); + Serial.println(devaddr, HEX); + Serial.print("artKey: "); + for (size_t i = 0; i < sizeof(artKey); ++i) + { + Serial.print(artKey[i], HEX); + } + Serial.println(""); + Serial.print("nwkKey: "); + for (size_t i = 0; i < sizeof(nwkKey); ++i) + { + Serial.print(nwkKey[i], HEX); + } + Serial.println(); +#endif // CFG_DEBUG + } + ttn->_joined = true; + // Disable link check validation (automatically enabled + // during join, but because slow data rates change max TX + // size, we don't use it in this example.) + LMIC_setLinkCheckMode(0); + break; + case EV_JOIN_FAILED: + ttn->_joined = false; + break; + case EV_TXCOMPLETE: + rtc_sequenceNumberUp = LMIC.seqnoUp; + + if (LMIC.txrxFlags & TXRX_ACK) + { + if (ttn->confirmCallback) + { + ttn->confirmCallback(); + } +#ifdef CFG_DEBUG + Serial.printf("txrxFlags : %02X\n", LMIC.txrxFlags); + Serial.println(F("Received ack")); +#endif // CFG_DEBUG + } + + if (LMIC.dataLen) + { +#ifdef CFG_DEBUG + Serial.print(F("Received ")); + Serial.print(LMIC.dataLen); + Serial.print(F(" bytes of payload. FPORT: ")); + Serial.println(LMIC.frame[LMIC.dataBeg-1]); + String JSONMessage = ""; + + for (byte i = LMIC.dataBeg; i < LMIC.dataBeg + LMIC.dataLen; i++) + { + JSONMessage += (char)LMIC.frame[i]; + } + Serial.println(JSONMessage); +#endif // CFG_DEBUG + + if (ttn->messageCallback) + { + uint8_t downlink[LMIC.dataLen]; + uint8_t offset = LMIC.dataBeg; //9;// offset to get data. + std::copy(LMIC.frame + offset, LMIC.frame + offset + LMIC.dataLen, downlink); + ttn->messageCallback(downlink, LMIC.dataLen, LMIC.rssi); + } + if (ttn->messageCallbackPort) + { + uint8_t downlink[LMIC.dataLen]; + uint8_t offset = LMIC.dataBeg; //9;// offset to get data. + std::copy(LMIC.frame + offset, LMIC.frame + offset + LMIC.dataLen, downlink); + ttn->messageCallbackPort(downlink, LMIC.dataLen, LMIC.frame[LMIC.dataBeg-1], LMIC.rssi); + } + } + // Schedule next transmission + // if (cyclique) + // { + // os_setTimedCallback(&ttn.sendjob, os_getTime() + sec2osticks(txInterval), ttn.txMessage); + // } + break; + case EV_RESET: + ttn->_joined = false; + break; + case EV_LINK_DEAD: + ttn->_joined = false; + break; + + default: + break; + } + + if (ttn->eventCallback) + { + ttn->eventCallback(event); + } +} diff --git a/src/EzLoRaWAN.h b/src/EzLoRaWAN.h new file mode 100644 index 0000000..9ffc246 --- /dev/null +++ b/src/EzLoRaWAN.h @@ -0,0 +1,469 @@ +// EzLoRaWAN.h + +#ifndef _LoRaWan_ESP32_h +#define _LoRaWan_ESP32_h + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" + +#else +#include "WProgram.h" + +#endif + +#include "oslmic_types.h" +#include "BasicMAC/basicmac.h" +#include "BasicMAC/hal/hal.h" +#include "SPI.h" + +class EzLoRaWAN +{ +public: + /// + /// Default constructor + /// Silently tries to restore the keys + /// + EzLoRaWAN(); + /// + /// Get the instance of EzLoRaWAN + /// + /// @return The singleton instance of EzLoRaWAN + /// + static EzLoRaWAN* getInstance(); + + /// + /// Disallow copying + /// + EzLoRaWAN(const EzLoRaWAN& ref) = delete; + /// + /// set lmic_pins + /// + void setPins(uint8_t nss, uint8_t rx, uint8_t tx, uint8_t rst, uint8_t dio0, uint8_t dio1, uint8_t dio2, uint8_t busy, uint8_t tcxo); + /// Start the LMIC stack with Pre-integrated boards + /// + /// @return True if the radio has been initialised, false if not + /// + bool begin(); + + /// + /// Initialize the LMIC stack with pinout as arguments + /// + /// @param nss The pin for chip select + /// @param rx The pin for rx control + /// @param tx The pin for tx control + /// @param rst The pin for reset + /// @param dio0 The DIO0 pin + /// @param dio1 The DOI1 pin + /// @param dio2 The DIO2 pin + /// @param busy The Busy pin + /// @param tcxo The tcxo pin + /// @return True if the radio has been initialised, false if not + /// + bool begin(uint8_t nss, uint8_t rx, uint8_t tx, uint8_t rst, uint8_t dio0, uint8_t dio1, uint8_t dio2, uint8_t busy, uint8_t tcxo); + + /// + /// Initialize the stack with pointer to pin mapping + /// + /// @param pPinmap A pointer to the HalPinmap_t object + /// @return True if the radio has been initialised, false if not + /// + bool begin(const lmic_pinmap* pPinmap); + + /// + /// Provision the Application EUI and Application key + /// @param appEui The Application EUI in MSB order + /// @param appKey The Application key in MSB order + /// @return True on success, false if not + /// + bool provision(const char* appEui, const char* appKey); + + /// + /// Provision the Application EUI, Application key and Device EUI + /// @param devEui The Device EUI in MSB order + /// @param appEui The Application EUI in MSB order + /// @param appKey The Application key in MSB order + /// @return True on success, false if not + /// + bool provision(const char* devEui, const char* appEui, const char* appKey); + + /// + /// Provision the Device address, Network key and Application key + /// @param devAddr The Device address in MSB order + /// @param nwkSKey The Network session key in MSB order + /// @param appSKey The Application session key in MSB order + /// @return True on success, false if not + /// + bool provisionABP(const char* devAddr, const char* nwkSKey, const char* appSKey); + + /// + /// Join the TTN + /// + /// Will check whether the keys are provided, if not try to load them from + /// non volatile memory and if present join the network + /// @return True if keys are present and join started, false if not + /// + bool join(); + + /// + /// Join the TTN + /// + /// Will provision the keys and join the network + /// \note { + /// retries and retryDelay are not yet implemented + /// } + /// @param appEui The Application EUI in MSB order + /// @param appKey The Application key in MSB order + /// @param force Force the provisioning + /// @param retries The number of retries until timeout + /// @param retryDelay The delay in milliseconds between each retry + /// + bool join( + const char* appEui, const char* appKey, int8_t retries = -1, uint32_t retryDelay = 10000); + + /// + /// Join the TTN + /// + /// Will provision the keys and join the network + /// \note { + /// retries and retryDelay are not yet implemented + /// } + /// @param devEui The Device EUI in MSB order + /// @param appEui The Application EUI in MSB order + /// @param appKey The Application key in MSB order + /// @param force Force the provisioning + /// @param retries The number of retries until timeout + /// @param retryDelay The delay in milliseconds between each retry + /// + bool join(const char* devEui, const char* appEui, const char* appKey, int8_t retries = -1, + uint32_t retryDelay = 10000); + + /// + /// Activate the device via ABP. + /// + /// @return True if the activation was successful, false if not + /// + bool personalize(); + + /// + /// Activate the device via ABP. + /// + /// @param devAddr Device address assigned to the device + /// @param nwkSKey Network session key assigned to the device for identification + /// @param appSKey Application session key assigned to the device for encryption + /// @return True if the activation was successful, false if not + /// + bool personalize(const char* devAddr, const char* nwkSKey, const char* appSKey); + + /// + /// Send a message to the application using raw bytes. + /// + /// @param payload The payload to send + /// @param length The size of the payload, use sizeof(payload) + /// @param port The optional port to address, default 1 + /// @param confirm Whether to ask for confirmation, default 0(false) + /// + bool sendBytes(uint8_t* payload, size_t length, uint8_t port = 1, uint8_t confirm = 0); + + /// + /// Send a message to the application at an interval using raw bytes. + /// + /// \note {Currently not implemented, only sends once} + /// + /// @param payload The payload to send + /// @param length The size of the payload, use sizeof(payload) + /// @param interval The interval in seconds to send at + /// @param port The optional port to address, default 1 + /// @param confirm Whether to ask for confirmation, default 0(false) + /// + void sendBytesAtInterval( + uint8_t* payload, size_t length, uint32_t interval = 60, uint8_t port = 1, uint8_t confirm = 0); + + /// + /// Poll for incoming messages + /// + /// Calls sendBytes() with { 0x00 } as payload. + /// + /// @param port The port to address, default 1 + /// @param confirm Whether to ask for confirmation, default 0(false) + /// @return True on success, false if not + /// + bool poll(uint8_t port = 1, uint8_t confirm = 0); + + /// + /// Stops the TTN task, which runs the stack + /// + /// @return True if task was stopped + /// + bool stop(void); + bool isRunning(void); + /// + /// Sets a function which will be called to process incoming messages + /// + /// You'll want to do this in your setup() function and then define a void (*callback)(const byte* payload, size_t + /// length, int rssi) function somewhere else in your sketch. + /// + /// @param callback The callback which gets called + /// + void onMessage(void(*callback)(const uint8_t* payload, size_t size, int rssi)); + /// + /// Sets a function which will be called to process incoming messages + /// + /// You'll want to do this in your setup() function and then define a void (*callback)(const byte* payload, size_t + /// length, uint8_t port, int rssi) function somewhere else in your sketch. + /// + /// @param callback The callback which gets called + /// + void onMessage(void(*callbackPort)(const uint8_t* payload, size_t size, uint8_t port, int rssi)); + /// + /// Sets a function which will be called upon LMIC events occur + /// + /// It will get called after the internal handling of the event + /// + /// @param callback The callback which gets called + /// + void onEvent(void(*callback)(const ev_t event)); + /// + /// Sets a function which will be called upon LMIC confirmation (if asked for confirmation) + /// + /// + /// @param callback The callback which gets called + /// + void onConfirm(void(*callback)()); + + /// + /// Check whether we have joined TTN + /// @return True when joined, false if not + /// + bool isJoined(); + + /// + /// Check whether LMIC stack is trasnmitting or receiving + /// + /// @return True if so, false if not + /// + bool isTransceiving(); + + /// + /// Wait until all pending transactions have been handled + /// + /// Busy wait using delay + /// + /// @return The number of milliseconds we have waited + /// + uint32_t waitForPendingTransactions(); + + /// + /// Check whether the Device address, Network session key and Application session key + /// are present + /// + bool hasSession(); + + /// + /// Check whether the Device EUI, Application EUI and Application key + /// are present + /// + /// @return True if provisioned, false if not + /// + bool isProvisioned(); + + /// + /// Store the Device EUI, Application EUI, Application key, + /// Device address, Network session key and Application session key + /// in the non volatile memory + /// + bool saveKeys(); + + /// + /// Restore the Device EUI, Application EUI, Application key, + /// Device address, Network session key and Application session key + /// from the non volatile memory + /// @param silent Set to false for debug output + /// + bool restoreKeys(bool silent = true); + /// + /// Erase the Device EUI, Application EUI, Application key, + /// Device address, Network session key and Application session key + /// in the non volatile memory. All the bytes are 00. + /// + bool eraseKeys(); + // + /// Store the current frame counter in NVM + /// + bool storeFrameCounter(); + + // + /// Get the current frame counter from NVM + /// + uint32_t getFrameCounter(); + + /// + /// Show the current status + /// + /// Prints the current status to the serial console + /// Included are Device EUI, Application EUI, netId, Device address, Network + /// session key, Application session key, data rate, tx power and frequency + /// + void showStatus(); + + /// + /// Get the currently used data rate + /// + /// @return The data rate + uint8_t getDatarate(); + + /// + /// Set the data rate of the radio + /// + /// @return True if data rate was set, false if not + /// + bool setDataRate(uint8_t rate = 7); + + /// + /// Set the interval at which to send cyclic data + /// + /// \note {You must call \ref sendBytesAtInterval first, otherwise this will have no effect} + /// + /// @param interval The interval to set + /// + void setTXInterval(const uint32_t interval); + + /// + /// Copy the Application EUI into the given buffer + /// @param buffer The buffer + /// @param size The size of the buffer + /// @return The number of bytes copied to the buffer + /// + //size_t getAppEui(char* buffer, size_t size); + size_t getAppEui(byte* buf); + + /// + /// Get the Application EUI as a String + /// @return A string containing the Application EUI + /// + String getAppEui(); + + /// + /// Copy the Device EUI or MAC Address into the given buffer + /// @param buffer The buffer + /// @param size The size of the buffer + /// @param hardwareEui Set true if you want to get the MAC Address of the ESP + /// @return The number of bytes copied to the buffer + /// + //size_t getDevEui(char* buffer, size_t size, bool hardwareEui = false); + size_t getDevEui(byte* buf, bool hardwareEui = false); + /// + /// Get the Device EUI or MAC Address as a String + /// @param hardwareEui Set true if you want to get the MAC Address of the ESP + /// @return A string containing the Device EUI or MAC Address + /// + String getDevEui(bool hardwareEui = false); + + + /// + /// Get the MAC address of the ESP + /// + /// @return The MAC address in Colon-Hexadecimal notation + /// + static String getMac(); + + /// + /// Get the currently used port that is addressed + /// + /// @return The port + /// + uint8_t getPort(); + + /// + /// Get the currently used frequency in Hz + /// + /// @return The frequency + uint32_t getFrequency(); + + /// + /// Get the currently used transmit power in dB + /// + /// @return The transmit power + int8_t getTXPower(); + + bool setDevEui(byte* value); + bool setAppEui(byte* value); + bool setAppKey(byte* value); + ; + +private: + + bool txBytes(uint8_t* payload, size_t length, uint8_t port, uint8_t confirm); + void txMessage(osjob_t* job); + void personalize(u4_t netID, u4_t DevAddr, uint8_t* NwkSKey, uint8_t* AppSKey); + bool decode(bool includeDevEui, const char* devEui, const char* appEui, const char* appKey); + void checkKeys(); + static void loopStack(void* parameter); + void(*messageCallback)(const uint8_t* payload, size_t size, int rssi) = NULL; + void(*messageCallbackPort)(const uint8_t* payload, size_t size, uint8_t port, int rssi) = NULL; + void(*confirmCallback)()=NULL; + void(*eventCallback)(const ev_t event) = NULL; + + /// + /// LMIC callback for getting the application key + /// + /// This is a friend so the function can access the private variables of this class so it can be declared globally + /// + //friend void os_getDevKey(xref2u1_t buf); + /// + /// LMIC callback for getting the application EUI + /// + /// This is a friend so the function can access the private variables of this class so it can be declared globally + /// + //friend void os_getArtEui(xref2u1_t buf); + /// + /// LMIC callback for getting the device EUI + /// + /// This is a friend so the function can access the private variables of this class so it can be declared globally + /// + friend void os_getDevEui(xref2u1_t buf); + /// + /// LMIC callback for events + /// + /// This is a friend so the function can access the private variables of this class so it can be declared globally + /// + friend void onLmicEvent(ev_t ev); + friend void os_getJoinEui(xref2u1_t buf); + friend void os_getNwkKey(xref2u1_t buf); + friend uint8_t os_getRegion(void); + + + +protected: + uint8_t dev_eui[8]; + uint8_t app_eui[8]; + uint8_t app_key[16]; + bool _joined; + /// +/// Copy the Application Key into the given buffer +/// @param buffer The buffer +/// @return The number of bytes copied to the buffer +/// + size_t getAppKey(byte* buf); + /// + /// Get the Appkey as String + /// @return A string containing the AppKey + /// + String getAppKey(); + +private: + bool _provisioned; + bool _session; + TaskHandle_t LoRaWan_task_Handle; + uint8_t* _message; + uint8_t _length; + uint8_t _port; + uint8_t _confirm; + uint32_t txInterval; + bool cyclique; + osjob_t sendjob; + +private: + static EzLoRaWAN* _instance; +}; + +#endif diff --git a/src/EzLoRaWAN_BLE.cpp.org b/src/EzLoRaWAN_BLE.cpp.org new file mode 100644 index 0000000..dddb468 --- /dev/null +++ b/src/EzLoRaWAN_BLE.cpp.org @@ -0,0 +1,138 @@ +#include "EzLoRaWAN_BLE.h" +#include "ByteArrayUtils.h" +#include "helper.h" + +/******************************************** +* BLE callback when client connect/disconnect +* reset the esp32 when disconnecting +********************************************/ +class MyServerCallbacks : public BLEServerCallbacks, EzLoRaWAN_BLE +{ + void onConnect(BLEServer* pServer) { + ESP_LOGI(TAG,"BLE client connected"); + } + + void onDisconnect(BLEServer* pServer) { + ESP_LOGI(TAG, "BLE client disconnected"); + rebootESP32(); + } +}; +/******************************** + * BLE callback when BLE client (the phone) sends data to characteristic + *********************************/ +class MyCallbacks : public BLECharacteristicCallbacks, EzLoRaWAN_BLE +{ + void onWrite(BLECharacteristic* pCharacteristic) + { + BLEUUID myUUID = pCharacteristic->getUUID(); + EzLoRaWAN* ttn = EzLoRaWAN::getInstance(); + ttn->restoreKeys(); + + /*OTAA*/ + if (myUUID.equals(BLEUUID::fromString(CHARACTERISTIC_DEVEUI))) + { + ESP_LOGI(TAG,"DevEUI"); + byte* value = pCharacteristic->getData(); + ByteArrayUtils::swapBytes(value, 8); + ttn->setDevEui(value); + } + if (myUUID.equals(BLEUUID::fromString(CHARACTERISTIC_APPEUI))) + { + ESP_LOGI(TAG,"AppEUI"); + byte* value = pCharacteristic->getData(); + ByteArrayUtils::swapBytes(value, 8); + ttn->setAppEui(value); + } + if (myUUID.equals(BLEUUID::fromString(CHARACTERISTIC_APPKEY))) + { + ESP_LOGI(TAG,"AppKey"); + byte* value = pCharacteristic->getData(); + ttn->setAppKey(value); + } + /*ABP*/ + if (myUUID.equals(BLEUUID::fromString(CHARACTERISTIC_DEV_ADDR))) + { + ESP_LOGI(TAG,"devADDR"); + } + if (myUUID.equals(BLEUUID::fromString(CHARACTERISTIC_NWKSKEY))) + { + ESP_LOGI(TAG,"NwkSKey"); + } + if (myUUID.equals(BLEUUID::fromString(CHARACTERISTIC_APP_SKEY))) + { + ESP_LOGI(TAG,"AppSKey"); + } + + ttn->saveKeys(); + } +}; + +bool EzLoRaWAN_BLE::begin(std::string bt_name) +{ + EzLoRaWAN* ttn = EzLoRaWAN::getInstance(); + ttn->restoreKeys(); + if (bt_name == "") + { + std::string nameDev(ttn->getDevEui(true).c_str()); + bt_name.append("RGOT_").append(nameDev); + } + + BLEDevice::init(bt_name.c_str()); + ESP_LOGI(TAG, "BLE Begin server: %s", bt_name.c_str()); + BLEServer* pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + BLEService* pService = pServer->createService(SERVICE_UUID); + BLECharacteristic* pCharacteristicAppKey = pService->createCharacteristic( + CHARACTERISTIC_APPKEY, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); + BLECharacteristic* pCharacteristicDevEUI = pService->createCharacteristic( + CHARACTERISTIC_DEVEUI, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); + BLECharacteristic* pCharacteristicAppEUI = pService->createCharacteristic( + CHARACTERISTIC_APPEUI, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); + BLECharacteristic* pCharacteristicDevAddr = pService->createCharacteristic( + CHARACTERISTIC_DEV_ADDR, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); + BLECharacteristic* pCharacteristicNwkSKey = pService->createCharacteristic( + CHARACTERISTIC_NWKSKEY, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); + BLECharacteristic* pCharacteristicAppSKey = pService->createCharacteristic( + CHARACTERISTIC_APP_SKEY, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); + + pCharacteristicDevEUI->setCallbacks(new MyCallbacks()); + pCharacteristicAppEUI->setCallbacks(new MyCallbacks()); + pCharacteristicAppKey->setCallbacks(new MyCallbacks()); + pCharacteristicDevAddr->setCallbacks(new MyCallbacks()); + pCharacteristicNwkSKey->setCallbacks(new MyCallbacks()); + pCharacteristicAppSKey->setCallbacks(new MyCallbacks()); + byte buf[33]; + + int len = ttn->getDevEui(buf); + pCharacteristicDevEUI->setValue(buf, len); + + len = ttn->getAppEui(buf); + pCharacteristicAppEUI->setValue(buf, len); + + //len = ttn->getAppKey(buf); + //pCharacteristicAppKey->setValue(buf, len); + + pService->start(); + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->start(); + return true; +} + +bool EzLoRaWAN_BLE::stop() +{ + ESP_LOGI(TAG, "stop BLE"); + BLEDevice::deinit(); + return true; +} + +void EzLoRaWAN_BLE::rebootESP32() { + ESP.restart(); +} + +bool EzLoRaWAN_BLE::getInitialized() +{ + return BLEDevice::getInitialized(); +} +EzLoRaWAN_BLE::EzLoRaWAN_BLE() {} +void EzLoRaWAN_BLE::init() {} diff --git a/src/EzLoRaWAN_BLE.h.org b/src/EzLoRaWAN_BLE.h.org new file mode 100644 index 0000000..758ae72 --- /dev/null +++ b/src/EzLoRaWAN_BLE.h.org @@ -0,0 +1,48 @@ +/************************************ +You can use this library with a BLE app. +note the service uuid and characteristics below. +you can install the android app TTN ESP32 Provisioning +https://play.google.com/store/apps/details?id=org.rgot.BLE_TEST +to use this library and provisioning the esp32 Lora +****************************************/ +#ifndef _LoRaWan_BLE_ESP32_h +#define _LoRaWan_BLE_ESP32_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "16f9e4cb-d966-4e2c-9b23-35cba66e4f09" +#define CHARACTERISTIC_APPKEY "c357e06e-523e-11ea-8d77-2e728ce88125" +#define CHARACTERISTIC_DEVEUI "c357e2d0-523e-11ea-8d77-2e728ce88125" +#define CHARACTERISTIC_APPEUI "c357e6cc-523e-11ea-8d77-2e728ce88125" +#define CHARACTERISTIC_DEV_ADDR "c357e848-523e-11ea-8d77-2e728ce88125" +#define CHARACTERISTIC_NWKSKEY "c357e992-523e-11ea-8d77-2e728ce88125" +#define CHARACTERISTIC_APP_SKEY "c357ebf4-523e-11ea-8d77-2e728ce88125" + +class EzLoRaWAN_BLE +{ + protected: + + + public: + EzLoRaWAN_BLE(); + void init(); + static bool begin(std::string bt_name = ""); + static bool stop(); + static void rebootESP32(); + static bool getInitialized(); +}; + +#endif + diff --git a/src/EzLoRaWAN_CayenneLPP.cpp b/src/EzLoRaWAN_CayenneLPP.cpp new file mode 100644 index 0000000..ea1ef31 --- /dev/null +++ b/src/EzLoRaWAN_CayenneLPP.cpp @@ -0,0 +1,240 @@ +// Adapted from https://developer.mbed.org/teams/myDevicesIoT/code/Cayenne-LPP/ + +// Copyright © 2017 The Things Network +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +#include "EzLoRaWan_CayenneLPP.h" + +EzLoRaWAN_CayenneLPP::EzLoRaWAN_CayenneLPP(uint8_t size) : maxsize(size) +{ + buffer = (uint8_t*)malloc(size); + cursor = 0; +} + +EzLoRaWAN_CayenneLPP::~EzLoRaWAN_CayenneLPP(void) +{ + free(buffer); +} + +void EzLoRaWAN_CayenneLPP::reset(void) +{ + cursor = 0; +} + +uint8_t EzLoRaWAN_CayenneLPP::getSize(void) +{ + return cursor; +} + +uint8_t* EzLoRaWAN_CayenneLPP::getBuffer(void) +{ + // uint8_t[cursor] result; + // memcpy(result, buffer, cursor); + // return result; + return buffer; +} + +uint8_t EzLoRaWAN_CayenneLPP::copy(uint8_t* dst) +{ + memcpy(dst, buffer, cursor); + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addDigitalInput(uint8_t channel, uint8_t value) +{ + if ((cursor + LPP_DIGITAL_INPUT_SIZE) > maxsize) + { + return 0; + } + buffer[cursor++] = channel; + buffer[cursor++] = LPP_DIGITAL_INPUT; + buffer[cursor++] = value; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addDigitalOutput(uint8_t channel, uint8_t value) +{ + if ((cursor + LPP_DIGITAL_OUTPUT_SIZE) > maxsize) + { + return 0; + } + buffer[cursor++] = channel; + buffer[cursor++] = LPP_DIGITAL_OUTPUT; + buffer[cursor++] = value; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addAnalogInput(uint8_t channel, float value) +{ + if ((cursor + LPP_ANALOG_INPUT_SIZE) > maxsize) + { + return 0; + } + + int16_t val = value * 100; + buffer[cursor++] = channel; + buffer[cursor++] = LPP_ANALOG_INPUT; + buffer[cursor++] = val >> 8; + buffer[cursor++] = val; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addAnalogOutput(uint8_t channel, float value) +{ + if ((cursor + LPP_ANALOG_OUTPUT_SIZE) > maxsize) + { + return 0; + } + int16_t val = value * 100; + buffer[cursor++] = channel; + buffer[cursor++] = LPP_ANALOG_OUTPUT; + buffer[cursor++] = val >> 8; + buffer[cursor++] = val; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addLuminosity(uint8_t channel, uint16_t lux) +{ + if ((cursor + LPP_LUMINOSITY_SIZE) > maxsize) + { + return 0; + } + buffer[cursor++] = channel; + buffer[cursor++] = LPP_LUMINOSITY; + buffer[cursor++] = lux >> 8; + buffer[cursor++] = lux; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addPresence(uint8_t channel, uint8_t value) +{ + if ((cursor + LPP_PRESENCE_SIZE) > maxsize) + { + return 0; + } + buffer[cursor++] = channel; + buffer[cursor++] = LPP_PRESENCE; + buffer[cursor++] = value; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addTemperature(uint8_t channel, float celsius) +{ + if ((cursor + LPP_TEMPERATURE_SIZE) > maxsize) + { + return 0; + } + int16_t val = celsius * 10; + buffer[cursor++] = channel; + buffer[cursor++] = LPP_TEMPERATURE; + buffer[cursor++] = val >> 8; + buffer[cursor++] = val; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addRelativeHumidity(uint8_t channel, float rh) +{ + if ((cursor + LPP_RELATIVE_HUMIDITY_SIZE) > maxsize) + { + return 0; + } + buffer[cursor++] = channel; + buffer[cursor++] = LPP_RELATIVE_HUMIDITY; + buffer[cursor++] = rh * 2; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addAccelerometer(uint8_t channel, float x, float y, float z) +{ + if ((cursor + LPP_ACCELEROMETER_SIZE) > maxsize) + { + return 0; + } + int16_t vx = x * 1000; + int16_t vy = y * 1000; + int16_t vz = z * 1000; + + buffer[cursor++] = channel; + buffer[cursor++] = LPP_ACCELEROMETER; + buffer[cursor++] = vx >> 8; + buffer[cursor++] = vx; + buffer[cursor++] = vy >> 8; + buffer[cursor++] = vy; + buffer[cursor++] = vz >> 8; + buffer[cursor++] = vz; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addBarometricPressure(uint8_t channel, float hpa) +{ + if ((cursor + LPP_BAROMETRIC_PRESSURE_SIZE) > maxsize) + { + return 0; + } + int16_t val = hpa * 10; + + buffer[cursor++] = channel; + buffer[cursor++] = LPP_BAROMETRIC_PRESSURE; + buffer[cursor++] = val >> 8; + buffer[cursor++] = val; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addGyrometer(uint8_t channel, float x, float y, float z) +{ + if ((cursor + LPP_GYROMETER_SIZE) > maxsize) + { + return 0; + } + int16_t vx = x * 100; + int16_t vy = y * 100; + int16_t vz = z * 100; + + buffer[cursor++] = channel; + buffer[cursor++] = LPP_GYROMETER; + buffer[cursor++] = vx >> 8; + buffer[cursor++] = vx; + buffer[cursor++] = vy >> 8; + buffer[cursor++] = vy; + buffer[cursor++] = vz >> 8; + buffer[cursor++] = vz; + + return cursor; +} + +uint8_t EzLoRaWAN_CayenneLPP::addGPS(uint8_t channel, float latitude, float longitude, float meters) +{ + if ((cursor + LPP_GPS_SIZE) > maxsize) + { + return 0; + } + int32_t lat = latitude * 10000; + int32_t lon = longitude * 10000; + int32_t alt = meters * 100; + + buffer[cursor++] = channel; + buffer[cursor++] = LPP_GPS; + + buffer[cursor++] = lat >> 16; + buffer[cursor++] = lat >> 8; + buffer[cursor++] = lat; + buffer[cursor++] = lon >> 16; + buffer[cursor++] = lon >> 8; + buffer[cursor++] = lon; + buffer[cursor++] = alt >> 16; + buffer[cursor++] = alt >> 8; + buffer[cursor++] = alt; + + return cursor; +} diff --git a/src/EzLoRaWAN_CayenneLPP.h b/src/EzLoRaWAN_CayenneLPP.h new file mode 100644 index 0000000..4c2b682 --- /dev/null +++ b/src/EzLoRaWAN_CayenneLPP.h @@ -0,0 +1,74 @@ +// Adapted from https://developer.mbed.org/teams/myDevicesIoT/code/Cayenne-LPP/ + +// Copyright © 2017 The Things Network +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +#ifndef _CAYENNE_LPP_H_ +#define _CAYENNE_LPP_H_ + +#include + +// LPP_BATTERY = // TODO Unsupported in IPSO Smart Object +// LPP_PROXIMITY = // TODO Unsupported in IPSO Smart Object + +#define LPP_DIGITAL_INPUT 0 // 1 byte +#define LPP_DIGITAL_OUTPUT 1 // 1 byte +#define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed +#define LPP_ANALOG_OUTPUT 3 // 2 bytes, 0.01 signed +#define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned +#define LPP_PRESENCE 102 // 1 byte, 1 +#define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed +#define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned +#define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G +#define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1 hPa Unsigned +#define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 °/s +#define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter + +// Data ID + Data Type + Data Size +#define LPP_DIGITAL_INPUT_SIZE 3 // 1 byte +#define LPP_DIGITAL_OUTPUT_SIZE 3 // 1 byte +#define LPP_ANALOG_INPUT_SIZE 4 // 2 bytes, 0.01 signed +#define LPP_ANALOG_OUTPUT_SIZE 4 // 2 bytes, 0.01 signed +#define LPP_LUMINOSITY_SIZE 4 // 2 bytes, 1 lux unsigned +#define LPP_PRESENCE_SIZE 3 // 1 byte, 1 +#define LPP_TEMPERATURE_SIZE 4 // 2 bytes, 0.1°C signed +#define LPP_RELATIVE_HUMIDITY_SIZE 3 // 1 byte, 0.5% unsigned +#define LPP_ACCELEROMETER_SIZE 8 // 2 bytes per axis, 0.001G +#define LPP_BAROMETRIC_PRESSURE_SIZE 4 // 2 bytes 0.1 hPa Unsigned +#define LPP_GYROMETER_SIZE 8 // 2 bytes per axis, 0.01 °/s +#define LPP_GPS_SIZE 11 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter + +class EzLoRaWAN_CayenneLPP +{ +public: + EzLoRaWAN_CayenneLPP(uint8_t size = 51); + ~EzLoRaWAN_CayenneLPP(); + + void reset(void); + uint8_t getSize(void); + uint8_t* getBuffer(void); + uint8_t copy(uint8_t* buffer); + + uint8_t addDigitalInput(uint8_t channel, uint8_t value); + uint8_t addDigitalOutput(uint8_t channel, uint8_t value); + + uint8_t addAnalogInput(uint8_t channel, float value); + uint8_t addAnalogOutput(uint8_t channel, float value); + + uint8_t addLuminosity(uint8_t channel, uint16_t lux); + uint8_t addPresence(uint8_t channel, uint8_t value); + uint8_t addTemperature(uint8_t channel, float celsius); + uint8_t addRelativeHumidity(uint8_t channel, float rh); + uint8_t addAccelerometer(uint8_t channel, float x, float y, float z); + uint8_t addBarometricPressure(uint8_t channel, float hpa); + uint8_t addGyrometer(uint8_t channel, float x, float y, float z); + uint8_t addGPS(uint8_t channel, float latitude, float longitude, float meters); + +private: + uint8_t* buffer; + uint8_t maxsize; + uint8_t cursor; +}; + +#endif diff --git a/src/helper.h b/src/helper.h new file mode 100644 index 0000000..28347e5 --- /dev/null +++ b/src/helper.h @@ -0,0 +1,17 @@ +#ifndef _HELPER_H +#define _HELPER_H + +#include "nvs_flash.h" + +//static const char* const CFG_DEBUG = "debug"; +static const char* const TAG = "LoRaWan_prov"; +static const char* const NVS_FLASH_PARTITION = "ttn"; +static const char* const NVS_FLASH_KEY_DEV_EUI = "devEui"; +static const char* const NVS_FLASH_KEY_APP_EUI = "appEui"; +static const char* const NVS_FLASH_KEY_APP_KEY = "appKey"; +static const char* const NVS_FLASH_KEY_APP_SESSION_KEY = "AppSKey"; +static const char* const NVS_FLASH_KEY_NWK_SESSION_KEY = "NwkSKey"; +static const char* const NVS_FLASH_KEY_DEV_ADDR = "devAddr"; +static const char* const NVS_FLASH_KEY_SEQ_NUM_UP = "seqNumUp"; + +#endif /* _HELPER_H */ diff --git a/src/oslmic_types.h b/src/oslmic_types.h new file mode 100644 index 0000000..33d0dcf --- /dev/null +++ b/src/oslmic_types.h @@ -0,0 +1,54 @@ +/* + +Module: oslmic_types.h + +Function: + Basic types from oslmic.h, shared by all layers. + +Copyright & License: + See accompanying LICENSE file. + +Author: + Terry Moore, MCCI November 2018 + (based on oslmic.h from IBM). + +*/ + +#ifndef _oslmic_types_h_ +# define _oslmic_types_h_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//================================================================================ +//================================================================================ +// Target platform as C library +typedef uint8_t bit_t; +typedef uint8_t u1_t; +typedef int8_t s1_t; +typedef uint16_t u2_t; +typedef int16_t s2_t; +typedef uint32_t u4_t; +typedef int32_t s4_t; +typedef unsigned int uint; +typedef const char* str_t; + +// the HAL needs to give us ticks, so it ought to know the right type. +typedef s4_t ostime_t; + +typedef struct osjob_t osjob_t; +//typedef struct band_t band_t; +typedef struct chnldef_t chnldef_t; +typedef struct rxsched_t rxsched_t; +typedef struct bcninfo_t bcninfo_t; +typedef const u1_t* xref2cu1_t; +typedef u1_t* xref2u1_t; +#ifdef __cplusplus +} +#endif + +/* end of oslmic_types.h */ +#endif /* _oslmic_types_h_ */ diff --git a/src/target-config.h b/src/target-config.h new file mode 100644 index 0000000..965c8a4 --- /dev/null +++ b/src/target-config.h @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * --- Revised 3-Clause BSD License --- + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL SEMTECH BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This file defines the configuration of the library and HAL. + *******************************************************************************/ +#ifndef _lmic_arduino_hal_config_h_ +#define _lmic_arduino_hal_config_h_ + +// This defines the region(s) to use. You can enable more than one and +// then select the right region at runtime using os_getRegion() and/or +// +#include +#ifdef REGION_EU868 +#define CFG_eu868 1 +#elif defined (REGION_US915) +#define CFG_us915 1 +#else +// todo complete region config + +////#define CFG_as923 1 +////#define CFG_il915 1 +////#define CFG_kr920 1 +////#define CFG_au915 1 +#define CFG_eu868 1 +#endif // REGION_EU868 + + +#define CFG_autojoin + +#if defined (ARDUINO_HELTEC_WIFI_LORA_32_V3) || defined (WIFI_LoRa_32_V3)\ +|| defined (ARDUINO_HELTEC_WIRELESS_STICK_V3) \ +|| defined (ARDUINO_HELTEC_WIRELESS_STICK_LITE_V3) \ +|| defined (ARDUINO_HELTEC_WIRELESS_PAPER) \ +|| defined (ARDUINO_HELTEC_WIRELESS_TRACKER) \ +|| defined (ARDUINO_WIFI_LoRa_32_V3) //sx1262 +#define BRD_sx1262_radio 1 +#define RADIO_SCLK_PIN 9 //SCK //voir pins_arduino.h +#define RADIO_MISO_PIN 11 //MISO +#define RADIO_MOSI_PIN 10 //MOSI +#define RADIO_TX_PIN LMIC_CONTROLLED_BY_DIO2 +#define RADIO_RX_PIN LMIC_UNUSED_PIN +#define RADIO_CS_PIN 8 //SS +#define RADIO_DIO0_PIN LMIC_UNUSED_PIN +#define RADIO_DIO1_PIN 14 //SX1280 DIO1 = IO9 +#define RADIO_DIO2_PIN LMIC_UNUSED_PIN +#define RADIO_BUSY_PIN 13 //BUSY_LoRa //SX1280 BUSY = IO36 +#define RADIO_RST_PIN 12 //RST_LoRa +#define RADIO_TCXO_PIN LMIC_CONTROLLED_BY_DIO3 +#define AUTO_PIN_MAP + +#elif defined(ARDUINO_HELTEC_WIRELESS_STICK) || defined(Wireless_Stick) \ +|| defined (ARDUINO_HELTEC_WIRELESS_STICK_LITE)|| defined(Wireless_Stick_Lite)\ +|| defined (ARDUINO_HELTEC_WIFI_LORA_32) || defined(WIFI_LoRa_32)\ +|| defined (ARDUINO_HELTEC_WIFI_LORA_32_V2) || defined(WIFI_LoRa_32_V2) \ +//sx1276 +#define BRD_sx1276_radio 1 +#define RADIO_SCLK_PIN SCK //voir pins_arduino.h +#define RADIO_MISO_PIN MISO +#define RADIO_MOSI_PIN MOSI +#define RADIO_CS_PIN SS +#define RADIO_DIO0_PIN DIO0 +#define RADIO_DIO1_PIN DIO1 //SX1280 DIO1 = IO9 +#define RADIO_DIO2_PIN DIO2 +#define RADIO_RST_PIN RST_LoRa +#define RADIO_BUSY_PIN LMIC_UNUSED_PIN //SX1280 BUSY = IO36 +#define RADIO_TCXO_PIN LMIC_UNUSED_PIN +#define RADIO_TX_PIN LMIC_UNUSED_PIN +#define RADIO_RX_PIN LMIC_UNUSED_PIN +#define AUTO_PIN_MAP +#elif defined (ARDUINO_TBEAM_USE_RADIO_SX1276) +#define BRD_sx1276_radio 1 +#define RADIO_SCLK_PIN LORA_SCK //voir pins_arduino.h +#define RADIO_MISO_PIN LORA_MISO +#define RADIO_MOSI_PIN LORA_MOSI +#define RADIO_CS_PIN LORA_CS +#define RADIO_DIO0_PIN LORA_IO0 +#define RADIO_DIO1_PIN LORA_IO1 //SX1280 DIO1 = IO9 +#define RADIO_DIO2_PIN LORA_IO2 +#define RADIO_RST_PIN LORA_RST +#define RADIO_BUSY_PIN LMIC_UNUSED_PIN //SX1280 BUSY = IO36 +#define RADIO_TCXO_PIN LMIC_UNUSED_PIN +#define RADIO_TX_PIN LMIC_UNUSED_PIN +#define RADIO_RX_PIN LMIC_UNUSED_PIN +#define AUTO_PIN_MAP +#else +//#define BRD_sx1262_radio +//#if defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) +//// Used for lmic_pinmap.tcxo only +//#define LMIC_CONTROLLED_BY_DIO3 0xff +//#define LMIC_CONTROLLED_BY_DIO2 0xfe +//#endif // defined(BRD_sx1261_radio) || defined(BRD_sx1262_radio) + + +#endif +#if !defined(BRD_sx1272_radio) && !defined(BRD_sx1276_radio) && !defined(BRD_sx1261_radio) && !defined(BRD_sx1262_radio) +// This is the SX1272/SX1273 radio, which is also used on the HopeRF +// RFM92 boards. +//#define BRD_sx1272_radio 1 +// This is the SX1276/SX1277/SX1278/SX1279 radio, which is also used on +// the HopeRF RFM95 boards. +//#define BRD_sx1276_radio 1 +// This is the newer SX1261 radio (up to +15dBM). +//#define BRD_sx1261_radio 1 +// This is the newer SX1262 radio (up to +22dBM). +#define BRD_sx1262_radio 1 +#endif // !defined(BRD_sx1272_radio) && !defined(BRD_sx1276_radio) && !defined(BRD_sx1262_radio) + +// 16 μs per tick +// LMIC requires ticks to be 15.5μs - 100 μs long +#define US_PER_OSTICK_EXPONENT 4 +#define US_PER_OSTICK (1 << US_PER_OSTICK_EXPONENT) +#define OSTICKS_PER_SEC (1000000 / US_PER_OSTICK) + +// When this is defined, some debug output will be printed and +// debug_printf(...) is available (which is a slightly non-standard +// printf implementation). +// Without this, assertion failures are *not* printed! +//#define CFG_DEBUG +// When this is defined, additional debug output is printed. +//#define CFG_DEBUG_VERBOSE +// Debug output (and assertion failures) are printed to this Stream +//->#define CFG_DEBUG_STREAM Serial +// Define these to add some TX or RX specific debug output (needs +// CFG_DEBUG) +//->#define DEBUG_TX +//->#define DEBUG_RX +// Define these to add some job scheduling specific debug output (needs +// CFG_DEBUG_VERBOSE) +//#define DEBUG_JOBS +// Uncomment to display timestamps in ticks rather than milliseconds +//#define CFG_DEBUG_RAW_TIMESTAMPS + +// When this is defined, the standard libc printf function will print to +// this Stream. You should probably use CFG_DEBUG and debug_printf() +// instead, though. +//#define LMIC_PRINTF_TO Serial + +// Remove/comment this to enable code related to beacon tracking. +#define DISABLE_CLASSB + +// This allows choosing between multiple included AES implementations. +// Make sure exactly one of these is uncommented. +// +// This selects the original AES implementation included LMIC. This +// implementation is optimized for speed on 32-bit processors using +// fairly big lookup tables, but it takes up big amounts of flash on the +// AVR architecture. +// #define USE_ORIGINAL_AES +// +// This selects the AES implementation written by Ideetroon for their +// own LoRaWAN library. It also uses lookup tables, but smaller +// byte-oriented ones, making it use a lot less flash space (but it is +// also about twice as slow as the original). +#define USE_IDEETRON_AES + +#endif // _lmic_arduino_hal_config_h_