From ba33e355916edd3fb5b840a0bd551f37cfc1b798 Mon Sep 17 00:00:00 2001 From: Manuel Bleichenbacher Date: Sun, 15 Jul 2018 22:11:11 +0200 Subject: [PATCH] Initial check-in --- .gitignore | 54 +- .vscode/c_cpp_properties.json | 32 + Kconfig | 35 + component.mk | 4 + examples/send_msg/Makefile | 3 + examples/send_msg/components/ttn-esp32 | 1 + examples/send_msg/main/component.mk | 0 examples/send_msg/main/main.cpp | 74 + include/TheThingsNetwork.h | 133 ++ src/TheThingsNetwork.cpp | 238 +++ src/aes.c | 383 ++++ src/config.h | 46 + src/hal.h | 116 ++ src/hal_esp32.c | 409 ++++ src/hal_esp32.h | 44 + src/lmic.c | 2400 ++++++++++++++++++++++++ src/lmic.h | 340 ++++ src/lorabase.h | 407 ++++ src/oslmic.c | 151 ++ src/oslmic.h | 242 +++ src/radio.c | 886 +++++++++ 21 files changed, 5946 insertions(+), 52 deletions(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 Kconfig create mode 100755 component.mk create mode 100644 examples/send_msg/Makefile create mode 120000 examples/send_msg/components/ttn-esp32 create mode 100644 examples/send_msg/main/component.mk create mode 100644 examples/send_msg/main/main.cpp create mode 100644 include/TheThingsNetwork.h create mode 100644 src/TheThingsNetwork.cpp create mode 100755 src/aes.c create mode 100644 src/config.h create mode 100755 src/hal.h create mode 100755 src/hal_esp32.c create mode 100644 src/hal_esp32.h create mode 100755 src/lmic.c create mode 100755 src/lmic.h create mode 100755 src/lorabase.h create mode 100755 src/oslmic.c create mode 100755 src/oslmic.h create mode 100755 src/radio.c diff --git a/.gitignore b/.gitignore index c6127b3..6b744e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,52 +1,2 @@ -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf +build/ +sdkconfig diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..74a44e2 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,32 @@ +{ + "configurations": [ + { + "name": "ESP32", + "includePath": [ + "${XTENSA_TOOLCHAIN}/xtensa-esp32-elf/include", + "${XTENSA_TOOLCHAIN}/lib/gcc/xtensa-esp32-elf/5.2.0/include", + "${IDF_PATH}/components/driver/include", + "${IDF_PATH}/components/esp32/include", + "${IDF_PATH}/components/freertos/include", + "${IDF_PATH}/components/heap/include", + "${IDF_PATH}/components/log/include", + "${IDF_PATH}/components/lwip/include/lwip", + "${IDF_PATH}/components/lwip/include/lwip/port", + "${IDF_PATH}/components/soc/include", + "${IDF_PATH}/components/soc/esp32/include", + "${IDF_PATH}/components/tcpip_adapter/include", + "${workspaceRoot}/examples/send_msg/build/include", + "${workspaceRoot}/include", + "${workspaceRoot}/src" + ], + "defines": [ + "_DEBUG" + ], + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "clang-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..d489ec2 --- /dev/null +++ b/Kconfig @@ -0,0 +1,35 @@ +menu "The Things Network" + +choice TTN_LORA_FREQ + prompt "TTN LoRa Frequency" + default TTN_LORA_FREQ_NONE + help + LoRa frequency must match the geographic region the device is operated in. + Running it with the incorrect frequency most like violates the law. + +config TTN_LORA_FREQ_NONE + bool "None" + +config TTN_LORA_FREQ_EU_868 + bool "868 MHz (Europe)" + +config TTN_LORA_FREQ_US_915 + bool "915 Mhz (North America)" + +endchoice + +choice TTN_RADIO_CHIP + prompt "TTN Radio Chip" + default TTN_RADIO_SX1276_77_78_79 + help + The chip type used for LoRa radio. + +config TTN_RADIO_SX1272_73 + bool "SX1272/SX1273 (used in HopeRF RFM92 boards)" + +config TTN_RADIO_SX1276_77_78_79 + bool "SX1276/SX1277/SX1278/SX1279 (used in HopeRF RFM96 and ttgo LoRa boards)" + +endchoice + +endmenu diff --git a/component.mk b/component.mk new file mode 100755 index 0000000..ef4f2bd --- /dev/null +++ b/component.mk @@ -0,0 +1,4 @@ +CFLAGS += -Wno-error=maybe-uninitialized -Wno-error=unused-value + +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_SRCDIRS := src diff --git a/examples/send_msg/Makefile b/examples/send_msg/Makefile new file mode 100644 index 0000000..6ef9a37 --- /dev/null +++ b/examples/send_msg/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := send_msg + +include $(IDF_PATH)/make/project.mk diff --git a/examples/send_msg/components/ttn-esp32 b/examples/send_msg/components/ttn-esp32 new file mode 120000 index 0000000..b53b712 --- /dev/null +++ b/examples/send_msg/components/ttn-esp32 @@ -0,0 +1 @@ +../../../../ttn-esp32 \ No newline at end of file diff --git a/examples/send_msg/main/component.mk b/examples/send_msg/main/component.mk new file mode 100644 index 0000000..e69de29 diff --git a/examples/send_msg/main/main.cpp b/examples/send_msg/main/main.cpp new file mode 100644 index 0000000..546be72 --- /dev/null +++ b/examples/send_msg/main/main.cpp @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2018 Manuel Bleichenbacher + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Sample program showing how to send a test message every 30 second. + *******************************************************************************/ + +#include "freertos/FreeRTOS.h" +#include "esp_event.h" + +#include "TheThingsNetwork.h" + +// NOTE: +// The LoRaWAN frequency and the radio chip must be configured by running 'make menuconfig'. +// Go to Components / The Things Network, select the appropriate values and save. + +// Copy the below two lines from bottom of your device's overview page in the TTN console +const char *appEui = "????????????????"; +const char *appKey = "????????????????????????????????"; + +// Copy the below hex string from the "Device EUI" field on the same pag. +const char *devEui = "????????????????";; + + +static TheThingsNetwork ttn; + +const unsigned TX_INTERVAL = 30; +static uint8_t msgData[] = "Hello, world"; + + +void send_messages(void* pvParameter) +{ + vTaskDelay(TX_INTERVAL * 1000 / portTICK_PERIOD_MS); + + while(1) { + printf("Sending message...\n"); + ttn_response_t res = ttn.sendBytes(msgData, sizeof(msgData) - 1); + if (res == TTN_SUCCESSFUL_TRANSMISSION) + printf("Message sent.\n"); + else + printf("Message transmission failed.\n"); + + vTaskDelay(TX_INTERVAL * 1000 / portTICK_PERIOD_MS); + } +} + +extern "C" void app_main(void) +{ + gpio_install_isr_service(ESP_INTR_FLAG_IRAM); + + // Initialize SPI bus + spi_bus_config_t spi_bus_config; + spi_bus_config.miso_io_num = 19; + spi_bus_config.mosi_io_num = 27; + spi_bus_config.sclk_io_num = 5; + spi_bus_config.quadwp_io_num = -1; + spi_bus_config.quadhd_io_num = -1; + spi_bus_config.max_transfer_sz = 0; + + esp_err_t ret = spi_bus_initialize(HSPI_HOST, &spi_bus_config, 1); + assert(ret == ESP_OK); + + ttn.configurePins(HSPI_HOST, 18, TTN_NOT_CONNECTED, 14, 26, 33); + + ttn.provision(appEui, appKey, devEui); + + ttn.join(); + + xTaskCreate(send_messages, "send_messages", 1024 * 4, (void* )0, 3, NULL); +} diff --git a/include/TheThingsNetwork.h b/include/TheThingsNetwork.h new file mode 100644 index 0000000..191687c --- /dev/null +++ b/include/TheThingsNetwork.h @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2018 Manuel Bleichenbacher + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * This the hardware abstraction layer to run LMIC in on ESP32 using ESP-iDF. + *******************************************************************************/ + +#ifndef _THETHINGSNETWORK_H_ +#define _THETHINGSNETWORK_H_ + +#include +#include "driver/spi_master.h" + +#define TTN_DEFAULT_SF 7 +#define TTN_DEFAULT_FSB 2 + +/** + * @brief Constant for indicating that a pin is not connected + */ +#define TTN_NOT_CONNECTED 0xff + + +typedef uint8_t port_t; + +/** + * @brief Response codes + */ +enum ttn_response_t +{ + TTN_ERROR_SEND_COMMAND_FAILED = (-1), + TTN_ERROR_UNEXPECTED_RESPONSE = (-10), + TTN_SUCCESSFUL_TRANSMISSION = 1, + TTN_SUCCESSFUL_RECEIVE = 2 +}; + + +/** + * @brief TTN device + * + * The TheThingsNetwork class enables ESP32 devices with SX1272/73/76/77/78/79 LoRaWAN chips + * to communicate via The Things Network. + * + * Only one instance of this class must be created. + */ +class TheThingsNetwork +{ +public: + /** + * @brief Construct a new The Things Network device + * + * @param sf The spreading factor. 7 to 10 for US915 frequency plan. 7 to 12 for other frequency plans. Defaults to 7. + * @param fsb Optional custom frequency subband. 1 to 8. Defaults to 2. + */ + TheThingsNetwork(uint8_t sf = TTN_DEFAULT_SF, uint8_t fsb = TTN_DEFAULT_FSB); + + /** + * @brief Destroy the The Things Network device. + */ + ~TheThingsNetwork(); + + /** + * @brief Reset the LoRaWAN radio. + * + * Does not clear provisioned keys. + */ + void reset(); + + /** + * @brief Configures the pins used to communicate with the LoRaWAN radio chip. + * + * The SPI bus must be first configured using spi_bus_initialize(). Then it is passed as the first parameter. + * + * @param spi_host The SPI bus/peripherial to use (SPI_HOST, HSPI_HOST or VSPI_HOST). + * @param nss The GPIO pin number connected to the radio chip's NSS pin (serving as the SPI chip select) + * @param rxtx The GPIO pin number connected to the radio chip's RXTX pin (TTN_NOT_CONNECTED if not connected) + * @param rst The GPIO pin number connected to the radio chip's RST pin (TTN_NOT_CONNECTED if not connected) + * @param dio0 The GPIO pin number connected to the radio chip's DIO0 pin + * @param dio1 The GPIO pin number connected to the radio chip's DIO1 pin + */ + void configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1); + + /** + * @brief Sets the information needed to activate the device via OTAA, without actually activating. + * + * Call join() without the first 2 arguments to activate. + * + * @param appEui Application EUI of the device (16 character string with hexadecimal data) + * @param appKey App Key of the device (32 character string with hexadecimal data) + * @param devEui Device EUI (16 character string with hexadecimal data) or NULL if already set + * @return true if the provisioning was successful + * @return false if the provisioning failed + */ + bool provision(const char *appEui, const char *appKey, const char *devEui = NULL); + + /** + * @brief Activate the device via OTAA. + * + * @param appEui Application EUI of the device (16 character string with hexadecimal data) + * @param appKey App Key of the device (32 character string with hexadecimal data) + * @param devEui Device EUI (16 character string with hexadecimal data) or NULL if already set + * @param retries Number of times to retry after failed or unconfirmed join. Defaults to -1 which means infinite. + * @param retryDelay Delay in ms between attempts. Defaults to 10 seconds. + * @return true + * @return false + */ + bool join(const char *appEui, const char *appKey, const char *devEui = NULL, int8_t retries = -1, uint32_t retryDelay = 10000); + + /** + * @brief Activate the device via OTAA. + * + * The app EUI and key must already have been provisioned. + * + * @param retries Number of times to retry after failed or unconfirmed join. Defaults to -1 which means infinite. + * @param retryDelay Delay in ms between attempts. Defaults to 10 seconds. + * @return true + * @return false + */ + bool join(int8_t retries = -1, uint32_t retryDelay = 10000); + + ttn_response_t sendBytes(const uint8_t *payload, size_t length, port_t port = 1, bool confirm = false, uint8_t sf = 0); + +private: + uint8_t spreadingFactor = TTN_DEFAULT_SF; + uint8_t frequencySubband = TTN_DEFAULT_FSB; + + bool decodeKeys(const char *appEui, const char *appKey, const char *devEui); +}; + +#endif diff --git a/src/TheThingsNetwork.cpp b/src/TheThingsNetwork.cpp new file mode 100644 index 0000000..1a6e2b6 --- /dev/null +++ b/src/TheThingsNetwork.cpp @@ -0,0 +1,238 @@ +/******************************************************************************* + * Copyright (c) 2018 Manuel Bleichenbacher + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * This the hardware abstraction layer to run LMIC in on ESP32 using ESP-iDF. + *******************************************************************************/ + +#include "TheThingsNetwork.h" +#include "esp_log.h" +#include "oslmic.h" +#include "hal.h" +#include "hal_esp32.h" +#include "lmic.h" + +static const char *TAG = "ttn"; + +static TheThingsNetwork* ttnInstance; +static uint8_t appEui[8]; +static uint8_t appKey[16]; +static uint8_t devEui[8]; + +static bool hexStringToBin(const char *hex, uint8_t *buf, int len); +static int hexTupleToByte(const char *hex); +static int hexDigitToVal(char ch); +static void swapByteOrder(uint8_t* buf, int len); + + +TheThingsNetwork::TheThingsNetwork(uint8_t sf, uint8_t fsb) +{ + ttnInstance = this; + spreadingFactor = sf; + frequencySubband = fsb; + hal_initCriticalSection(); +} + +TheThingsNetwork::~TheThingsNetwork() +{ + // nothing to do +} + +void TheThingsNetwork::configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1) +{ + lmic_pins.spi_host = spi_host; + lmic_pins.nss = nss; + lmic_pins.rxtx = rxtx; + lmic_pins.rst = rst; + lmic_pins.dio0 = dio0; + lmic_pins.dio1 = dio1; + + os_init(); + reset(); + + hal_startBgTask(); +} + +void TheThingsNetwork::reset() +{ + hal_enterCriticalSection(); + LMIC_reset(); + hal_leaveCriticalSection(); +} + +bool TheThingsNetwork::provision(const char *appEui, const char *appKey, const char* devEui) +{ + return decodeKeys(appEui, appKey, devEui); +} + +bool TheThingsNetwork::decodeKeys(const char *appEui, const char *appKey, const char* devEui) +{ + if (strlen(appEui) != 16 || !hexStringToBin(appEui, ::appEui, 8)) + { + ESP_LOGW(TAG, "Invalid application EUI: %s", appEui); + return false; + } + + swapByteOrder(::appEui, 8); + + if (strlen(appKey) != 32 || !hexStringToBin(appKey, ::appKey, 16)) + { + ESP_LOGW(TAG, "Invalid application key: %s", appEui); + return false; + } + + if (strlen(devEui) != 16 || !hexStringToBin(devEui, ::devEui, 8)) + { + ESP_LOGW(TAG, "Invalid device EUI: %s", devEui); + return false; + } + + swapByteOrder(::devEui, 8); + + return true; +} + +bool TheThingsNetwork::join(const char *appEui, const char *appKey, const char *devEui, int8_t retries, uint32_t retryDelay) +{ + if (!decodeKeys(appEui, appKey, devEui)) + return false; + + return join(retries, retryDelay); +} + +bool TheThingsNetwork::join(int8_t retries, uint32_t retryDelay) +{ + hal_enterCriticalSection(); + LMIC_startJoining(); + hal_wakeUp(); + hal_leaveCriticalSection(); + return true; +} + +ttn_response_t TheThingsNetwork::sendBytes(const uint8_t *payload, size_t length, port_t port, bool confirm, uint8_t sf) +{ + hal_enterCriticalSection(); + if (LMIC.opmode & OP_TXRXPEND) + { + hal_leaveCriticalSection(); + return TTN_ERROR_SEND_COMMAND_FAILED; + } + + LMIC_setTxData2(port, (xref2u1_t)payload, length, confirm); + hal_wakeUp(); + hal_leaveCriticalSection(); + return TTN_SUCCESSFUL_TRANSMISSION; +} + + +// --- LMIC functions --- + +#if CONFIG_LOG_DEFAULT_LEVEL >= 3 +static const char *eventNames[] = { + NULL, + "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" +}; +#endif + +// 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 TheThingsNetwork::provision(). +void os_getArtEui (u1_t* buf) +{ + memcpy(buf, appEui, 8); +} + +// This should also be in little endian format, see above. +void os_getDevEui (u1_t* buf) +{ + memcpy(buf, devEui, 8); +} + +// 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_getDevKey (u1_t* buf) +{ + memcpy(buf, appKey, 16); +} + +void onEvent (ev_t ev) { + #if CONFIG_LOG_DEFAULT_LEVEL >= 3 + ESP_LOGI(TAG, "%ld: event %s", os_getTime(), eventNames[ev]); + #endif + + if (ev == EV_JOINED) + { + // Disable link check validation (automatically enabled + // during join, but not supported by TTN at this time). + LMIC_setLinkCheckMode(0); + } else if (ev == EV_TXCOMPLETE) { + if (LMIC.txrxFlags & TXRX_ACK) + printf("Received ack\n"); + if (LMIC.dataLen) { + printf("Received %d bytes of payload\n", LMIC.dataLen); + } + } +} + + +// --- Helper functions --- + +bool hexStringToBin(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; +} + +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; +} + +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; +} + +void swapByteOrder(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--; + } +} \ No newline at end of file diff --git a/src/aes.c b/src/aes.c new file mode 100755 index 0000000..902be32 --- /dev/null +++ b/src/aes.c @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2014-2016 IBM Corporation. + * All rights reserved. + * + * 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 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 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 "oslmic.h" + +#define AES_MICSUB 0x30 // internal use only + +static const u4_t AES_RCON[10] = { + 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, + 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000 +}; + +static const u1_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 u4_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 u4_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 u4_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 u4_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) ((u1_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 ^= (AES_S[ r0>>24 ]<<24); \ + a ^= (AES_S[u1(r1>>16)]<<16); \ + a ^= (AES_S[u1(r2>> 8)]<< 8); \ + a ^= AES_S[u1(r3) ] + +// global area for passing parameters (aux, key) and for storing round keys +u4_t AESAUX[16/sizeof(u4_t)]; +u4_t AESKEY[11*16/sizeof(u4_t)]; + +// 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; + u4_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 = (AES_S[u1(b >> 16)] << 24) ^ + (AES_S[u1(b >> 8)] << 16) ^ + (AES_S[u1(b) ] << 8) ^ + (AES_S[ b >> 24 ] ) ^ + AES_RCON[(i-4)/4]; + } + AESKEY[i] = b ^= AESKEY[i-4]; + } +} + +u4_t os_aes (u1_t mode, xref2u1_t buf, u2_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( (signed char)len > 0 ) { + u4_t a0, a1, a2, a3; + u4_t t0, t1, t2, t3; + u4_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]; +} + diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..30cf054 --- /dev/null +++ b/src/config.h @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2018 Manuel Bleichenbacher + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * This the hardware abstraction layer to run LMIC in on ESP32 using ESP-iDF. + *******************************************************************************/ + +#ifndef _lmic_config_h_ +#define _lmic_config_h_ + +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(CONFIG_TTN_LORA_FREQ_EU_868) +#define CFG_eu868 1 +#elif defined(CONFIG_TTN_LORA_FREQ_US_915) +#define CFG_us915 1 +#else +#error TTN LoRa frequency must be configured +#endif + +#if defined(CONFIG_TTN_RADIO_SX1272_73) +#define CFG_sx1272_radio 1 +#elif defined(CONFIG_TTN_RADIO_SX1276_77_78_79) +#define CFG_sx1276_radio 1 +#else +#error TTN LoRa radio chip must be configured +#endif + +// 16 μs per tick +// LMIC requires ticks to be 15.5μs - 100 μs long +#define US_PER_OSTICK 16 +#define OSTICKS_PER_SEC (1000000 / US_PER_OSTICK) + +#ifdef __cplusplus +} +#endif + +#endif // _lmic_config_h_ diff --git a/src/hal.h b/src/hal.h new file mode 100755 index 0000000..2acd42b --- /dev/null +++ b/src/hal.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2014-2016 IBM Corporation. + * All rights reserved. + * + * 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 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 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 _hal_hpp_ +#define _hal_hpp_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * initialize hardware (IO, SPI, TIMER, IRQ). + */ +void hal_init (void); + +/* + * drive radio NSS pin (0=low, 1=high). + */ +void hal_pin_nss (u1_t val); + +/* + * drive radio RX/TX pins (0=rx, 1=tx). + */ +void hal_pin_rxtx (u1_t val); + +/* + * control radio RST pin (0=low, 1=high, 2=floating) + */ +void hal_pin_rst (u1_t val); + +/* + * perform SPI write transaction with radio + * - write the command byte 'cmd' + * - write 'len' bytes in 'buf' + */ +void hal_spi_write(u1_t cmd, const u1_t* buf, int len); + +/* + * perform SPI read transaction with radio + * - write the command byte 'cmd' + * - read 'len' bytes into 'buf' + */ +void hal_spi_read(u1_t cmd, u1_t* buf, int len); + +/* + * 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 interrupt. + */ +void hal_sleep (void); + +/* + * return 32-bit system time in ticks. + */ +u4_t hal_ticks (void); + +/* + * busy-wait until specified timestamp (in ticks) is reached. + */ +void hal_waitUntil (u4_t time); + +/* + * check and rewind timer for target time. + * - return 1 if target time is close + * - otherwise rewind timer for target time or full period and return 0 + */ +u1_t hal_checkTimer (u4_t targettime); + +/* + * perform fatal failure action. + * - called by assertions + * - action could be HALT or reboot + */ +void hal_failed (const char *file, u2_t line); + + +#ifdef __cplusplus +} +#endif + + +#endif // _hal_hpp_ diff --git a/src/hal_esp32.c b/src/hal_esp32.c new file mode 100755 index 0000000..3125475 --- /dev/null +++ b/src/hal_esp32.c @@ -0,0 +1,409 @@ +/******************************************************************************* + * Copyright (c) 2018 Manuel Bleichenbacher + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Hardware abstraction layer to run LMIC on a ESP32 using ESP-iDF. + *******************************************************************************/ + +#include "lmic.h" +#include "hal_esp32.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "driver/timer.h" +#include "esp_log.h" + +#define LMIC_UNUSED_PIN 0xff + +static const char *TAG = "ttn_hal"; + +lmic_pinmap lmic_pins; + +typedef enum { + DIO0, + DIO1, + DIO2, + TIMER, + WAKEUP +} event_t; + +typedef struct { + ostime_t time; + event_t ev; +} queue_item_t; + +// ----------------------------------------------------------------------------- +// I/O + +static QueueHandle_t dio_queue; + +void IRAM_ATTR dio_irq_handler(void *arg) +{ + uint64_t now; + timer_get_counter_value(TIMER_GROUP_0, TIMER_1, &now); + event_t ev = (long)arg; + BaseType_t higher_prio_task_woken = pdFALSE; + queue_item_t item = { + .time = (ostime_t)now, + .ev = ev + }; + xQueueSendFromISR(dio_queue, &item, &higher_prio_task_woken); + if (higher_prio_task_woken) + portYIELD_FROM_ISR(); +} + +static void hal_io_init() +{ + ESP_LOGI(TAG, "Starting IO initialization"); + + // NSS and DIO0 and DIO1 are required + ASSERT(lmic_pins.nss != LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio0 != LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio1 != LMIC_UNUSED_PIN); + + gpio_pad_select_gpio(lmic_pins.nss); + gpio_set_level(lmic_pins.nss, 0); + gpio_set_direction(lmic_pins.nss, GPIO_MODE_OUTPUT); + + if (lmic_pins.rxtx != LMIC_UNUSED_PIN) + { + gpio_pad_select_gpio(lmic_pins.rxtx); + gpio_set_level(lmic_pins.rxtx, 0); + gpio_set_direction(lmic_pins.rxtx, GPIO_MODE_OUTPUT); + } + + if (lmic_pins.rst != LMIC_UNUSED_PIN) + { + gpio_pad_select_gpio(lmic_pins.rst); + gpio_set_level(lmic_pins.rst, 0); + gpio_set_direction(lmic_pins.rst, GPIO_MODE_OUTPUT); + } + + dio_queue = xQueueCreate(12, sizeof(queue_item_t)); + assert(dio_queue != NULL); + + gpio_pad_select_gpio(lmic_pins.dio0); + gpio_set_direction(lmic_pins.dio0, GPIO_MODE_INPUT); + gpio_set_intr_type(lmic_pins.dio0, GPIO_INTR_POSEDGE); + gpio_isr_handler_add(lmic_pins.dio0, dio_irq_handler, (void *)0); + + gpio_pad_select_gpio(lmic_pins.dio1); + gpio_set_direction(lmic_pins.dio1, GPIO_MODE_INPUT); + gpio_set_intr_type(lmic_pins.dio1, GPIO_INTR_POSEDGE); + gpio_isr_handler_add(lmic_pins.dio1, dio_irq_handler, (void *)1); + + ESP_LOGI(TAG, "Finished IO initialization"); +} + +void hal_pin_rxtx(u1_t val) +{ + if (lmic_pins.rxtx == LMIC_UNUSED_PIN) + return; + + gpio_set_level(lmic_pins.rxtx, val); +} + +void hal_pin_rst(u1_t val) +{ + if (lmic_pins.rst == LMIC_UNUSED_PIN) + return; + + if (val == 0 || val == 1) + { // drive pin + gpio_set_level(lmic_pins.rst, val); + gpio_set_direction(lmic_pins.rst, GPIO_MODE_OUTPUT); + } + else + { // keep pin floating + gpio_set_level(lmic_pins.rst, val); + gpio_set_direction(lmic_pins.rst, GPIO_MODE_INPUT); + } +} + +// ----------------------------------------------------------------------------- +// SPI + +#define SPI_QUEUE_SIZE 4 +#define SPI_NUM_TRX_SLOTS (SPI_QUEUE_SIZE + 1) + +static spi_device_handle_t spi_handle; +static spi_transaction_t spi_trx_queue[SPI_NUM_TRX_SLOTS]; +static int spi_trx_queue_head = 0; +static int spi_num_outstanding_trx = 0; + +static spi_transaction_t* get_next_spi_trx_desc() +{ + spi_transaction_t* trx = spi_trx_queue + spi_trx_queue_head; + memset(trx, 0, sizeof(spi_transaction_t)); + return trx; +} + +static void collect_spi_result() +{ + int head = spi_trx_queue_head; + int tail = head - spi_num_outstanding_trx; + if (tail < 0) + tail += SPI_NUM_TRX_SLOTS; + + spi_transaction_t* trx; + esp_err_t err = spi_device_get_trans_result(spi_handle, &trx, 100 / portTICK_PERIOD_MS); + assert(err == ESP_OK); + assert(trx == spi_trx_queue + tail); + spi_num_outstanding_trx--; +} + +static void submit_spi_trx() +{ + if (spi_num_outstanding_trx >= SPI_QUEUE_SIZE) + collect_spi_result(); + + int head = spi_trx_queue_head; + esp_err_t err = spi_device_queue_trans(spi_handle, spi_trx_queue + head, 100 / portTICK_PERIOD_MS); + assert(err == ESP_OK); + spi_num_outstanding_trx++; + + head++; + if (head >= SPI_NUM_TRX_SLOTS) + head = 0; + spi_trx_queue_head = head; +} + +static void hal_spi_init() +{ + ESP_LOGI(TAG, "Starting SPI initialization"); + esp_err_t ret; + + // init device + spi_device_interface_config_t spi_device_intf_config = { + .mode = 0, + .clock_speed_hz = 10000000, + .command_bits = 0, + .address_bits = 8, + .spics_io_num = lmic_pins.nss, + .queue_size = SPI_QUEUE_SIZE + }; + + ret = spi_bus_add_device(lmic_pins.spi_host, &spi_device_intf_config, &spi_handle); + assert(ret == ESP_OK); + + ESP_LOGI(TAG, "Finished SPI initialization"); +} + +void hal_spi_write(u1_t cmd, const u1_t *buf, int len) +{ + spi_transaction_t* trx = get_next_spi_trx_desc(); + trx->addr = cmd; + trx->length = 8 * len; + trx->tx_buffer = buf; + submit_spi_trx(); +} + +void hal_spi_read(u1_t cmd, u1_t *buf, int len) +{ + memset(buf, 0, len); + spi_transaction_t* trx = get_next_spi_trx_desc(); + trx->addr = cmd; + trx->length = 8 * len; + trx->rxlength = 8 * len; + trx->tx_buffer = buf; + trx->rx_buffer = buf; + submit_spi_trx(); + + while (spi_num_outstanding_trx > 0) + collect_spi_result(); +} + +// ----------------------------------------------------------------------------- +// TIME + +static uint64_t nextTimerEvent = 0xffffffff; + +static void IRAM_ATTR timer_irq_handler(void *arg) +{ + TIMERG0.int_clr_timers.t1 = 1; + BaseType_t higher_prio_task_woken = pdFALSE; + queue_item_t item = { + .time = (ostime_t)nextTimerEvent, + .ev = TIMER + }; + xQueueSendFromISR(dio_queue, &item, &higher_prio_task_woken); + if (higher_prio_task_woken) + portYIELD_FROM_ISR(); +} + +typedef enum { + CHECK_IO, + WAIT_FOR_ANY_EVENT, + WAIT_FOR_TIMER +} WaitOption; + +static bool hal_wait(WaitOption waitOption) +{ + TickType_t ticksToWait = waitOption == CHECK_IO ? 0 : portMAX_DELAY; + while (true) + { + queue_item_t item; + if (xQueueReceive(dio_queue, &item, ticksToWait) == pdFALSE) + return false; + + if (item.ev == WAKEUP) + { + return true; + // nothing to do; just wake up event + } + else if (item.ev == TIMER) { + ostime_t t = (ostime_t)nextTimerEvent; + if (item.time == t) + { + nextTimerEvent = 0xffffffff; + if (waitOption != CHECK_IO) + return true; + } + } + else + { + hal_enterCriticalSection(); + radio_irq_handler(item.ev, item.time); + hal_leaveCriticalSection(); + if (waitOption != WAIT_FOR_TIMER) + return true; + } + } +} + +static void hal_time_init() +{ + ESP_LOGI(TAG, "Starting initialisation of timer"); + + timer_group_t timer_group = TIMER_GROUP_0; + timer_idx_t timer_idx = TIMER_1; + timer_config_t config = { + .alarm_en = false, + .counter_en = false, + .intr_type = TIMER_INTR_LEVEL, + .counter_dir = TIMER_COUNT_UP, + .auto_reload = false, + .divider = 1280 + }; + timer_init(timer_group, timer_idx, &config); + timer_set_counter_value(timer_group, timer_idx, 0x0); + timer_isr_register(timer_group, timer_idx, timer_irq_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); + timer_start(timer_group, timer_idx); + + ESP_LOGI(TAG, "Finished initalisation of timer"); +} + +u4_t hal_ticks() +{ + uint64_t val; + timer_get_counter_value(TIMER_GROUP_0, TIMER_1, &val); + return (u4_t)val; +} + +void hal_waitUntil(u4_t time) +{ + nextTimerEvent = time; + timer_set_alarm(TIMER_GROUP_0, TIMER_1, TIMER_ALARM_DIS); + timer_set_alarm_value(TIMER_GROUP_0, TIMER_1, nextTimerEvent); + timer_set_alarm(TIMER_GROUP_0, TIMER_1, TIMER_ALARM_EN); + hal_wait(WAIT_FOR_TIMER); +} + +void hal_wakeUp() { + queue_item_t item = { + .ev = WAKEUP + }; + xQueueSend(dio_queue, &item, 0); +} + +// check and rewind for target time +u1_t hal_checkTimer(u4_t time) +{ + uint64_t now; + timer_get_counter_value(TIMER_GROUP_0, TIMER_1, &now); + if (time <= now) + return 1; + if (time - now < 5) + return 1; + + nextTimerEvent = time; + return 0; +} + +// ----------------------------------------------------------------------------- +// IRQ + +void hal_disableIRQs() +{ + // nothing to do as interrupt handlers post message to queue + // and don't access any shared data structures +} + +void hal_enableIRQs() +{ + // nothing to do as interrupt handlers post message to queue + // and don't access any shared data structures +} + +void hal_sleep() +{ + if (hal_wait(CHECK_IO)) + return; + + timer_set_alarm(TIMER_GROUP_0, TIMER_1, TIMER_ALARM_DIS); + timer_set_alarm_value(TIMER_GROUP_0, TIMER_1, nextTimerEvent); + timer_set_alarm(TIMER_GROUP_0, TIMER_1, TIMER_ALARM_EN); + hal_wait(WAIT_FOR_ANY_EVENT); +} + +// ----------------------------------------------------------------------------- +// Synchronization between application code and background task + +static SemaphoreHandle_t mutex; + +void hal_initCriticalSection() +{ + mutex = xSemaphoreCreateRecursiveMutex(); +} + +void hal_enterCriticalSection() +{ + xSemaphoreTakeRecursive(mutex, portMAX_DELAY); +} + +void hal_leaveCriticalSection() +{ + xSemaphoreGiveRecursive(mutex); +} + +// ----------------------------------------------------------------------------- + +static void hal_bgTask(void* pvParameter) { + os_runloop(); +} + +void hal_init() +{ + // configure radio I/O and interrupt handler + hal_io_init(); + // configure radio SPI + hal_spi_init(); + // configure timer and interrupt handler + hal_time_init(); +} + +void hal_startBgTask() { + xTaskCreate(hal_bgTask, "ttn_lora_task", 1024 * 4, (void* )0, 10, NULL); +} + +void hal_failed(const char *file, u2_t line) +{ + ESP_LOGE(TAG, "%s:%d", file, line); + assert(0); +} diff --git a/src/hal_esp32.h b/src/hal_esp32.h new file mode 100644 index 0000000..c2aa37e --- /dev/null +++ b/src/hal_esp32.h @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2018 Manuel Bleichenbacher + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Hardware abstraction layer to run LMIC on a ESP32 using ESP-iDF. + *******************************************************************************/ + +#ifndef _hal_esp32_h_ +#define _hal_esp32_h_ + +#include +#include "driver/spi_master.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct lmic_pinmap { + spi_host_device_t spi_host; + uint8_t nss; + uint8_t rxtx; + uint8_t rst; + uint8_t dio0; + uint8_t dio1; +} lmic_pinmap; + +extern lmic_pinmap lmic_pins; + +void hal_startBgTask(); +void hal_wakeUp(); +void hal_initCriticalSection(); +void hal_enterCriticalSection(); +void hal_leaveCriticalSection(); + + +#ifdef __cplusplus +} +#endif + +#endif // _hal_esp32_h_ \ No newline at end of file diff --git a/src/lmic.c b/src/lmic.c new file mode 100755 index 0000000..9ed15dc --- /dev/null +++ b/src/lmic.c @@ -0,0 +1,2400 @@ +/* + * Copyright (c) 2014-2016 IBM Corporation. + * All rights reserved. + * + * 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 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 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. + */ + +//! \file +#include "lmic.h" + +#if defined(DISABLE_BEACONS) && !defined(DISABLE_PING) +#error Ping needs beacon tracking +#endif + +#if !defined(MINRX_SYMS) +#define MINRX_SYMS 5 +#endif // !defined(MINRX_SYMS) +#define PAMBL_SYMS 8 +#define PAMBL_FSK 5 +#define PRERX_FSK 1 +#define RXLEN_FSK (1+5+2) + +#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_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 us2osticks(AIRTIME_BCN) +#if defined(CFG_eu868) +#define DNW2_SAFETY_ZONE ms2osticks(3000) +#endif +#if defined(CFG_us915) +#define DNW2_SAFETY_ZONE ms2osticks(750) +#endif + +// Special APIs - for development or testing +#define isTESTMODE() 0 + +DEFINE_LMIC; + + +// Fwd decls. +static void engineUpdate(void); +static void startScan (void); + + +// ================================================================================ +// BEG OS - default implementations for certain OS suport functions + +#if !defined(HAS_os_calls) + +#if !defined(os_rlsbf2) +u2_t os_rlsbf2 (xref2cu1_t buf) { + return (u2_t)((u2_t)buf[0] | ((u2_t)buf[1]<<8)); +} +#endif + +#if !defined(os_rlsbf4) +u4_t os_rlsbf4 (xref2cu1_t buf) { + return (u4_t)((u4_t)buf[0] | ((u4_t)buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24)); +} +#endif + + +#if !defined(os_rmsbf4) +u4_t os_rmsbf4 (xref2cu1_t buf) { + return (u4_t)((u4_t)buf[3] | ((u4_t)buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24)); +} +#endif + + +#if !defined(os_wlsbf2) +void os_wlsbf2 (xref2u1_t buf, u2_t v) { + buf[0] = v; + buf[1] = v>>8; +} +#endif + +#if !defined(os_wlsbf4) +void os_wlsbf4 (xref2u1_t buf, u4_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 (xref2u1_t buf, u4_t v) { + buf[3] = v; + buf[2] = v>>8; + buf[1] = v>>16; + buf[0] = v>>24; +} +#endif + +#if !defined(os_getBattLevel) +u1_t os_getBattLevel (void) { + return MCMD_DEVS_BATT_NOINFO; +} +#endif + +#if !defined(os_crc16) +// New CRC-16 CCITT(XMODEM) checksum for beacons: +u2_t os_crc16 (xref2u1_t data, uint len) { + u2_t remainder = 0; + u2_t polynomial = 0x1021; + for( uint i = 0; i < len; i++ ) { + remainder ^= data[i] << 8; + for( u1_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 AES + +static void micB0 (u4_t devaddr, u4_t seqno, int dndir, int len) { + os_clearMem(AESaux,16); + AESaux[0] = 0x49; + AESaux[5] = dndir?1:0; + AESaux[15] = len; + os_wlsbf4(AESaux+ 6,devaddr); + os_wlsbf4(AESaux+10,seqno); +} + + +static int aes_verifyMic (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t pdu, int len) { + micB0(devaddr, seqno, dndir, len); + os_copyMem(AESkey,key,16); + return os_aes(AES_MIC, pdu, len) == os_rmsbf4(pdu+len); +} + + +static void aes_appendMic (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t pdu, int len) { + micB0(devaddr, seqno, dndir, len); + os_copyMem(AESkey,key,16); + // MSB because of internal structure of AES + os_wmsbf4(pdu+len, os_aes(AES_MIC, pdu, len)); +} + + +static void aes_appendMic0 (xref2u1_t pdu, int len) { + os_getDevKey(AESkey); + os_wmsbf4(pdu+len, os_aes(AES_MIC|AES_MICNOAUX, pdu, len)); // MSB because of internal structure of AES +} + + +static int aes_verifyMic0 (xref2u1_t pdu, int len) { + os_getDevKey(AESkey); + return os_aes(AES_MIC|AES_MICNOAUX, pdu, len) == os_rmsbf4(pdu+len); +} + + +static void aes_encrypt (xref2u1_t pdu, int len) { + os_getDevKey(AESkey); + os_aes(AES_ENC, pdu, len); +} + + +static void aes_cipher (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t payload, int len) { + if( len <= 0 ) + return; + os_clearMem(AESaux, 16); + AESaux[0] = AESaux[15] = 1; // mode=cipher / dir=down / block counter=1 + AESaux[5] = dndir?1:0; + os_wlsbf4(AESaux+ 6,devaddr); + os_wlsbf4(AESaux+10,seqno); + os_copyMem(AESkey,key,16); + os_aes(AES_CTR, payload, len); +} + + +static void aes_sessKeys (u2_t devnonce, xref2cu1_t artnonce, xref2u1_t nwkkey, xref2u1_t artkey) { + os_clearMem(nwkkey, 16); + nwkkey[0] = 0x01; + os_copyMem(nwkkey+1, artnonce, LEN_ARTNONCE+LEN_NETID); + os_wlsbf2(nwkkey+1+LEN_ARTNONCE+LEN_NETID, devnonce); + os_copyMem(artkey, nwkkey, 16); + artkey[0] = 0x02; + + os_getDevKey(AESkey); + os_aes(AES_ENC, nwkkey, 16); + os_getDevKey(AESkey); + os_aes(AES_ENC, artkey, 16); +} + +// END AES +// ================================================================================ + + +// ================================================================================ +// BEG LORA + +#if defined(CFG_eu868) // ======================================== + +#define maxFrameLen(dr) ((dr)<=DR_SF9 ? maxFrameLens[(dr)] : 0xFF) +const u1_t maxFrameLens [] = { 64,64,64,123 }; + +const u1_t _DR2RPS_CRC[] = { + ILLEGAL_RPS, + (u1_t)MAKERPS(SF12, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF11, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF10, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF9, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF8, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF7, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF7, BW250, CR_4_5, 0, 0), + (u1_t)MAKERPS(FSK, BW125, CR_4_5, 0, 0), + ILLEGAL_RPS +}; + +static const s1_t TXPOWLEVELS[] = { + 20, 14, 11, 8, 5, 2, 0,0, 0,0,0,0, 0,0,0,0 +}; +#define pow2dBm(mcmd_ladr_p1) (TXPOWLEVELS[(mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT]) + +#elif defined(CFG_us915) // ======================================== + +#define maxFrameLen(dr) ((dr)<=DR_SF11CR ? maxFrameLens[(dr)] : 0xFF) +const u1_t maxFrameLens [] = { 24,66,142,255,255,255,255,255, 66,142 }; + +const u1_t _DR2RPS_CRC[] = { + ILLEGAL_RPS, + MAKERPS(SF10, BW125, CR_4_5, 0, 0), + MAKERPS(SF9 , BW125, CR_4_5, 0, 0), + MAKERPS(SF8 , BW125, CR_4_5, 0, 0), + MAKERPS(SF7 , BW125, CR_4_5, 0, 0), + MAKERPS(SF8 , BW500, CR_4_5, 0, 0), + ILLEGAL_RPS , + ILLEGAL_RPS , + ILLEGAL_RPS , + MAKERPS(SF12, BW500, CR_4_5, 0, 0), + MAKERPS(SF11, BW500, CR_4_5, 0, 0), + MAKERPS(SF10, BW500, CR_4_5, 0, 0), + MAKERPS(SF9 , BW500, CR_4_5, 0, 0), + MAKERPS(SF8 , BW500, CR_4_5, 0, 0), + MAKERPS(SF7 , BW500, CR_4_5, 0, 0), + ILLEGAL_RPS +}; + +#define pow2dBm(mcmd_ladr_p1) ((s1_t)(30 - (((mcmd_ladr_p1)&MCMD_LADR_POW_MASK)<<1))) + +#endif // ================================================ + +static const u1_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, u1_t plen) { + u1_t bw = getBw(rps); // 0,1,2 = 125,250,500kHz + u1_t sf = getSf(rps); // 0=FSK, 1..6 = SF7..12 + if( sf == FSK ) { + return (plen+/*preamble*/5+/*syncword*/3+/*len*/1+/*crc*/2) * /*bits/byte*/8 + * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000; + } + u1_t sfx = 4*(sf+(7-SF7)); + u1_t q = sfx - (sf >= SF11 ? 8 : 0); + 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 updr2rps (dr_t dr); +extern inline rps_t dndr2rps (dr_t dr); +extern inline int isFasterDR (dr_t dr1, dr_t dr2); +extern inline int isSlowerDR (dr_t dr1, dr_t dr2); +extern inline dr_t incDR (dr_t dr); +extern inline dr_t decDR (dr_t dr); +extern inline dr_t assertDR (dr_t dr); +extern inline dr_t validDR (dr_t dr); +extern inline dr_t lowerDR (dr_t dr, u1_t n); + +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 rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc); +extern inline int sameSfBw (rps_t r1, rps_t r2); + +// END LORA +// ================================================================================ + + +// Adjust DR for TX retries +// - indexed by retry count +// - return steps to lower DR +static const u1_t DRADJUST[2+TXCONF_ATTEMPTS] = { + // normal frames - 1st try / no retry + 0, + // confirmed frames + 0,0,1,0,1,0,1,0,0 +}; + + +// Table below defines the size of one symbol as +// symtime = 256us * 2^T(sf,bw) +// 256us is called one symunit. +// SF: +// BW: |__7___8___9__10__11__12 +// 125kHz | 2 3 4 5 6 7 +// 250kHz | 1 2 3 4 5 6 +// 500kHz | 0 1 2 3 4 5 +// +// Times for half symbol per DR +// Per DR table to minimize rounding errors +static const ostime_t DR2HSYM_osticks[] = { +#if defined(CFG_eu868) +#define dr2hsym(dr) (DR2HSYM_osticks[(dr)]) + us2osticksRound(128<<7), // DR_SF12 + us2osticksRound(128<<6), // DR_SF11 + us2osticksRound(128<<5), // DR_SF10 + us2osticksRound(128<<4), // DR_SF9 + us2osticksRound(128<<3), // DR_SF8 + us2osticksRound(128<<2), // DR_SF7 + us2osticksRound(128<<1), // DR_SF7B + us2osticksRound(80) // FSK -- not used (time for 1/2 byte) +#elif defined(CFG_us915) +#define dr2hsym(dr) (DR2HSYM_osticks[(dr)&7]) // map DR_SFnCR -> 0-6 + us2osticksRound(128<<5), // DR_SF10 DR_SF12CR + us2osticksRound(128<<4), // DR_SF9 DR_SF11CR + us2osticksRound(128<<3), // DR_SF8 DR_SF10CR + us2osticksRound(128<<2), // DR_SF7 DR_SF9CR + us2osticksRound(128<<1), // DR_SF8C DR_SF8CR + us2osticksRound(128<<0) // ------ DR_SF7CR +#endif +}; + + +#if !defined(DISABLE_BEACONS) +static ostime_t calcRxWindow (u1_t secs, dr_t dr) { + ostime_t rxoff, err; + if( secs==0 ) { + // aka 128 secs (next becaon) + rxoff = LMIC.drift; + err = LMIC.lastDriftDiff; + } else { + // scheduled RX window within secs into current beacon period + rxoff = (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp; + err = (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp; + } + u1_t rxsyms = MINRX_SYMS; + err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns; + LMIC.rxsyms = MINRX_SYMS + (err / dr2hsym(dr)); + + return (rxsyms-PAMBL_SYMS) * dr2hsym(dr) + rxoff; +} + + +// Setup beacon RX parameters assuming we have an error of ms (aka +/-(ms/2)) +static void calcBcnRxWindowFromMillis (u1_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(DR_BCN); + LMIC.bcnRxsyms = MINRX_SYMS + ms2osticksCeil(ms) / hsym; + LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-PAMBL_SYMS) * hsym; +} +#endif // !DISABLE_BEACONS + + +#if !defined(DISABLE_PING) +// Setup scheduled RX window (ping/multicast slot) +static void rxschedInit (xref2rxsched_t rxsched) { + os_clearMem(AESkey,16); + os_clearMem(LMIC.frame+8,8); + os_wlsbf4(LMIC.frame, LMIC.bcninfo.time); + os_wlsbf4(LMIC.frame+4, LMIC.devaddr); + os_aes(AES_ENC,LMIC.frame,16); + u1_t intvExp = rxsched->intvExp; + ostime_t off = os_rlsbf2(LMIC.frame) & (0x0FFF >> (7 - intvExp)); // random offset (slot units) + rxsched->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 (xref2rxsched_t rxsched, ostime_t cando) { + again: + if( rxsched->rxtime - cando >= 0 ) + return 1; + u1_t slot; + if( (slot=rxsched->slot) >= 128 ) + return 0; + u1_t intv = 1<intvExp; + 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 // !DISABLE_PING) + + +static ostime_t rndDelay (u1_t secSpan) { + u2_t r = os_getRndU2(); + ostime_t delay = r; + if( delay > OSTICKS_PER_SEC ) + delay = r % (u2_t)OSTICKS_PER_SEC; + if( secSpan > 0 ) + delay += ((u1_t)r % secSpan) * OSTICKS_PER_SEC; + return delay; +} + + +static void txDelay (ostime_t reftime, u1_t secSpan) { + reftime += rndDelay(secSpan); + if( LMIC.globalDutyRate == 0 || (reftime - LMIC.globalDutyAvail) > 0 ) { + LMIC.globalDutyAvail = reftime; + LMIC.opmode |= OP_RNDTX; + } +} + + +static void setDrJoin (u1_t reason, u1_t dr) { + EV(drChange, INFO, (e_.reason = reason, + e_.deveui = MAIN::CDEV->getEui(), + e_.dr = dr|DR_PAGE, + e_.txpow = LMIC.adrTxPow, + e_.prevdr = LMIC.datarate|DR_PAGE, + e_.prevtxpow = LMIC.adrTxPow)); + LMIC.datarate = dr; + DO_DEVDB(LMIC.datarate,datarate); +} + + +static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) { + EV(drChange, INFO, (e_.reason = reason, + e_.deveui = MAIN::CDEV->getEui(), + e_.dr = dr|DR_PAGE, + e_.txpow = pow, + e_.prevdr = LMIC.datarate|DR_PAGE, + e_.prevtxpow = LMIC.adrTxPow)); + + if( pow != KEEP_TXPOW ) + LMIC.adrTxPow = pow; + if( LMIC.datarate != dr ) { + LMIC.datarate = dr; + DO_DEVDB(LMIC.datarate,datarate); + LMIC.opmode |= OP_NEXTCHNL; + } +} + + +#if !defined(DISABLE_PING) +void LMIC_stopPingable (void) { + LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI); +} + + +void LMIC_setPingable (u1_t intvExp) { + // Change setting + LMIC.ping.intvExp = (intvExp & 0x7); + LMIC.opmode |= OP_PINGABLE; + // App may call LMIC_enableTracking() explicitely before + // Otherwise tracking is implicitly enabled here + if( (LMIC.opmode & (OP_TRACK|OP_SCAN)) == 0 && LMIC.bcninfoTries == 0 ) + LMIC_enableTracking(0); +} + +#endif // !DISABLE_PING + +#if defined(CFG_eu868) +// ================================================================================ +// +// BEG: EU868 related stuff +// +enum { NUM_DEFAULT_CHANNELS=3 }; +static const u4_t iniChannelFreq[6] = { + // Join frequencies and duty cycle limit (0.1%) + EU868_F1|BAND_MILLI, EU868_F2|BAND_MILLI, EU868_F3|BAND_MILLI, + // Default operational frequencies + EU868_F1|BAND_CENTI, EU868_F2|BAND_CENTI, EU868_F3|BAND_CENTI, +}; + +static void initDefaultChannels (bit_t join) { + os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq)); + os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap)); + os_clearMem(&LMIC.bands, sizeof(LMIC.bands)); + + LMIC.channelMap = 0x07; + u1_t su = join ? 0 : 3; + for( u1_t fu=0; fu<3; fu++,su++ ) { + LMIC.channelFreq[fu] = iniChannelFreq[su]; + LMIC.channelDrMap[fu] = DR_RANGE_MAP(DR_SF12,DR_SF7); + } + + LMIC.bands[BAND_MILLI].txcap = 1000; // 0.1% + LMIC.bands[BAND_MILLI].txpow = 14; + LMIC.bands[BAND_MILLI].lastchnl = os_getRndU1() % MAX_CHANNELS; + LMIC.bands[BAND_CENTI].txcap = 100; // 1% + LMIC.bands[BAND_CENTI].txpow = 14; + LMIC.bands[BAND_CENTI].lastchnl = os_getRndU1() % MAX_CHANNELS; + LMIC.bands[BAND_DECI ].txcap = 10; // 10% + LMIC.bands[BAND_DECI ].txpow = 27; + LMIC.bands[BAND_DECI ].lastchnl = os_getRndU1() % MAX_CHANNELS; + LMIC.bands[BAND_MILLI].avail = + LMIC.bands[BAND_CENTI].avail = + LMIC.bands[BAND_DECI ].avail = os_getTime(); +} + +bit_t LMIC_setupBand (u1_t bandidx, s1_t txpow, u2_t txcap) { + if( bandidx > BAND_AUX ) return 0; + band_t* b = &LMIC.bands[bandidx]; + b->txpow = txpow; + b->txcap = txcap; + b->avail = os_getTime(); + b->lastchnl = os_getRndU1() % MAX_CHANNELS; + return 1; +} + +bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { + if( chidx >= MAX_CHANNELS ) + return 0; + if( band == -1 ) { + if( freq >= 869400000 && freq <= 869650000 ) + freq |= BAND_DECI; // 10% 27dBm + else if( (freq >= 868000000 && freq <= 868600000) || + (freq >= 869700000 && freq <= 870000000) ) + freq |= BAND_CENTI; // 1% 14dBm + else + freq |= BAND_MILLI; // 0.1% 14dBm + } else { + if( band > BAND_AUX ) return 0; + freq = (freq&~3) | band; + } + LMIC.channelFreq [chidx] = freq; + LMIC.channelDrMap[chidx] = drmap==0 ? DR_RANGE_MAP(DR_SF12,DR_SF7) : drmap; + LMIC.channelMap |= 1<> 8) * 100; + if( freq < EU868_FREQ_MIN || freq > EU868_FREQ_MAX ) + freq = 0; + return freq; +} + +static u1_t mapChannels (u1_t chpage, u2_t chmap) { + // Bad page, disable all channel, enable non-existent + if( chpage != 0 || chmap==0 || (chmap & ~LMIC.channelMap) != 0 ) + return 0; // illegal input + for( u1_t chnl=0; chnltxpow; + band->avail = txbeg + airtime * band->txcap; + if( LMIC.globalDutyRate != 0 ) + LMIC.globalDutyAvail = txbeg + (airtime< 1 + lmic_printf("%lu: Updating info for TX at %lu, airtime will be %lu. Setting available time for band %ld to %lu\n", os_getTime(), txbeg, airtime, freq & 0x3, band->avail); + if( LMIC.globalDutyRate != 0 ) + lmic_printf("%lu: Updating global duty avail to %lu\n", os_getTime(), LMIC.globalDutyAvail); + #endif +} + +static ostime_t nextTx (ostime_t now) { + u1_t bmap=0xF; + do { + ostime_t mintime = now + /*8h*/sec2osticks(28800); + u1_t band=0; + for( u1_t bi=0; bi<4; bi++ ) { + if( (bmap & (1< 0 ) { + #if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Considering band %d, which is available at %lu\n", os_getTime(), bi, LMIC.bands[bi].avail); + #endif + mintime = LMIC.bands[band = bi].avail; + } + } + // Find next channel in given band + u1_t chnl = LMIC.bands[band].lastchnl; + for( u1_t ci=0; ci= MAX_CHANNELS ) + chnl -= MAX_CHANNELS; + if( (LMIC.channelMap & (1< 1 + lmic_printf("%lu: No channel found in band %d\n", os_getTime(), band); + #endif + if( (bmap &= ~(1<>LMIC.datarate)); + #if LMIC_DEBUG_LEVEL > 1 + if (failed) + lmic_printf("%lu: Join failed\n", os_getTime()); + else + lmic_printf("%lu: Scheduling next join at %lu\n", os_getTime(), LMIC.txend); + #endif + // 1 - triggers EV_JOIN_FAILED event + return failed; +} +#endif // !DISABLE_JOIN + +// +// END: EU868 related stuff +// +// ================================================================================ +#elif defined(CFG_us915) +// ================================================================================ +// +// BEG: US915 related stuff +// + + +static void initDefaultChannels (void) { + for( u1_t i=0; i<4; i++ ) + LMIC.channelMap[i] = 0xFFFF; + LMIC.channelMap[4] = 0x00FF; +} + +static u4_t convFreq (xref2u1_t ptr) { + u4_t freq = (os_rlsbf4(ptr-1) >> 8) * 100; + if( freq < US915_FREQ_MIN || freq > US915_FREQ_MAX ) + freq = 0; + return freq; +} + +bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { + if( chidx < 72 || chidx >= 72+MAX_XCHANNELS ) + return 0; // channels 0..71 are hardwired + chidx -= 72; + LMIC.xchFreq[chidx] = freq; + LMIC.xchDrMap[chidx] = drmap==0 ? DR_RANGE_MAP(DR_SF10,DR_SF8C) : drmap; + LMIC.channelMap[chidx>>4] |= (1<<(chidx&0xF)); + return 1; +} + +void LMIC_disableChannel (u1_t channel) { + if( channel < 72+MAX_XCHANNELS ) + LMIC.channelMap[channel>>4] &= ~(1<<(channel&0xF)); +} + +void LMIC_enableChannel (u1_t channel) { + if( channel < 72+MAX_XCHANNELS ) + LMIC.channelMap[channel>>4] |= (1<<(channel&0xF)); +} + +void LMIC_enableSubBand (u1_t band) { + ASSERT(band < 8); + u1_t start = band * 8; + u1_t end = start + 8; + for (int channel=start; channel < end; ++channel ) + LMIC_enableChannel(channel); +} +void LMIC_disableSubBand (u1_t band) { + ASSERT(band < 8); + u1_t start = band * 8; + u1_t end = start + 8; + for (int channel=start; channel < end; ++channel ) + LMIC_disableChannel(channel); +} +void LMIC_selectSubBand (u1_t band) { + ASSERT(band < 8); + for (int b=0; b<8; ++b) { + if (band==b) + LMIC_enableSubBand(b); + else + LMIC_disableSubBand(b); + } +} + +static u1_t mapChannels (u1_t chpage, u2_t chmap) { + if( chpage == MCMD_LADR_CHP_125ON || chpage == MCMD_LADR_CHP_125OFF ) { + u2_t en125 = chpage == MCMD_LADR_CHP_125ON ? 0xFFFF : 0x0000; + for( u1_t u=0; u<4; u++ ) + LMIC.channelMap[u] = en125; + LMIC.channelMap[64/16] = chmap; + } else { + if( chpage >= (72+MAX_XCHANNELS+15)/16 ) + return 0; + LMIC.channelMap[chpage] = chmap; + } + return 1; +} + +static void updateTx (ostime_t txbeg) { + u1_t chnl = LMIC.txChnl; + if( chnl < 64 ) { + LMIC.freq = US915_125kHz_UPFBASE + chnl*US915_125kHz_UPFSTEP; + LMIC.txpow = 30; + return; + } + LMIC.txpow = 26; + if( chnl < 64+8 ) { + LMIC.freq = US915_500kHz_UPFBASE + (chnl-64)*US915_500kHz_UPFSTEP; + } else { + ASSERT(chnl < 64+8+MAX_XCHANNELS); + LMIC.freq = LMIC.xchFreq[chnl-72]; + } + + // Update global duty cycle stats + if( LMIC.globalDutyRate != 0 ) { + ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen); + LMIC.globalDutyAvail = txbeg + (airtime<= DR_SF8C ) { // 500kHz + u1_t map = LMIC.channelMap[64/16]&0xFF; + for( u1_t i=0; i<8; i++ ) { + if( (map & (1<<(++LMIC.chRnd & 7))) != 0 ) { + LMIC.txChnl = 64 + (LMIC.chRnd & 7); + return; + } + } + } else { // 125kHz + for( u1_t i=0; i<64; i++ ) { + u1_t chnl = ++LMIC.chRnd & 0x3F; + if( (LMIC.channelMap[(chnl >> 4)] & (1<<(chnl & 0xF))) != 0 ) { + LMIC.txChnl = chnl; + return; + } + } + } + // No feasible channel found! Keep old one. +} + +#if !defined(DISABLE_BEACONS) +static void setBcnRxParams (void) { + LMIC.dataLen = 0; + LMIC.freq = US915_500kHz_DNFBASE + LMIC.bcnChnl * US915_500kHz_DNFSTEP; + LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN); +} +#endif // !DISABLE_BEACONS + +#define setRx1Params() { \ + LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; \ + if( /* TX datarate */LMIC.dndr < DR_SF8C ) \ + LMIC.dndr += DR_SF10CR - DR_SF10; \ + else if( LMIC.dndr == DR_SF8C ) \ + LMIC.dndr = DR_SF7CR; \ + LMIC.rps = dndr2rps(LMIC.dndr); \ +} + +#if !defined(DISABLE_JOIN) +static void initJoinLoop (void) { + LMIC.chRnd = 0; + LMIC.txChnl = 0; + LMIC.adrTxPow = 20; + ASSERT((LMIC.opmode & OP_NEXTCHNL)==0); + LMIC.txend = os_getTime(); + setDrJoin(DRCHG_SET, DR_SF7); +} + +static ostime_t nextJoinState (void) { + // Try the following: + // SF7/8/9/10 on a random channel 0..63 + // SF8C on a random channel 64..71 + // + u1_t failed = 0; + if( LMIC.datarate != DR_SF8C ) { + LMIC.txChnl = 64+(LMIC.txChnl&7); + setDrJoin(DRCHG_SET, DR_SF8C); + } else { + LMIC.txChnl = os_getRndU1() & 0x3F; + s1_t dr = DR_SF7 - ++LMIC.txCnt; + if( dr < DR_SF10 ) { + dr = DR_SF10; + failed = 1; // All DR exhausted - signal failed + } + setDrJoin(DRCHG_SET, dr); + } + LMIC.opmode &= ~OP_NEXTCHNL; + LMIC.txend = os_getTime() + + (isTESTMODE() + // Avoid collision with JOIN ACCEPT being sent by GW (but we missed it - GW is still busy) + ? DNW2_SAFETY_ZONE + // Otherwise: randomize join (street lamp case): + // SF10:16, SF9=8,..SF8C:1secs + : rndDelay(16>>LMIC.datarate)); + // 1 - triggers EV_JOIN_FAILED event + return failed; +} +#endif // !DISABLE_JOIN + +// +// END: US915 related stuff +// +// ================================================================================ +#else +#error Unsupported frequency band! +#endif + + +static void runEngineUpdate (xref2osjob_t osjob) { + engineUpdate(); +} + + +static void reportEvent (ev_t ev) { + EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV, + e_.eui = MAIN::CDEV->getEui(), + e_.info = ev)); + ON_LMIC_EVENT(ev); + engineUpdate(); +} + + +static void runReset (xref2osjob_t osjob) { + // Disable session + LMIC_reset(); +#if !defined(DISABLE_JOIN) + LMIC_startJoining(); +#endif // !DISABLE_JOIN + reportEvent(EV_RESET); +} + +static void stateJustJoined (void) { + LMIC.seqnoDn = LMIC.seqnoUp = 0; + LMIC.rejoinCnt = 0; + LMIC.dnConf = LMIC.adrChanged = LMIC.ladrAns = LMIC.devsAns = 0; +#if !defined(DISABLE_MCMD_SNCH_REQ) + LMIC.snchAns = 0; +#endif +#if !defined(DISABLE_MCMD_DN2P_SET) + LMIC.dn2Ans = 0; +#endif + LMIC.moreData = 0; +#if !defined(DISABLE_MCMD_DCAP_REQ) + LMIC.dutyCapAns = 0; +#endif +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) + LMIC.pingSetAns = 0; +#endif + LMIC.upRepeat = 0; + LMIC.adrAckReq = LINK_CHECK_INIT; + LMIC.dn2Dr = DR_DNW2; + LMIC.dn2Freq = FREQ_DNW2; +#if !defined(DISABLE_BEACONS) + LMIC.bcnChnl = CHNL_BCN; +#endif +#if !defined(DISABLE_PING) + LMIC.ping.freq = FREQ_PING; + LMIC.ping.dr = DR_PING; +#endif +} + + +// ================================================================================ +// Decoding frames + + +#if !defined(DISABLE_BEACONS) +// Decode beacon - do not overwrite bcninfo unless we have a match! +static int decodeBeacon (void) { + ASSERT(LMIC.dataLen == LEN_BCN); // implicit header RX guarantees this + xref2u1_t d = LMIC.frame; + if( +#if CFG_eu868 + d[OFF_BCN_CRC1] != (u1_t)os_crc16(d,OFF_BCN_CRC1) +#elif CFG_us915 + os_rlsbf2(&d[OFF_BCN_CRC1]) != os_crc16(d,OFF_BCN_CRC1) +#endif + ) + return 0; // first (common) part fails CRC check + // First set of fields is ok + u4_t bcnnetid = os_rlsbf4(&d[OFF_BCN_NETID]) & 0xFFFFFF; + if( bcnnetid != LMIC.netid ) + return -1; // not the beacon we're looking for + + 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 - AIRTIME_BCN_osticks; + LMIC.bcninfo.time = os_rlsbf4(&d[OFF_BCN_TIME]); + LMIC.bcninfo.flags |= BCN_PARTIAL; + + // Check 2nd set + if( os_rlsbf2(&d[OFF_BCN_CRC2]) != os_crc16(d,OFF_BCN_CRC2) ) + return 1; + // Second set of fields is ok + LMIC.bcninfo.lat = (s4_t)os_rlsbf4(&d[OFF_BCN_LAT-1]) >> 8; // read as signed 24-bit + LMIC.bcninfo.lon = (s4_t)os_rlsbf4(&d[OFF_BCN_LON-1]) >> 8; // ditto + LMIC.bcninfo.info = d[OFF_BCN_INFO]; + LMIC.bcninfo.flags |= BCN_FULL; + return 2; +} +#endif // !DISABLE_BEACONS + + +static bit_t decodeFrame (void) { + xref2u1_t d = LMIC.frame; + u1_t hdr = d[0]; + u1_t ftype = hdr & HDR_FTYPE; + int dlen = LMIC.dataLen; +#if LMIC_DEBUG_LEVEL > 0 + const char *window = (LMIC.txrxFlags & TXRX_DNW1) ? "RX1" : ((LMIC.txrxFlags & TXRX_DNW2) ? "RX2" : "Other"); +#endif + if( dlen < OFF_DAT_OPTS+4 || + (hdr & HDR_MAJOR) != HDR_MAJOR_V1 || + (ftype != HDR_FTYPE_DADN && ftype != HDR_FTYPE_DCDN) ) { + // Basic sanity checks failed + EV(specCond, WARN, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME, + e_.eui = MAIN::CDEV->getEui(), + e_.info = dlen < 4 ? 0 : os_rlsbf4(&d[dlen-4]), + e_.info2 = hdr + (dlen<<8))); + norx: +#if LMIC_DEBUG_LEVEL > 0 + lmic_printf("%lu: Invalid downlink, window=%s\n", os_getTime(), window); +#endif + 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]; + u4_t addr = os_rlsbf4(&d[OFF_DAT_ADDR]); + u4_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 ) { + EV(specCond, WARN, (e_.reason = EV::specCond_t::ALIEN_ADDRESS, + e_.eui = MAIN::CDEV->getEui(), + e_.info = addr, + e_.info2 = LMIC.devaddr)); + goto norx; + } + if( poff > pend ) { + EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME, + e_.eui = MAIN::CDEV->getEui(), + e_.info = 0x1000000 + (poff-pend) + (fct<<8) + (dlen<<16))); + goto norx; + } + + int port = -1; + int replayConf = 0; + + if( pend > poff ) + port = d[poff++]; + + seqno = LMIC.seqnoDn + (u2_t)(seqno - LMIC.seqnoDn); + + if( !aes_verifyMic(LMIC.nwkKey, LMIC.devaddr, seqno, /*dn*/1, d, pend) ) { + EV(spe3Cond, ERR, (e_.reason = EV::spe3Cond_t::CORRUPTED_MIC, + e_.eui1 = MAIN::CDEV->getEui(), + e_.info1 = Base::lsbf4(&d[pend]), + e_.info2 = seqno, + e_.info3 = LMIC.devaddr)); + goto norx; + } + if( seqno < LMIC.seqnoDn ) { + if( (s4_t)seqno > (s4_t)LMIC.seqnoDn ) { + EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.seqnoDn, + e_.info2 = seqno)); + goto norx; + } + if( seqno != LMIC.seqnoDn-1 || !LMIC.dnConf || ftype != HDR_FTYPE_DCDN ) { + EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_OBSOLETE, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.seqnoDn, + e_.info2 = seqno)); + goto norx; + } + // Replay of previous sequence number allowed only if + // previous frame and repeated both requested confirmation + replayConf = 1; + } + else { + if( seqno > LMIC.seqnoDn ) { + EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_SKIP, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.seqnoDn, + e_.info2 = seqno)); + } + LMIC.seqnoDn = seqno+1; // next number to be expected + DO_DEVDB(LMIC.seqnoDn,seqnoDn); + // 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) ) + LMIC.opmode |= OP_POLL; + + // We heard from network + LMIC.adrChanged = LMIC.rejoinCnt = 0; + if( LMIC.adrAckReq != LINK_CHECK_OFF ) + LMIC.adrAckReq = LINK_CHECK_INIT; + + // Process OPTS + int m = LMIC.rssi - RSSI_OFF - getSensitivity(LMIC.rps); + LMIC.margin = m < 0 ? 0 : m > 254 ? 254 : m; + + xref2u1_t opts = &d[OFF_DAT_OPTS]; + int oidx = 0; + while( oidx < olen ) { + switch( opts[oidx] ) { + case MCMD_LCHK_ANS: { + //int gwmargin = opts[oidx+1]; + //int ngws = opts[oidx+2]; + oidx += 3; + continue; + } + case MCMD_LADR_REQ: { + u1_t p1 = opts[oidx+1]; // txpow + DR + u2_t chmap = os_rlsbf2(&opts[oidx+2]);// list of enabled channels + u1_t chpage = opts[oidx+4] & MCMD_LADR_CHPAGE_MASK; // channel page + u1_t uprpt = opts[oidx+4] & MCMD_LADR_REPEAT_MASK; // up repeat count + oidx += 5; + + LMIC.ladrAns = 0x80 | // Include an answer into next frame up + MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK; + if( !mapChannels(chpage, chmap) ) + LMIC.ladrAns &= ~MCMD_LADR_ANS_CHACK; + dr_t dr = (dr_t)(p1>>MCMD_LADR_DR_SHIFT); + if( !validDR(dr) ) { + LMIC.ladrAns &= ~MCMD_LADR_ANS_DRACK; + EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD, + e_.eui = MAIN::CDEV->getEui(), + e_.info = Base::lsbf4(&d[pend]), + e_.info2 = Base::msbf4(&opts[oidx-4]))); + } + if( (LMIC.ladrAns & 0x7F) == (MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK) ) { + // Nothing went wrong - use settings + LMIC.upRepeat = uprpt; + setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1)); + } + LMIC.adrChanged = 1; // Trigger an ACK to NWK + continue; + } + case MCMD_DEVS_REQ: { + LMIC.devsAns = 1; + oidx += 1; + continue; + } + case MCMD_DN2P_SET: { +#if !defined(DISABLE_MCMD_DN2P_SET) + dr_t dr = (dr_t)(opts[oidx+1] & 0x0F); + u4_t freq = convFreq(&opts[oidx+2]); + LMIC.dn2Ans = 0x80; // answer pending + if( validDR(dr) ) + LMIC.dn2Ans |= MCMD_DN2P_ANS_DRACK; + if( freq != 0 ) + LMIC.dn2Ans |= MCMD_DN2P_ANS_CHACK; + if( LMIC.dn2Ans == (0x80|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK) ) { + LMIC.dn2Dr = dr; + LMIC.dn2Freq = freq; + DO_DEVDB(LMIC.dn2Dr,dn2Dr); + DO_DEVDB(LMIC.dn2Freq,dn2Freq); + } +#endif // !DISABLE_MCMD_DN2P_SET + oidx += 5; + continue; + } + case MCMD_DCAP_REQ: { +#if !defined(DISABLE_MCMD_DCAP_REQ) + u1_t cap = opts[oidx+1]; + // A value cap=0xFF means device is OFF unless enabled again manually. + if( cap==0xFF ) + LMIC.opmode |= OP_SHUTDOWN; // stop any sending + LMIC.globalDutyRate = cap & 0xF; + LMIC.globalDutyAvail = os_getTime(); + DO_DEVDB(cap,dutyCap); + LMIC.dutyCapAns = 1; + oidx += 2; +#endif // !DISABLE_MCMD_DCAP_REQ + continue; + } + case MCMD_SNCH_REQ: { +#if !defined(DISABLE_MCMD_SNCH_REQ) + u1_t chidx = opts[oidx+1]; // channel + u4_t freq = convFreq(&opts[oidx+2]); // freq + u1_t drs = opts[oidx+5]; // datarate span + LMIC.snchAns = 0x80; + if( freq != 0 && LMIC_setupChannel(chidx, freq, DR_RANGE_MAP(drs&0xF,drs>>4), -1) ) + LMIC.snchAns |= MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK; +#endif // !DISABLE_MCMD_SNCH_REQ + oidx += 6; + continue; + } + case MCMD_PING_SET: { +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) + u4_t freq = convFreq(&opts[oidx+1]); + u1_t flags = 0x80; + if( freq != 0 ) { + flags |= MCMD_PING_ANS_FQACK; + LMIC.ping.freq = freq; + DO_DEVDB(LMIC.ping.intvExp, pingIntvExp); + DO_DEVDB(LMIC.ping.freq, pingFreq); + DO_DEVDB(LMIC.ping.dr, pingDr); + } + LMIC.pingSetAns = flags; +#endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING + oidx += 4; + continue; + } + case MCMD_BCNI_ANS: { +#if !defined(DISABLE_MCMD_BCNI_ANS) && !defined(DISABLE_BEACONS) + // Ignore if tracking already enabled + if( (LMIC.opmode & OP_TRACK) == 0 ) { + LMIC.bcnChnl = opts[oidx+3]; + // Enable tracking - bcninfoTries + LMIC.opmode |= OP_TRACK; + // Cleared later in txComplete handling - triggers EV_BEACON_FOUND + ASSERT(LMIC.bcninfoTries!=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 + + EV(lostFrame, INFO, (e_.reason = EV::lostFrame_t::MCMD_BCNI_ANS, + e_.eui = MAIN::CDEV->getEui(), + e_.lostmic = Base::lsbf4(&d[pend]), + e_.info = (LMIC.missedBcns | + (osticks2us(LMIC.bcninfo.txtime + BCN_INTV_osticks + - LMIC.bcnRxtime) << 8)), + e_.time = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks))); + } +#endif // !DISABLE_MCMD_BCNI_ANS && !DISABLE_BEACONS + oidx += 4; + continue; + } + } + EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD, + e_.eui = MAIN::CDEV->getEui(), + e_.info = Base::lsbf4(&d[pend]), + e_.info2 = Base::msbf4(&opts[oidx]))); + break; + } + if( oidx != olen ) { + EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME, + e_.eui = MAIN::CDEV->getEui(), + e_.info = 0x1000000 + (oidx) + (olen<<8))); + } + + if( !replayConf ) { + // Handle payload only if not a replay + // Decrypt payload - if any + if( port >= 0 && pend-poff > 0 ) + aes_cipher(port <= 0 ? LMIC.nwkKey : LMIC.artKey, LMIC.devaddr, seqno, /*dn*/1, d+poff, pend-poff); + + EV(dfinfo, DEBUG, (e_.deveui = MAIN::CDEV->getEui(), + e_.devaddr = LMIC.devaddr, + e_.seqno = seqno, + e_.flags = (port < 0 ? EV::dfinfo_t::NOPORT : 0) | EV::dfinfo_t::DN, + e_.mic = Base::lsbf4(&d[pend]), + e_.hdr = d[LORA::OFF_DAT_HDR], + e_.fct = d[LORA::OFF_DAT_FCT], + e_.port = port, + e_.plen = dlen, + e_.opts.length = olen, + memcpy(&e_.opts[0], opts, olen))); + } else { + EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_REPLAY, + e_.eui = MAIN::CDEV->getEui(), + e_.info = Base::lsbf4(&d[pend]), + e_.info2 = seqno)); + } + + if( // NWK acks but we don't have a frame pending + (ackup && LMIC.txCnt == 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.txCnt != 0) ) { + EV(specCond, ERR, (e_.reason = EV::specCond_t::SPURIOUS_ACK, + e_.eui = MAIN::CDEV->getEui(), + e_.info = seqno, + e_.info2 = ackup)); + } + + if( LMIC.txCnt != 0 ) // we requested an ACK + LMIC.txrxFlags |= ackup ? TXRX_ACK : TXRX_NACK; + + 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; + } +#if LMIC_DEBUG_LEVEL > 0 + lmic_printf("%lu: Received downlink, window=%s, port=%d, ack=%d\n", os_getTime(), window, port, ackup); +#endif + return 1; +} + + +// ================================================================================ +// TX/RX transaction support + + +static void setupRx2 (void) { + LMIC.txrxFlags = TXRX_DNW2; + LMIC.rps = dndr2rps(LMIC.dn2Dr); + LMIC.freq = LMIC.dn2Freq; + LMIC.dataLen = 0; + os_radio(RADIO_RX); +} + + +static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { + ostime_t hsym = dr2hsym(dr); + + LMIC.rxsyms = MINRX_SYMS; + + // If a clock error is specified, compensate for it by extending the + // receive window + if (LMIC.clockError != 0) { + // Calculate how much the clock will drift maximally after delay has + // passed. This indicates the amount of time we can be early + // _or_ late. + ostime_t drift = (int64_t)delay * LMIC.clockError / MAX_CLOCK_ERROR; + + // Increase the receive window by twice the maximum drift (to + // compensate for a slow or a fast clock). + // decrease the rxtime to compensate for. Note that hsym is a + // *half* symbol time, so the factor 2 is hidden. First check if + // this would overflow (which can happen if the drift is very + // high, or the symbol time is low at high datarates). + if ((255 - LMIC.rxsyms) * hsym < drift) + LMIC.rxsyms = 255; + else + LMIC.rxsyms += drift / hsym; + + } + + // Center the receive window on the center of the expected preamble + // (again note that hsym is half a sumbol time, so no /2 needed) + LMIC.rxtime = LMIC.txend + delay + PAMBL_SYMS * hsym - LMIC.rxsyms * hsym; + + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); +} + +static void setupRx1 (osjobcb_t func) { + LMIC.txrxFlags = 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 (ostime_t delay, osjobcb_t func) { +#if !defined(DISABLE_PING) + 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 // !DISABLE_PING + + // Change RX frequency / rps (US only) before we increment txChnl + setRx1Params(); + // LMIC.rxsyms carries the TX datarate (can be != LMIC.datarate [confirm retries etc.]) + // Setup receive - LMIC.rxtime is preloaded with 1.5 symbols offset to tune + // into the middle of the 8 symbols preamble. +#if defined(CFG_eu868) + if( /* TX datarate */LMIC.rxsyms == DR_FSK ) { + LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160); + LMIC.rxsyms = RXLEN_FSK; + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); + } + else +#endif + { + schedRx12(delay, func, LMIC.dndr); + } +} + + +// ======================================== Join frames + + +#if !defined(DISABLE_JOIN) +static void onJoinFailed (xref2osjob_t osjob) { + // 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(); + EV(devCond, DEBUG, (e_.reason = EV::devCond_t::NO_JACC, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.datarate|DR_PAGE, + e_.info2 = osticks2ms(delay))); + // Build next JOIN REQUEST with next engineUpdate call + // Optionally, report join failed. + // Both after a random/chosen amount of ticks. + os_setTimedCallback(&LMIC.osjob, os_getTime()+delay, + (delay&1) != 0 + ? FUNC_ADDR(onJoinFailed) // one JOIN iteration done and failed + : FUNC_ADDR(runEngineUpdate)); // next step to be delayed + return 1; + } + u1_t hdr = LMIC.frame[0]; + u1_t dlen = LMIC.dataLen; + os_rlsbf4(&LMIC.frame[dlen-4]); // safe before modified by encrypt! + if( (dlen != LEN_JA && dlen != LEN_JAEXT) + || (hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) { + EV(specCond, ERR, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME, + e_.eui = MAIN::CDEV->getEui(), + e_.info = dlen < 4 ? 0 : mic, + e_.info2 = hdr + (dlen<<8))); + badframe: + if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) + return 0; + goto nojoinframe; + } + aes_encrypt(LMIC.frame+1, dlen-1); + if( !aes_verifyMic0(LMIC.frame, dlen-4) ) { + EV(specCond, ERR, (e_.reason = EV::specCond_t::JOIN_BAD_MIC, + e_.info = mic)); + goto badframe; + } + + u4_t addr = os_rlsbf4(LMIC.frame+OFF_JA_DEVADDR); + LMIC.devaddr = addr; + LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_JA_NETID]) & 0xFFFFFF; + +#if defined(CFG_eu868) + initDefaultChannels(0); +#endif + if( dlen > LEN_JA ) { +#if defined(CFG_us915) + goto badframe; +#endif + dlen = OFF_CFLIST; + for( u1_t chidx=3; chidx<8; chidx++, dlen+=3 ) { + u4_t freq = convFreq(&LMIC.frame[dlen]); + if( freq ) { + LMIC_setupChannel(chidx, freq, 0, -1); +#if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Setup channel, idx=%d, freq=%lu\n", os_getTime(), chidx, (unsigned long)freq); +#endif + } + } + } + + // already incremented when JOIN REQ got sent off + aes_sessKeys(LMIC.devNonce-1, &LMIC.frame[OFF_JA_ARTNONCE], LMIC.nwkKey, LMIC.artKey); + DO_DEVDB(LMIC.netid, netid); + DO_DEVDB(LMIC.devaddr, devaddr); + DO_DEVDB(LMIC.nwkKey, nwkkey); + DO_DEVDB(LMIC.artKey, artkey); + + EV(joininfo, INFO, (e_.arteui = MAIN::CDEV->getArtEui(), + e_.deveui = MAIN::CDEV->getEui(), + e_.devaddr = LMIC.devaddr, + e_.oldaddr = oldaddr, + e_.nonce = LMIC.devNonce-1, + e_.mic = mic, + e_.reason = ((LMIC.opmode & OP_REJOIN) != 0 + ? EV::joininfo_t::REJOIN_ACCEPT + : EV::joininfo_t::ACCEPT))); + + 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); + } + LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL; + LMIC.txCnt = 0; + stateJustJoined(); + LMIC.dn2Dr = LMIC.frame[OFF_JA_DLSET] & 0x0F; + LMIC.rxDelay = LMIC.frame[OFF_JA_RXDLY]; + if (LMIC.rxDelay == 0) LMIC.rxDelay = 1; + reportEvent(EV_JOINED); + return 1; +} + + +static void processRx2Jacc (xref2osjob_t osjob) { + if( LMIC.dataLen == 0 ) + LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot + processJoinAccept(); +} + + +static void setupRx2Jacc (xref2osjob_t osjob) { + LMIC.osjob.func = FUNC_ADDR(processRx2Jacc); + setupRx2(); +} + + +static void processRx1Jacc (xref2osjob_t osjob) { + if( LMIC.dataLen == 0 || !processJoinAccept() ) + schedRx12(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc), LMIC.dn2Dr); +} + + +static void setupRx1Jacc (xref2osjob_t osjob) { + setupRx1(FUNC_ADDR(processRx1Jacc)); +} + + +static void jreqDone (xref2osjob_t osjob) { + txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc)); +} + +#endif // !DISABLE_JOIN + +// ======================================== Data frames + +// Fwd decl. +static bit_t processDnData(void); + +static void processRx2DnData (xref2osjob_t osjob) { + if( LMIC.dataLen == 0 ) { + LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot + // It could be that the gateway *is* sending a reply, but we + // just didn't pick it up. To avoid TX'ing again while the + // gateay is not listening anyway, delay the next transmission + // until DNW2_SAFETY_ZONE from now, and add up to 2 seconds of + // extra randomization. + txDelay(os_getTime() + DNW2_SAFETY_ZONE, 2); + } + processDnData(); +} + + +static void setupRx2DnData (xref2osjob_t osjob) { + LMIC.osjob.func = FUNC_ADDR(processRx2DnData); + setupRx2(); +} + + +static void processRx1DnData (xref2osjob_t osjob) { + if( LMIC.dataLen == 0 || !processDnData() ) + schedRx12(sec2osticks(LMIC.rxDelay +(int)DELAY_EXTDNW2), FUNC_ADDR(setupRx2DnData), LMIC.dn2Dr); +} + + +static void setupRx1DnData (xref2osjob_t osjob) { + setupRx1(FUNC_ADDR(processRx1DnData)); +} + + +static void updataDone (xref2osjob_t osjob) { + txDone(sec2osticks(LMIC.rxDelay), FUNC_ADDR(setupRx1DnData)); +} + +// ======================================== + + +static void buildDataFrame (void) { + bit_t txdata = ((LMIC.opmode & (OP_TXDATA|OP_POLL)) != OP_POLL); + u1_t dlen = txdata ? LMIC.pendTxLen : 0; + + // Piggyback MAC options + // Prioritize by importance + int end = OFF_DAT_OPTS; +#if !defined(DISABLE_PING) + if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE)) == (OP_TRACK|OP_PINGABLE) ) { + // Indicate pingability in every UP frame + LMIC.frame[end] = MCMD_PING_IND; + LMIC.frame[end+1] = LMIC.ping.dr | (LMIC.ping.intvExp<<4); + end += 2; + } +#endif // !DISABLE_PING +#if !defined(DISABLE_MCMD_DCAP_REQ) + if( LMIC.dutyCapAns ) { + LMIC.frame[end] = MCMD_DCAP_ANS; + end += 1; + LMIC.dutyCapAns = 0; + } +#endif // !DISABLE_MCMD_DCAP_REQ +#if !defined(DISABLE_MCMD_DN2P_SET) + if( LMIC.dn2Ans ) { + LMIC.frame[end+0] = MCMD_DN2P_ANS; + LMIC.frame[end+1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU; + end += 2; + LMIC.dn2Ans = 0; + } +#endif // !DISABLE_MCMD_DN2P_SET + if( LMIC.devsAns ) { // answer to device status + LMIC.frame[end+0] = MCMD_DEVS_ANS; + LMIC.frame[end+1] = os_getBattLevel(); + LMIC.frame[end+2] = LMIC.margin; + end += 3; + LMIC.devsAns = 0; + } + if( LMIC.ladrAns ) { // answer to ADR change + LMIC.frame[end+0] = MCMD_LADR_ANS; + LMIC.frame[end+1] = LMIC.ladrAns & ~MCMD_LADR_ANS_RFU; + end += 2; + LMIC.ladrAns = 0; + } +#if !defined(DISABLE_BEACONS) + if( LMIC.bcninfoTries > 0 ) { + LMIC.frame[end] = MCMD_BCNI_REQ; + end += 1; + } +#endif // !DISABLE_BEACONS + if( LMIC.adrChanged ) { + if( LMIC.adrAckReq < 0 ) + LMIC.adrAckReq = 0; + LMIC.adrChanged = 0; + } +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) + if( LMIC.pingSetAns != 0 ) { + LMIC.frame[end+0] = MCMD_PING_ANS; + LMIC.frame[end+1] = LMIC.pingSetAns & ~MCMD_PING_ANS_RFU; + end += 2; + LMIC.pingSetAns = 0; + } +#endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING +#if !defined(DISABLE_MCMD_SNCH_REQ) + if( LMIC.snchAns ) { + LMIC.frame[end+0] = MCMD_SNCH_ANS; + LMIC.frame[end+1] = LMIC.snchAns & ~MCMD_SNCH_ANS_RFU; + end += 2; + LMIC.snchAns = 0; + } +#endif // !DISABLE_MCMD_SNCH_REQ + ASSERT(end <= OFF_DAT_OPTS+16); + + u1_t flen = end + (txdata ? 5+dlen : 4); + if( flen > MAX_LEN_FRAME ) { + // Options and payload too big - delay payload + txdata = 0; + flen = end+4; + } + LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DAUP | HDR_MAJOR_V1; + LMIC.frame[OFF_DAT_FCT] = (LMIC.dnConf | LMIC.adrEnabled + | (LMIC.adrAckReq >= 0 ? FCT_ADRARQ : 0) + | (end-OFF_DAT_OPTS)); + os_wlsbf4(LMIC.frame+OFF_DAT_ADDR, LMIC.devaddr); + + if( LMIC.txCnt == 0 ) { + LMIC.seqnoUp += 1; + DO_DEVDB(LMIC.seqnoUp,seqnoUp); + } else { + EV(devCond, INFO, (e_.reason = EV::devCond_t::RE_TX, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.seqnoUp-1, + e_.info2 = ((LMIC.txCnt+1) | + (DRADJUST[LMIC.txCnt+1] << 8) | + ((LMIC.datarate|DR_PAGE)<<16)))); + } + 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; + if( LMIC.txCnt == 0 ) LMIC.txCnt = 1; + } + LMIC.frame[end] = LMIC.pendTxPort; + os_copyMem(LMIC.frame+end+1, LMIC.pendTxData, dlen); + aes_cipher(LMIC.pendTxPort==0 ? LMIC.nwkKey : LMIC.artKey, + LMIC.devaddr, LMIC.seqnoUp-1, + /*up*/0, LMIC.frame+end+1, dlen); + } + aes_appendMic(LMIC.nwkKey, LMIC.devaddr, LMIC.seqnoUp-1, /*up*/0, LMIC.frame, flen-4); + + EV(dfinfo, DEBUG, (e_.deveui = MAIN::CDEV->getEui(), + e_.devaddr = LMIC.devaddr, + e_.seqno = LMIC.seqnoUp-1, + e_.flags = (LMIC.pendTxPort < 0 ? EV::dfinfo_t::NOPORT : EV::dfinfo_t::NOP), + e_.mic = Base::lsbf4(&LMIC.frame[flen-4]), + e_.hdr = LMIC.frame[LORA::OFF_DAT_HDR], + e_.fct = LMIC.frame[LORA::OFF_DAT_FCT], + e_.port = LMIC.pendTxPort, + e_.plen = txdata ? dlen : 0, + e_.opts.length = end-LORA::OFF_DAT_OPTS, + memcpy(&e_.opts[0], LMIC.frame+LORA::OFF_DAT_OPTS, end-LORA::OFF_DAT_OPTS))); + LMIC.dataLen = flen; +} + + +#if !defined(DISABLE_BEACONS) +// Callback from HAL during scan mode or when job timer expires. +static void onBcnRx (xref2osjob_t job) { + // If we arrive via job timer make sure to put radio to rest. + os_radio(RADIO_RST); + os_clearCallback(&LMIC.osjob); + if( LMIC.dataLen == 0 ) { + // Nothing received - timeout + LMIC.opmode &= ~(OP_SCAN | OP_TRACK); + reportEvent(EV_SCAN_TIMEOUT); + return; + } + if( decodeBeacon() <= 0 ) { + // Something is wrong with the beacon - continue scan + LMIC.dataLen = 0; + os_radio(RADIO_RXON); + os_setTimedCallback(&LMIC.osjob, LMIC.bcninfo.txtime, FUNC_ADDR(onBcnRx)); + return; + } + // Found our 1st beacon + // We don't have a previous beacon to calc some drift - assume + // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm + calcBcnRxWindowFromMillis(13,1); + LMIC.opmode &= ~OP_SCAN; // turn SCAN off + LMIC.opmode |= OP_TRACK; // auto enable tracking + reportEvent(EV_BEACON_FOUND); // can be disabled in callback +} + + +// Enable receiver to listen to incoming beacons +// netid defines when scan stops (any or specific beacon) +// This mode ends with events: EV_SCAN_TIMEOUT/EV_SCAN_BEACON +// Implicitely cancels any pending TX/RX transaction. +// Also cancels an onpoing joining procedure. +static void startScan (void) { + ASSERT(LMIC.devaddr!=0 && (LMIC.opmode & OP_JOINING)==0); + if( (LMIC.opmode & OP_SHUTDOWN) != 0 ) + return; + // Cancel onging TX/RX transaction + LMIC.txCnt = LMIC.dnConf = LMIC.bcninfo.flags = 0; + LMIC.opmode = (LMIC.opmode | OP_SCAN) & ~(OP_TXRXPEND); + setBcnRxParams(); + LMIC.rxtime = LMIC.bcninfo.txtime = os_getTime() + sec2osticks(BCN_INTV_sec+1); + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime, FUNC_ADDR(onBcnRx)); + os_radio(RADIO_RXON); +} + + +bit_t LMIC_enableTracking (u1_t tryBcnInfo) { + if( (LMIC.opmode & (OP_SCAN|OP_TRACK|OP_SHUTDOWN)) != 0 ) + return 0; // already in progress or failed to enable + // If BCN info requested from NWK then app has to take are + // of sending data up so that MCMD_BCNI_REQ can be attached. + if( (LMIC.bcninfoTries = tryBcnInfo) == 0 ) + startScan(); + return 1; // enabled +} + + +void LMIC_disableTracking (void) { + LMIC.opmode &= ~(OP_SCAN|OP_TRACK); + LMIC.bcninfoTries = 0; + engineUpdate(); +} +#endif // !DISABLE_BEACONS + + +// ================================================================================ +// +// Join stuff +// +// ================================================================================ + +#if !defined(DISABLE_JOIN) +static void buildJoinRequest (u1_t ftype) { + // Do not use pendTxData since we might have a pending + // user level frame in there. Use RX holding area instead. + xref2u1_t d = LMIC.frame; + d[OFF_JR_HDR] = ftype; + os_getArtEui(d + OFF_JR_ARTEUI); + os_getDevEui(d + OFF_JR_DEVEUI); + os_wlsbf2(d + OFF_JR_DEVNONCE, LMIC.devNonce); + aes_appendMic0(d, OFF_JR_MIC); + + EV(joininfo,INFO,(e_.deveui = MAIN::CDEV->getEui(), + e_.arteui = MAIN::CDEV->getArtEui(), + e_.nonce = LMIC.devNonce, + e_.oldaddr = LMIC.devaddr, + e_.mic = Base::lsbf4(&d[LORA::OFF_JR_MIC]), + e_.reason = ((LMIC.opmode & OP_REJOIN) != 0 + ? EV::joininfo_t::REJOIN_REQUEST + : EV::joininfo_t::REQUEST))); + LMIC.dataLen = LEN_JR; + LMIC.devNonce++; + DO_DEVDB(LMIC.devNonce,devNonce); +} + +static void startJoining (xref2osjob_t osjob) { + reportEvent(EV_JOINING); +} + +// Start join procedure if not already joined. +bit_t LMIC_startJoining (void) { + if( LMIC.devaddr == 0 ) { + // 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 = 0; + initJoinLoop(); + LMIC.opmode |= OP_JOINING; + // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS + os_setCallback(&LMIC.osjob, FUNC_ADDR(startJoining)); + return 1; + } + return 0; // already joined +} +#endif // !DISABLE_JOIN + + +// ================================================================================ +// +// +// +// ================================================================================ + +#if !defined(DISABLE_PING) +static void processPingRx (xref2osjob_t osjob) { + if( LMIC.dataLen != 0 ) { + LMIC.txrxFlags = TXRX_PING; + if( decodeFrame() ) { + reportEvent(EV_RXCOMPLETE); + return; + } + } + // Pick next ping slot + engineUpdate(); +} +#endif // !DISABLE_PING + + +static bit_t processDnData (void) { + ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); + + if( LMIC.dataLen == 0 ) { + norx: + if( LMIC.txCnt != 0 ) { + if( LMIC.txCnt < TXCONF_ATTEMPTS ) { + LMIC.txCnt += 1; + setDrTxpow(DRCHG_NOACK, lowerDR(LMIC.datarate, DRADJUST[LMIC.txCnt]), KEEP_TXPOW); + // Schedule another retransmission + txDelay(LMIC.rxtime, RETRY_PERIOD_secs); + LMIC.opmode &= ~OP_TXRXPEND; + engineUpdate(); + return 1; + } + LMIC.txrxFlags = TXRX_NACK | TXRX_NOPORT; + } else { + // Nothing received - implies no port + LMIC.txrxFlags = TXRX_NOPORT; + } + if( LMIC.adrAckReq != LINK_CHECK_OFF ) + LMIC.adrAckReq += 1; + LMIC.dataBeg = LMIC.dataLen = 0; + txcomplete: + LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND); + if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0 && (LMIC.opmode & OP_LINKDEAD) != 0 ) { + LMIC.opmode &= ~OP_LINKDEAD; + reportEvent(EV_LINK_ALIVE); + } + 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 > 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. Lower DR one notch. + EV(devCond, ERR, (e_.reason = EV::devCond_t::LINK_DEAD, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.adrAckReq)); + setDrTxpow(DRCHG_NOADRACK, decDR((dr_t)LMIC.datarate), KEEP_TXPOW); + LMIC.adrAckReq = LINK_CHECK_CONT; + LMIC.opmode |= OP_REJOIN|OP_LINKDEAD; + reportEvent(EV_LINK_DEAD); + } +#if !defined(DISABLE_BEACONS) + // If this falls to zero the NWK did not answer our MCMD_BCNI_REQ commands - try full scan + if( LMIC.bcninfoTries > 0 ) { + if( (LMIC.opmode & OP_TRACK) != 0 ) { + reportEvent(EV_BEACON_FOUND); + LMIC.bcninfoTries = 0; + } + else if( --LMIC.bcninfoTries == 0 ) { + startScan(); // NWK did not answer - try scan + } + } +#endif // !DISABLE_BEACONS + return 1; + } + if( !decodeFrame() ) { + if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) + return 0; + goto norx; + } + goto txcomplete; +} + + +#if !defined(DISABLE_BEACONS) +static void processBeacon (xref2osjob_t osjob) { + ostime_t lasttx = LMIC.bcninfo.txtime; // save here - decodeBeacon might overwrite + u1_t flags = LMIC.bcninfo.flags; + ev_t ev; + + if( LMIC.dataLen != 0 && 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 + // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm + calcBcnRxWindowFromMillis(13,0); + goto rev; + } + // We have a previous BEACON to calculate some drift + s2_t drift = BCN_INTV_osticks - (LMIC.bcninfo.txtime - lasttx); + if( LMIC.missedBcns > 0 ) { + drift = LMIC.drift + (drift - LMIC.drift) / (LMIC.missedBcns+1); + } + if( (LMIC.bcninfo.flags & BCN_NODRIFT) == 0 ) { + s2_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; + EV(devCond,INFO,(e_.reason = EV::devCond_t::CLOCK_DRIFT, + e_.eui = MAIN::CDEV->getEui(), + e_.info = drift, + e_.info2 = /*occasion BEACON*/0)); + 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,DR_BCN); + LMIC.bcnRxsyms = LMIC.rxsyms; + rev: +#if CFG_us915 + LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; +#endif +#if !defined(DISABLE_PING) + if( (LMIC.opmode & OP_PINGINI) != 0 ) + rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! +#endif // !DISABLE_PING + reportEvent(ev); +} + + +static void startRxBcn (xref2osjob_t osjob) { + LMIC.osjob.func = FUNC_ADDR(processBeacon); + os_radio(RADIO_RX); +} +#endif // !DISABLE_BEACONS + + +#if !defined(DISABLE_PING) +static void startRxPing (xref2osjob_t osjob) { + LMIC.osjob.func = FUNC_ADDR(processPingRx); + os_radio(RADIO_RX); +} +#endif // !DISABLE_PING + + +// Decide what to do next for the MAC layer of a device +static void engineUpdate (void) { +#if LMIC_DEBUG_LEVEL > 0 + lmic_printf("%lu: engineUpdate, opmode=0x%x\n", os_getTime(), LMIC.opmode); +#endif + // Check for ongoing state: scan or TX/RX transaction + if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) + return; + +#if !defined(DISABLE_JOIN) + if( LMIC.devaddr == 0 && (LMIC.opmode & OP_JOINING) == 0 ) { + LMIC_startJoining(); + return; + } +#endif // !DISABLE_JOIN + + ostime_t now = os_getTime(); + ostime_t rxtime = 0; + ostime_t txbeg = 0; + +#if !defined(DISABLE_BEACONS) + if( (LMIC.opmode & OP_TRACK) != 0 ) { + // We are tracking a beacon + ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 ); + rxtime = LMIC.bcnRxtime - RX_RAMPUP; + } +#endif // !DISABLE_BEACONS + + if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) { + // 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 LMIC_DEBUG_LEVEL > 1 + if (jacc) + lmic_printf("%lu: Uplink join pending\n", os_getTime()); + else + lmic_printf("%lu: Uplink data pending\n", os_getTime()); + #endif + // Find next suitable channel and return availability time + if( (LMIC.opmode & OP_NEXTCHNL) != 0 ) { + txbeg = LMIC.txend = nextTx(now); + LMIC.opmode &= ~OP_NEXTCHNL; + #if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Airtime available at %lu (channel duty limit)\n", os_getTime(), txbeg); + #endif + } else { + txbeg = LMIC.txend; + #if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Airtime available at %lu (previously determined)\n", os_getTime(), txbeg); + #endif + } + // Delayed TX or waiting for duty cycle? + if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0) && (txbeg - LMIC.globalDutyAvail) < 0 ) { + txbeg = LMIC.globalDutyAvail; + #if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Airtime available at %lu (global duty limit)\n", os_getTime(), txbeg); + #endif + } +#if !defined(DISABLE_BEACONS) + // 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 ) { + + #if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Awaiting beacon before uplink\n", os_getTime()); + #endif + + // 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 // !DISABLE_BEACONS + // Earliest possible time vs overhead to setup radio + if( txbeg - (now + TX_RAMPUP) < 0 ) { + #if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Ready for uplink\n", os_getTime()); + #endif + // We could send right now! + txbeg = now; + dr_t txdr = (dr_t)LMIC.datarate; +#if !defined(DISABLE_JOIN) + if( jacc ) { + u1_t ftype; + if( (LMIC.opmode & OP_REJOIN) != 0 ) { + txdr = lowerDR(txdr, LMIC.rejoinCnt); + ftype = HDR_FTYPE_REJOIN; + } else { + ftype = HDR_FTYPE_JREQ; + } + buildJoinRequest(ftype); + LMIC.osjob.func = FUNC_ADDR(jreqDone); + } else +#endif // !DISABLE_JOIN + { + if( LMIC.seqnoDn >= 0xFFFFFF80 ) { + // Imminent roll over - proactively reset MAC + EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER, + e_.eui = MAIN::CDEV->getEui(), + e_.info = LMIC.seqnoDn, + e_.info2 = 0)); + // Device has to react! NWK will not roll over and just stop sending. + // Thus, we have N frames to detect a possible lock up. + reset: + os_setCallback(&LMIC.osjob, FUNC_ADDR(runReset)); + return; + } + if( (LMIC.txCnt==0 && LMIC.seqnoUp == 0xFFFFFFFF) ) { + // Roll over of up seq counter + EV(specCond, ERR, (e_.reason = EV::specCond_t::UPSEQNO_ROLL_OVER, + e_.eui = MAIN::CDEV->getEui(), + e_.info2 = LMIC.seqnoUp)); + // Do not run RESET event callback from here! + // App code might do some stuff after send unaware of RESET. + goto reset; + } + buildDataFrame(); + LMIC.osjob.func = FUNC_ADDR(updataDone); + } + LMIC.rps = setCr(updr2rps(txdr), (cr_t)LMIC.errcr); + LMIC.dndr = txdr; // carry TX datarate (can be != LMIC.datarate) over to txDone/setupRx1 + LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL; + updateTx(txbeg); + os_radio(RADIO_TX); + return; + } + #if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Uplink delayed until %lu\n", os_getTime(), txbeg); + #endif + // 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 ) + return; + } + +#if !defined(DISABLE_BEACONS) + // Are we pingable? + checkrx: +#if !defined(DISABLE_PING) + 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; + 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 + } +#endif // !DISABLE_PING + + 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 // !DISABLE_BEACONS + + txdelay: + EV(devCond, INFO, (e_.reason = EV::devCond_t::TX_DELAY, + e_.eui = MAIN::CDEV->getEui(), + e_.info = osticks2ms(txbeg-now), + e_.info2 = LMIC.seqnoUp-1)); + 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, s1_t txpow) { + setDrTxpow(DRCHG_SET, dr, txpow); +} + + +void LMIC_shutdown (void) { + os_clearCallback(&LMIC.osjob); + os_radio(RADIO_RST); + LMIC.opmode |= OP_SHUTDOWN; +} + + +void LMIC_reset (void) { + EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV, + e_.eui = MAIN::CDEV->getEui(), + e_.info = EV_RESET)); + os_radio(RADIO_RST); + os_clearCallback(&LMIC.osjob); + + os_clearMem((xref2u1_t)&LMIC,SIZEOFEXPR(LMIC)); + LMIC.devaddr = 0; + LMIC.devNonce = os_getRndU2(); + LMIC.opmode = OP_NONE; + LMIC.errcr = CR_4_5; + LMIC.adrEnabled = FCT_ADREN; + LMIC.dn2Dr = DR_DNW2; // we need this for 2nd DN window of join accept + LMIC.dn2Freq = FREQ_DNW2; // ditto + LMIC.rxDelay = DELAY_DNW1; +#if !defined(DISABLE_PING) + LMIC.ping.freq = FREQ_PING; // defaults for ping + LMIC.ping.dr = DR_PING; // ditto + LMIC.ping.intvExp = 0xFF; +#endif // !DISABLE_PING +#if defined(CFG_us915) + initDefaultChannels(); +#endif + DO_DEVDB(LMIC.devaddr, devaddr); + DO_DEVDB(LMIC.devNonce, devNonce); + DO_DEVDB(LMIC.dn2Dr, dn2Dr); + DO_DEVDB(LMIC.dn2Freq, dn2Freq); +#if !defined(DISABLE_PING) + DO_DEVDB(LMIC.ping.freq, pingFreq); + DO_DEVDB(LMIC.ping.dr, pingDr); + DO_DEVDB(LMIC.ping.intvExp, pingIntvExp); +#endif // !DISABLE_PING +} + + +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 + return; + os_clearCallback(&LMIC.osjob); + os_radio(RADIO_RST); + engineUpdate(); +} + + +void LMIC_setTxData (void) { + LMIC.opmode |= OP_TXDATA; + if( (LMIC.opmode & OP_JOINING) == 0 ) + LMIC.txCnt = 0; // cancel any ongoing TX/RX retries + engineUpdate(); +} + + +// +int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { + if( dlen > SIZEOFEXPR(LMIC.pendTxData) ) + return -2; + if( data != (xref2u1_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) { + LMIC.opmode |= OP_POLL; + engineUpdate(); +} + + +// Check if other networks are around. +void LMIC_tryRejoin (void) { + LMIC.opmode |= OP_REJOIN; + 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. +//! \param artKey the 16 byte application router session key used for message confidentiality. +//! If NULL the caller has copied the key into `LMIC.artKey` before. +void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey) { + LMIC.netid = netid; + LMIC.devaddr = devaddr; + if( nwkKey != (xref2u1_t)0 ) + os_copyMem(LMIC.nwkKey, nwkKey, 16); + if( artKey != (xref2u1_t)0 ) + os_copyMem(LMIC.artKey, artKey, 16); + +#if defined(CFG_eu868) + initDefaultChannels(0); +#endif + + LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI); + LMIC.opmode |= OP_NEXTCHNL; + stateJustJoined(); + DO_DEVDB(LMIC.netid, netid); + DO_DEVDB(LMIC.devaddr, devaddr); + DO_DEVDB(LMIC.nwkKey, nwkkey); + DO_DEVDB(LMIC.artKey, artkey); + DO_DEVDB(LMIC.seqnoUp, seqnoUp); + DO_DEVDB(LMIC.seqnoDn, seqnoDn); +} + +// 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.adrChanged = 0; + LMIC.adrAckReq = enabled ? LINK_CHECK_INIT : LINK_CHECK_OFF; +} + +// Sets the max clock error to compensate for (defaults to 0, which +// allows for +/- 640 at SF7BW250). MAX_CLOCK_ERROR represents +/-100%, +// so e.g. for a +/-1% error you would pass MAX_CLOCK_ERROR * 1 / 100. +void LMIC_setClockError(u2_t error) { + LMIC.clockError = error; +} diff --git a/src/lmic.h b/src/lmic.h new file mode 100755 index 0000000..67a54d6 --- /dev/null +++ b/src/lmic.h @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2014-2016 IBM Corporation. + * All rights reserved. + * + * 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 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 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. + */ + +//! @file +//! @brief LMIC API + +#ifndef _lmic_h_ +#define _lmic_h_ + +#include "config.h" +#include "oslmic.h" +#include "lorabase.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// LMIC version +#define LMIC_VERSION_MAJOR 1 +#define LMIC_VERSION_MINOR 6 +#define LMIC_VERSION_BUILD 1468577746 + +enum { MAX_FRAME_LEN = 64 }; //!< Library cap on max frame length +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 + +enum { LINK_CHECK_CONT = 12 , // continue with this after reported dead link + LINK_CHECK_DEAD = 24 , // after this UP frames and no response from NWK assume link is dead + LINK_CHECK_INIT = -12 , // UP frame count until we inc datarate + LINK_CHECK_OFF =-128 }; // link check disabled + +enum { TIME_RESYNC = 6*128 }; // secs +enum { TXRX_GUARD_ms = 6000 }; // msecs - don't start TX-RX transaction before beacon +enum { JOIN_GUARD_ms = 9000 }; // msecs - don't start Join Req/Acc transaction before beacon +enum { TXRX_BCNEXT_secs = 2 }; // secs - earliest start after beacon time +enum { RETRY_PERIOD_secs = 3 }; // secs - random period for retrying a confirmed send + +#if defined(CFG_eu868) // EU868 spectrum ==================================================== + +enum { MAX_CHANNELS = 16 }; //!< Max supported channels +enum { MAX_BANDS = 4 }; + +enum { LIMIT_CHANNELS = (1<<4) }; // EU868 will never have more channels +//! \internal +struct band_t { + u2_t txcap; // duty cycle limitation: 1/txcap + s1_t txpow; // maximum TX power + u1_t lastchnl; // last used channel + ostime_t avail; // channel is blocked until this time +}; +TYPEDEF_xref2band_t; //!< \internal + +#elif defined(CFG_us915) // US915 spectrum ================================================= + +enum { MAX_XCHANNELS = 2 }; // extra channels in RAM, channels 0-71 are immutable +enum { MAX_TXPOW_125kHz = 30 }; + +#endif // ========================================================================== + +// Keep in sync with evdefs.hpp::drChange +enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD }; +enum { KEEP_TXPOW = -128 }; + + +#if !defined(DISABLE_PING) +//! \internal +struct rxsched_t { + u1_t dr; + u1_t intvExp; // 0..7 + u1_t slot; // runs from 0 to 128 + u1_t rxsyms; + ostime_t rxbase; + ostime_t rxtime; // start of next spot + u4_t freq; +}; +TYPEDEF_xref2rxsched_t; //!< \internal +#endif // !DISABLE_PING + + +#if !defined(DISABLE_BEACONS) +//! Parsing and tracking states of beacons. +enum { BCN_NONE = 0x00, //!< No beacon received + BCN_PARTIAL = 0x01, //!< Only first (common) part could be decoded (info,lat,lon invalid/previous) + BCN_FULL = 0x02, //!< Full beacon decoded + BCN_NODRIFT = 0x04, //!< No drift value measured yet + BCN_NODDIFF = 0x08 }; //!< No differential drift measured yet +//! Information about the last and previous beacons. +struct bcninfo_t { + ostime_t txtime; //!< Time when the beacon was sent + s1_t rssi; //!< Adjusted RSSI value of last received beacon + s1_t snr; //!< Scaled SNR value of last received beacon + u1_t flags; //!< Last beacon reception and tracking states. See BCN_* values. + u4_t time; //!< GPS time in seconds of last beacon (received or surrogate) + // + u1_t info; //!< Info field of last beacon (valid only if BCN_FULL set) + s4_t lat; //!< Lat field of last beacon (valid only if BCN_FULL set) + s4_t lon; //!< Lon field of last beacon (valid only if BCN_FULL set) +}; +#endif // !DISABLE_BEACONS + +// purpose of receive window - lmic_t.rxState +enum { RADIO_RST=0, RADIO_TX=1, RADIO_RX=2, RADIO_RXON=3 }; +// Netid values / lmic_t.netid +enum { NETID_NONE=(int)~0U, NETID_MASK=(int)0xFFFFFF }; +// MAC operation modes (lmic_t.opmode). +enum { OP_NONE = 0x0000, + OP_SCAN = 0x0001, // radio scan to find a beacon + OP_TRACK = 0x0002, // track my networks beacon (netid) + OP_JOINING = 0x0004, // device joining in progress (blocks other activities) + OP_TXDATA = 0x0008, // TX user data (buffered in pendTxData) + OP_POLL = 0x0010, // send empty UP frame to ACK confirmed DN/fetch more DN data + OP_REJOIN = 0x0020, // occasionally send JOIN REQUEST + OP_SHUTDOWN = 0x0040, // prevent MAC from doing anything + OP_TXRXPEND = 0x0080, // TX/RX transaction pending + OP_RNDTX = 0x0100, // prevent TX lining up after a beacon + OP_PINGINI = 0x0200, // pingable is initialized and scheduling active + OP_PINGABLE = 0x0400, // we're pingable + OP_NEXTCHNL = 0x0800, // find a new channel + OP_LINKDEAD = 0x1000, // link was reported as dead + OP_TESTMODE = 0x2000, // developer test mode +}; +// TX-RX transaction flags - report back to user +enum { TXRX_ACK = 0x80, // confirmed UP frame was acked + TXRX_NACK = 0x40, // confirmed UP frame was not acked + TXRX_NOPORT = 0x20, // set if a frame with a port was RXed, clr if no frame/no port + TXRX_PORT = 0x10, // set if a frame with a port was RXed, LMIC.frame[LMIC.dataBeg-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 +// 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 }; +typedef enum _ev_t ev_t; + +enum { + // This value represents 100% error in LMIC.clockError + MAX_CLOCK_ERROR = 65536, +}; + +struct lmic_t { + // Radio settings TX/RX (also accessed by HAL) + ostime_t txend; + ostime_t rxtime; + u4_t freq; + s1_t rssi; + s1_t snr; + rps_t rps; + u1_t rxsyms; + u1_t dndr; + s1_t txpow; // dBm + + osjob_t osjob; + + // Channel scheduling +#if defined(CFG_eu868) + band_t bands[MAX_BANDS]; + u4_t channelFreq[MAX_CHANNELS]; + u2_t channelDrMap[MAX_CHANNELS]; + u2_t channelMap; +#elif defined(CFG_us915) + u4_t xchFreq[MAX_XCHANNELS]; // extra channel frequencies (if device is behind a repeater) + u2_t xchDrMap[MAX_XCHANNELS]; // extra channel datarate ranges ---XXX: ditto + u2_t channelMap[(72+MAX_XCHANNELS+15)/16]; // enabled bits + u2_t chRnd; // channel randomizer +#endif + u1_t txChnl; // channel for next TX + u1_t globalDutyRate; // max rate: 1/2^k + ostime_t globalDutyAvail; // time device can send again + + u4_t netid; // current network id (~0 - none) + u2_t opmode; + u1_t upRepeat; // configured up repeat + s1_t adrTxPow; // ADR adjusted TX power + u1_t datarate; // current data rate + u1_t errcr; // error coding rate (used for TX only) + u1_t rejoinCnt; // adjustment for rejoin datarate +#if !defined(DISABLE_BEACONS) + s2_t drift; // last measured drift + s2_t lastDriftDiff; + s2_t maxDriftDiff; +#endif + + u2_t clockError; // Inaccuracy in the clock. CLOCK_ERROR_MAX + // represents +/-100% error + + u1_t pendTxPort; + u1_t pendTxConf; // confirmed data + u1_t pendTxLen; // +0x80 = confirmed + u1_t pendTxData[MAX_LEN_PAYLOAD]; + + u2_t devNonce; // last generated nonce + u1_t nwkKey[16]; // network session key + u1_t artKey[16]; // application router session key + devaddr_t devaddr; + u4_t seqnoDn; // device level down stream seqno + u4_t seqnoUp; + + u1_t dnConf; // dn frame confirm pending: LORA::FCT_ACK or 0 + s1_t adrAckReq; // counter until we reset data rate (0=off) + u1_t adrChanged; + + u1_t rxDelay; // Rx delay after TX + + u1_t margin; + bit_t ladrAns; // link adr adapt answer pending + bit_t devsAns; // device status answer pending + u1_t adrEnabled; + u1_t moreData; // NWK has more data pending +#if !defined(DISABLE_MCMD_DCAP_REQ) + bit_t dutyCapAns; // have to ACK duty cycle settings +#endif +#if !defined(DISABLE_MCMD_SNCH_REQ) + u1_t snchAns; // answer set new channel +#endif + // 2nd RX window (after up stream) + u1_t dn2Dr; + u4_t dn2Freq; +#if !defined(DISABLE_MCMD_DN2P_SET) + u1_t dn2Ans; // 0=no answer pend, 0x80+ACKs +#endif + + // Class B state +#if !defined(DISABLE_BEACONS) + u1_t missedBcns; // unable to track last N beacons + u1_t bcninfoTries; // how often to try (scan mode only) +#endif +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) + u1_t pingSetAns; // answer set cmd and ACK bits +#endif +#if !defined(DISABLE_PING) + rxsched_t ping; // pingable setup +#endif + + // Public part of MAC state + u1_t txCnt; + u1_t txrxFlags; // transaction flags (TX-RX combo) + u1_t dataBeg; // 0 or start of data (dataBeg-1 is port) + u1_t dataLen; // 0 no data or zero length data, >0 byte count of data + u1_t frame[MAX_LEN_FRAME]; + +#if !defined(DISABLE_BEACONS) + u1_t bcnChnl; + u1_t bcnRxsyms; // + ostime_t bcnRxtime; + bcninfo_t bcninfo; // Last received beacon info +#endif + + u1_t noRXIQinversion; +}; +//! \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) (((u2_t)0xFFFF<<(drlo)) & ((u2_t)0xFFFF>>(15-(drhi)))) +#if defined(CFG_eu868) +enum { BAND_MILLI=0, BAND_CENTI=1, BAND_DECI=2, BAND_AUX=3 }; +bit_t LMIC_setupBand (u1_t bandidx, s1_t txpow, u2_t txcap); +#endif +bit_t LMIC_setupChannel (u1_t channel, u4_t freq, u2_t drmap, s1_t band); +void LMIC_disableChannel (u1_t channel); +#if defined(CFG_us915) +void LMIC_enableChannel (u1_t channel); +void LMIC_enableSubBand (u1_t band); +void LMIC_disableSubBand (u1_t band); +void LMIC_selectSubBand (u1_t band); +#endif + +void LMIC_setDrTxpow (dr_t dr, s1_t txpow); // set default/start DR/txpow +void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off) +#if !defined(DISABLE_JOIN) +bit_t LMIC_startJoining (void); +#endif + +void LMIC_shutdown (void); +void LMIC_init (void); +void LMIC_start (void); +void LMIC_reset (void); +void LMIC_clrTxData (void); +void LMIC_setTxData (void); +int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed); +void LMIC_sendAlive (void); + +#if !defined(DISABLE_BEACONS) +bit_t LMIC_enableTracking (u1_t tryBcnInfo); +void LMIC_disableTracking (void); +#endif + +#if !defined(DISABLE_PING) +void LMIC_stopPingable (void); +void LMIC_setPingable (u1_t intvExp); +#endif +#if !defined(DISABLE_JOIN) +void LMIC_tryRejoin (void); +#endif + +void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey); +void LMIC_setLinkCheckMode (bit_t enabled); +void LMIC_setClockError(u2_t error); + +// 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!!! + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _lmic_h_ diff --git a/src/lorabase.h b/src/lorabase.h new file mode 100755 index 0000000..1596dfd --- /dev/null +++ b/src/lorabase.h @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2014-2016 IBM Corporation. + * All rights reserved. + * + * 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 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 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 _lorabase_h_ +#define _lorabase_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +// ================================================================================ +// BEG: Keep in sync with lorabase.hpp +// + +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 u1_t cr_t; +typedef u1_t sf_t; +typedef u1_t bw_t; +typedef u1_t dr_t; +// Radio parameter set (encodes SF/BW/CR/IH/NOCRC) +typedef u2_t rps_t; +TYPEDEF_xref2rps_t; + +enum { ILLEGAL_RPS = 0xFF }; +enum { DR_PAGE_EU868 = 0x00 }; +enum { DR_PAGE_US915 = 0x10 }; + +// Global maximum frame length +enum { STD_PREAMBLE_LEN = 8 }; +enum { MAX_LEN_FRAME = 64 }; +enum { LEN_DEVNONCE = 2 }; +enum { LEN_ARTNONCE = 3 }; +enum { LEN_NETID = 3 }; +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 rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc) { + return sf | (bw<<3) | (cr<<5) | (nocrc?(1<<7):0) | ((ih&0xFF)<<8); +} +#define MAKERPS(sf,bw,cr,ih,nocrc) ((rps_t)((sf) | ((bw)<<3) | ((cr)<<5) | ((nocrc)?(1<<7):0) | ((ih&0xFF)<<8))) +// Two frames with params r1/r2 would interfere on air: same SFx + BWx +inline int sameSfBw(rps_t r1, rps_t r2) { return ((r1^r2)&0x1F) == 0; } + +extern const u1_t _DR2RPS_CRC[]; +inline rps_t updr2rps (dr_t dr) { return (rps_t)_DR2RPS_CRC[dr+1]; } +inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); } +inline int isFasterDR (dr_t dr1, dr_t dr2) { return dr1 > dr2; } +inline int isSlowerDR (dr_t dr1, dr_t dr2) { return dr1 < dr2; } +inline dr_t incDR (dr_t dr) { return _DR2RPS_CRC[dr+2]==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate +inline dr_t decDR (dr_t dr) { return _DR2RPS_CRC[dr ]==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate +inline dr_t assertDR (dr_t dr) { return _DR2RPS_CRC[dr+1]==ILLEGAL_RPS ? (dr_t)DR_DFLTMIN : dr; } // force into a valid DR +inline bit_t validDR (dr_t dr) { return _DR2RPS_CRC[dr+1]!=ILLEGAL_RPS; } // in range +inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps + +// +// BEG: Keep in sync with lorabase.hpp +// ================================================================================ + + +// Convert between dBm values and power codes (MCMD_LADR_XdBm) +s1_t pow2dBm (u1_t mcmd_ladr_p1); +// Calculate airtime +ostime_t calcAirTime (rps_t rps, u1_t plen); +// Sensitivity at given SF/BW +int getSensitivity (rps_t rps); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _lorabase_h_ diff --git a/src/oslmic.c b/src/oslmic.c new file mode 100755 index 0000000..d9fcf72 --- /dev/null +++ b/src/oslmic.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2014-2016 IBM Corporation. + * All rights reserved. + * + * 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 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 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 "lmic.h" +#include "hal_esp32.h" +#include + +// RUNTIME STATE +static struct { + osjob_t* scheduledjobs; + osjob_t* runnablejobs; +} OS; + +void os_init () { + memset(&OS, 0x00, sizeof(OS)); + hal_init(); + radio_init(); + LMIC_init(); +} + +ostime_t os_getTime () { + return hal_ticks(); +} + +static u1_t unlinkjob (osjob_t** pnext, osjob_t* job) { + for( ; *pnext; pnext = &((*pnext)->next)) { + if(*pnext == job) { // unlink + *pnext = job->next; + return 1; + } + } + return 0; +} + +// clear scheduled job +void os_clearCallback (osjob_t* job) { + hal_disableIRQs(); + #if LMIC_DEBUG_LEVEL > 1 + u1_t res = + #endif + unlinkjob(&OS.scheduledjobs, job) || unlinkjob(&OS.runnablejobs, job); + hal_enableIRQs(); + #if LMIC_DEBUG_LEVEL > 1 + if (res) + lmic_printf("%lu: Cleared job %p\n", os_getTime(), job); + #endif +} + +// schedule immediately runnable job +void os_setCallback (osjob_t* job, osjobcb_t cb) { + osjob_t** pnext; + hal_disableIRQs(); + // remove if job was already queued + os_clearCallback(job); + // fill-in job + job->func = cb; + job->next = NULL; + // add to end of run queue + for(pnext=&OS.runnablejobs; *pnext; pnext=&((*pnext)->next)); + *pnext = job; + hal_enableIRQs(); + #if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Scheduled job %p, cb %p ASAP\n", os_getTime(), job, cb); + #endif +} + +// schedule timed job +void os_setTimedCallback (osjob_t* job, ostime_t time, osjobcb_t cb) { + osjob_t** pnext; + hal_disableIRQs(); + // remove if job was already queued + os_clearCallback(job); + // fill-in job + job->deadline = time; + job->func = cb; + job->next = NULL; + // 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(); + #if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Scheduled job %p, cb %p at %lu\n", os_getTime(), job, cb, time); + #endif +} + +// execute jobs from timer and from run queue +void os_runloop () { + while(1) { + os_runloop_once(); + } +} + +void os_runloop_once() { + #if LMIC_DEBUG_LEVEL > 1 + bool has_deadline = false; + #endif + + osjob_t* j = NULL; + hal_enterCriticalSection(); + // check for runnable jobs + if(OS.runnablejobs) { + j = OS.runnablejobs; + OS.runnablejobs = j->next; + } else if(OS.scheduledjobs && hal_checkTimer(OS.scheduledjobs->deadline)) { // check for expired timed jobs + j = OS.scheduledjobs; + OS.scheduledjobs = j->next; + #if LMIC_DEBUG_LEVEL > 1 + has_deadline = true; + #endif + } + if(j) { // run job callback + #if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: Running job %p, cb %p, deadline %lu\n", os_getTime(), j, j->func, has_deadline ? j->deadline : 0); + #endif + j->func(j); + hal_leaveCriticalSection(); + } else { // nothing pending + hal_leaveCriticalSection(); + hal_sleep(); // wake by irq (timer already restarted) + } +} diff --git a/src/oslmic.h b/src/oslmic.h new file mode 100755 index 0000000..ee150b6 --- /dev/null +++ b/src/oslmic.h @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2014-2016 IBM Corporation. + * All rights reserved. + * + * 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 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 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. + */ + +//! \file +#ifndef _oslmic_h_ +#define _oslmic_h_ + +// Dependencies required for the LoRa MAC in C to run. +// These settings can be adapted to the underlying system. +// You should not, however, change the lmic.[hc] + +#include "config.h" +#include +#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 unsigned long u4_t; +typedef long s4_t; +typedef unsigned int uint; +typedef const char* str_t; + +#include +#include "hal.h" +#define EV(a,b,c) /**/ +#define DO_DEVDB(field1,field2) /**/ +#if !defined(CFG_noassert) +#define ASSERT(cond) if(!(cond)) hal_failed(__FILE__, __LINE__) +#else +#define ASSERT(cond) /**/ +#endif + +#define os_clearMem(a,b) memset(a,0,b) +#define os_copyMem(a,b,c) memcpy(a,b,c) + +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; +#define TYPEDEF_xref2rps_t typedef rps_t* xref2rps_t +#define TYPEDEF_xref2rxsched_t typedef rxsched_t* xref2rxsched_t +#define TYPEDEF_xref2chnldef_t typedef chnldef_t* xref2chnldef_t +#define TYPEDEF_xref2band_t typedef band_t* xref2band_t +#define TYPEDEF_xref2osjob_t typedef osjob_t* xref2osjob_t + +#define SIZEOFEXPR(x) sizeof(x) + +#define ON_LMIC_EVENT(ev) onEvent(ev) +#define DECL_ON_LMIC_EVENT void onEvent(ev_t e) + +typedef s4_t ostime_t; + + +extern u4_t AESAUX[]; +extern u4_t AESKEY[]; +#define AESkey ((u1_t*)AESKEY) +#define AESaux ((u1_t*)AESAUX) +#define FUNC_ADDR(func) (&(func)) + +u1_t radio_rand1 (void); +#define os_getRndU1() radio_rand1() + +#define DEFINE_LMIC struct lmic_t LMIC +#define DECLARE_LMIC extern struct lmic_t LMIC + +void radio_init (void); +void radio_irq_handler (u1_t dio, ostime_t t); +void os_init (void); +void os_runloop (void); +void os_runloop_once(); + +//================================================================================ + + +#ifndef RX_RAMPUP +#define RX_RAMPUP (us2osticks(2000)) +#endif +#ifndef TX_RAMPUP +#define TX_RAMPUP (us2osticks(2000)) +#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 + +#if !HAS_ostick_conv +#define us2osticks(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC) / 1000000)) +#define ms2osticks(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC) / 1000)) +#define sec2osticks(sec) ((ostime_t)( (int64_t)(sec) * OSTICKS_PER_SEC)) +#define osticks2ms(os) ((s4_t)(((os)*(int64_t)1000 ) / OSTICKS_PER_SEC)) +#define osticks2us(os) ((s4_t)(((os)*(int64_t)1000000 ) / OSTICKS_PER_SEC)) +// Special versions +#define us2osticksCeil(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC + 999999) / 1000000)) +#define us2osticksRound(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC + 500000) / 1000000)) +#define ms2osticksCeil(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC + 999) / 1000)) +#define ms2osticksRound(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC + 500) / 1000)) +#endif + + +struct osjob_t; // fwd decl. +typedef void (*osjobcb_t) (struct osjob_t*); +struct osjob_t { + struct osjob_t* next; + ostime_t deadline; + osjobcb_t func; +}; +TYPEDEF_xref2osjob_t; + + +#ifndef HAS_os_calls + +#ifndef os_getDevKey +void os_getDevKey (xref2u1_t buf); +#endif +#ifndef os_getArtEui +void os_getArtEui (xref2u1_t buf); +#endif +#ifndef os_getDevEui +void os_getDevEui (xref2u1_t buf); +#endif +#ifndef os_setCallback +void os_setCallback (xref2osjob_t job, osjobcb_t cb); +#endif +#ifndef os_setTimedCallback +void os_setTimedCallback (xref2osjob_t job, ostime_t time, osjobcb_t cb); +#endif +#ifndef os_clearCallback +void os_clearCallback (xref2osjob_t job); +#endif +#ifndef os_getTime +ostime_t os_getTime (void); +#endif +#ifndef os_getTimeSecs +uint os_getTimeSecs (void); +#endif +#ifndef os_radio +void os_radio (u1_t mode); +#endif +#ifndef os_getBattLevel +u1_t os_getBattLevel (void); +#endif + +#ifndef os_rlsbf4 +//! Read 32-bit quantity from given pointer in little endian byte order. +u4_t os_rlsbf4 (xref2cu1_t buf); +#endif +#ifndef os_wlsbf4 +//! Write 32-bit quntity into buffer in little endian byte order. +void os_wlsbf4 (xref2u1_t buf, u4_t value); +#endif +#ifndef os_rmsbf4 +//! Read 32-bit quantity from given pointer in big endian byte order. +u4_t os_rmsbf4 (xref2cu1_t buf); +#endif +#ifndef os_wmsbf4 +//! Write 32-bit quntity into buffer in big endian byte order. +void os_wmsbf4 (xref2u1_t buf, u4_t value); +#endif +#ifndef os_rlsbf2 +//! Read 16-bit quantity from given pointer in little endian byte order. +u2_t os_rlsbf2 (xref2cu1_t buf); +#endif +#ifndef os_wlsbf2 +//! Write 16-bit quntity into buffer in little endian byte order. +void os_wlsbf2 (xref2u1_t buf, u2_t value); +#endif + +//! Get random number (default impl for u2_t). +#ifndef os_getRndU2 +#define os_getRndU2() ((u2_t)((os_getRndU1()<<8)|os_getRndU1())) +#endif +#ifndef os_crc16 +u2_t os_crc16 (xref2u1_t d, uint len); +#endif + +#endif // !HAS_os_calls + +#define lmic_printf printf + +// ====================================================================== +// AES support +// !!Keep in sync with lorabase.hpp!! + +#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 xref2u1_t AESkey; +extern xref2u1_t AESaux; +#endif +#ifndef os_aes +u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len); +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _oslmic_h_ diff --git a/src/radio.c b/src/radio.c new file mode 100755 index 0000000..a76967a --- /dev/null +++ b/src/radio.c @@ -0,0 +1,886 @@ +/* + * Copyright (c) 2014-2016 IBM Corporation. + * All rights reserved. + * + * 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 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 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 "lmic.h" + +// ---------------------------------------- +// 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 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 RegTcxo 0x58 // common +#define RegPaDac 0x5A // common +// #define RegPll 0x5C // common +// #define RegPllLowPn 0x5E // common +// #define RegFormerTemp 0x6C // common +// #define RegBitRateFrac 0x70 // common + +// ---------------------------------------- +// spread factors and mode for RegModemConfig2 +#define SX1272_MC2_FSK 0x00 +#define SX1272_MC2_SF7 0x70 +#define SX1272_MC2_SF8 0x80 +#define SX1272_MC2_SF9 0x90 +#define SX1272_MC2_SF10 0xA0 +#define SX1272_MC2_SF11 0xB0 +#define SX1272_MC2_SF12 0xC0 +// bandwidth for RegModemConfig1 +#define SX1272_MC1_BW_125 0x00 +#define SX1272_MC1_BW_250 0x40 +#define SX1272_MC1_BW_500 0x80 +// coding rate for RegModemConfig1 +#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 // mandated for SF11 and SF12 +// transmit power configuration for RegPaConfig +#define SX1272_PAC_PA_SELECT_PA_BOOST 0x80 +#define SX1272_PAC_PA_SELECT_RFIO_PIN 0x00 + + +// sx1276 RegModemConfig1 +#define SX1276_MC1_BW_125 0x70 +#define SX1276_MC1_BW_250 0x80 +#define SX1276_MC1_BW_500 0x90 +#define SX1276_MC1_CR_4_5 0x02 +#define SX1276_MC1_CR_4_6 0x04 +#define SX1276_MC1_CR_4_7 0x06 +#define SX1276_MC1_CR_4_8 0x08 + +#define SX1276_MC1_IMPLICIT_HEADER_MODE_ON 0x01 + +// sx1276 RegModemConfig2 +#define SX1276_MC2_RX_PAYLOAD_CRCON 0x04 + +// sx1276 RegModemConfig3 +#define SX1276_MC3_LOW_DATA_RATE_OPTIMIZE 0x08 +#define SX1276_MC3_AGCAUTO 0x04 + +// preamble for lora networks (nibbles swapped) +#define LORA_MAC_PREAMBLE 0x34 + +#define RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG1 0x0A +#ifdef CFG_sx1276_radio +#define RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG2 0x70 +#elif CFG_sx1272_radio +#define RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG2 0x74 +#endif + + + +// ---------------------------------------- +// Constants for radio registers +#define OPMODE_LORA 0x80 +#define OPMODE_MASK 0x07 +#define OPMODE_SLEEP 0x00 +#define OPMODE_STANDBY 0x01 +#define OPMODE_FSTX 0x02 +#define OPMODE_TX 0x03 +#define OPMODE_FSRX 0x04 +#define OPMODE_RX 0x05 +#define OPMODE_RX_SINGLE 0x06 +#define OPMODE_CAD 0x07 + +// ---------------------------------------- +// 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 + +#define IRQ_FSK1_MODEREADY_MASK 0x80 +#define IRQ_FSK1_RXREADY_MASK 0x40 +#define IRQ_FSK1_TXREADY_MASK 0x20 +#define IRQ_FSK1_PLLLOCK_MASK 0x10 +#define IRQ_FSK1_RSSI_MASK 0x08 +#define IRQ_FSK1_TIMEOUT_MASK 0x04 +#define IRQ_FSK1_PREAMBLEDETECT_MASK 0x02 +#define IRQ_FSK1_SYNCADDRESSMATCH_MASK 0x01 +#define IRQ_FSK2_FIFOFULL_MASK 0x80 +#define IRQ_FSK2_FIFOEMPTY_MASK 0x40 +#define IRQ_FSK2_FIFOLEVEL_MASK 0x20 +#define IRQ_FSK2_FIFOOVERRUN_MASK 0x10 +#define IRQ_FSK2_PACKETSENT_MASK 0x08 +#define IRQ_FSK2_PAYLOADREADY_MASK 0x04 +#define IRQ_FSK2_CRCOK_MASK 0x02 +#define IRQ_FSK2_LOWBAT_MASK 0x01 + +// ---------------------------------------- +// DIO function mappings D0D1D2D3 +#define MAP_DIO0_LORA_RXDONE 0x00 // 00------ +#define MAP_DIO0_LORA_TXDONE 0x40 // 01------ +#define MAP_DIO1_LORA_RXTOUT 0x00 // --00---- +#define MAP_DIO1_LORA_NOP 0x30 // --11---- +#define MAP_DIO2_LORA_NOP 0xC0 // ----11-- + +#define MAP_DIO0_FSK_READY 0x00 // 00------ (packet sent / payload ready) +#define MAP_DIO1_FSK_NOP 0x30 // --11---- +#define MAP_DIO2_FSK_TXNOP 0x04 // ----01-- +#define MAP_DIO2_FSK_TIMEOUT 0x08 // ----10-- + + +// FSK IMAGECAL defines +#define RF_IMAGECAL_AUTOIMAGECAL_MASK 0x7F +#define RF_IMAGECAL_AUTOIMAGECAL_ON 0x80 +#define RF_IMAGECAL_AUTOIMAGECAL_OFF 0x00 // Default + +#define RF_IMAGECAL_IMAGECAL_MASK 0xBF +#define RF_IMAGECAL_IMAGECAL_START 0x40 + +#define RF_IMAGECAL_IMAGECAL_RUNNING 0x20 +#define RF_IMAGECAL_IMAGECAL_DONE 0x00 // Default + + +// RADIO STATE +// (initialized by radio_init(), used by radio_rand1()) +static u1_t randbuf[16]; + + +#ifdef CFG_sx1276_radio +#define LNA_RX_GAIN (0x20|0x1) +#elif CFG_sx1272_radio +#define LNA_RX_GAIN (0x20|0x03) +#else +#error Missing CFG_sx1272_radio/CFG_sx1276_radio +#endif + + +static void writeReg (u1_t addr, u1_t data ) { + hal_spi_write(addr | 0x80, &data, 1); + /* + hal_pin_nss(0); + hal_spi(addr | 0x80); + hal_spi(data); + hal_pin_nss(1); + */ +} + +static u1_t readReg (u1_t addr) { + u1_t buf[1]; + hal_spi_read(addr & 0x7f, buf, 1); + return buf[0]; + /* + hal_pin_nss(0); + hal_spi(addr & 0x7F); + u1_t val = hal_spi(0x00); + hal_pin_nss(1); + return val; + */ +} + +static void writeBuf (u1_t addr, xref2u1_t buf, u1_t len) { + hal_spi_write(addr | 0x80, buf, len); + /* + hal_pin_nss(0); + hal_spi(addr | 0x80); + for (u1_t i=0; i>16)); + writeReg(RegFrfMid, (u1_t)(frf>> 8)); + writeReg(RegFrfLsb, (u1_t)(frf>> 0)); +} + + + +static void configPower () { +#ifdef CFG_sx1276_radio + // no boost used for now + s1_t pw = (s1_t)LMIC.txpow; + if(pw >= 17) { + pw = 15; + } else if(pw < 2) { + pw = 2; + } + // check board type for BOOST pin + writeReg(RegPaConfig, (u1_t)(0x80|(pw&0xf))); + writeReg(RegPaDac, readReg(RegPaDac)|0x4); + +#elif CFG_sx1272_radio + // set PA config (2-17 dBm using PA_BOOST) + s1_t pw = (s1_t)LMIC.txpow; + if(pw > 17) { + pw = 17; + } else if(pw < 2) { + pw = 2; + } + writeReg(RegPaConfig, (u1_t)(0x80|(pw-2))); +#else +#error Missing CFG_sx1272_radio/CFG_sx1276_radio +#endif /* CFG_sx1272_radio */ +} + +static void txfsk () { + // select FSK modem (from sleep mode) + writeReg(RegOpMode, 0x10); // FSK, BT=0.5 + ASSERT(readReg(RegOpMode) == 0x10); + // enter standby mode (required for FIFO loading)) + opmode(OPMODE_STANDBY); + // set bitrate + writeReg(FSKRegBitrateMsb, 0x02); // 50kbps + writeReg(FSKRegBitrateLsb, 0x80); + // set frequency deviation + writeReg(FSKRegFdevMsb, 0x01); // +/- 25kHz + writeReg(FSKRegFdevLsb, 0x99); + // frame and packet handler settings + writeReg(FSKRegPreambleMsb, 0x00); + writeReg(FSKRegPreambleLsb, 0x05); + writeReg(FSKRegSyncConfig, 0x12); + writeReg(FSKRegPacketConfig1, 0xD0); + writeReg(FSKRegPacketConfig2, 0x40); + writeReg(FSKRegSyncValue1, 0xC1); + writeReg(FSKRegSyncValue2, 0x94); + writeReg(FSKRegSyncValue3, 0xC1); + // configure frequency + configChannel(); + // configure output power + configPower(); + + // set the IRQ mapping DIO0=PacketSent DIO1=NOP DIO2=NOP + writeReg(RegDioMapping1, MAP_DIO0_FSK_READY|MAP_DIO1_FSK_NOP|MAP_DIO2_FSK_TXNOP); + + // initialize the payload size and address pointers + writeReg(FSKRegPayloadLength, LMIC.dataLen+1); // (insert length byte into payload)) + + // download length byte and buffer to the radio FIFO + writeReg(RegFifo, LMIC.dataLen); + writeBuf(RegFifo, LMIC.frame, LMIC.dataLen); + + // enable antenna switch for TX + hal_pin_rxtx(1); + + // now we actually start the transmission + opmode(OPMODE_TX); +} + +static void txlora () { + // select LoRa modem (from sleep mode) + //writeReg(RegOpMode, OPMODE_LORA); + opmodeLora(); + ASSERT((readReg(RegOpMode) & OPMODE_LORA) != 0); + + // enter standby mode (required for FIFO loading)) + opmode(OPMODE_STANDBY); + // configure LoRa modem (cfg1, cfg2) + configLoraModem(); + // configure frequency + configChannel(); + // configure output power + writeReg(RegPaRamp, (readReg(RegPaRamp) & 0xF0) | 0x08); // set PA ramp-up time 50 uSec + configPower(); + // set sync word + writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE); + + // set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP + writeReg(RegDioMapping1, MAP_DIO0_LORA_TXDONE|MAP_DIO1_LORA_NOP|MAP_DIO2_LORA_NOP); + // clear all radio IRQ flags + writeReg(LORARegIrqFlags, 0xFF); + // mask all IRQs but TxDone + writeReg(LORARegIrqFlagsMask, ~IRQ_LORA_TXDONE_MASK); + + // initialize the payload size and address pointers + writeReg(LORARegFifoTxBaseAddr, 0x00); + writeReg(LORARegFifoAddrPtr, 0x00); + writeReg(LORARegPayloadLength, LMIC.dataLen); + + // download buffer to the radio FIFO + writeBuf(RegFifo, LMIC.frame, LMIC.dataLen); + + // enable antenna switch for TX + hal_pin_rxtx(1); + + // now we actually start the transmission + opmode(OPMODE_TX); + +#if LMIC_DEBUG_LEVEL > 0 + u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 + u1_t bw = getBw(LMIC.rps); + u1_t cr = getCr(LMIC.rps); + lmic_printf("%lu: TXMODE, freq=%lu, len=%d, SF=%d, BW=%d, CR=4/%d, IH=%d\n", + os_getTime(), LMIC.freq, LMIC.dataLen, sf, + bw == BW125 ? 125 : (bw == BW250 ? 250 : 500), + cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)), + getIh(LMIC.rps) + ); +#endif +} + +// start transmitter (buf=LMIC.frame, len=LMIC.dataLen) +static void starttx () { + ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); + if(getSf(LMIC.rps) == FSK) { // 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. +} + +enum { RXMODE_SINGLE, RXMODE_SCAN, RXMODE_RSSI }; + +static const u1_t rxlorairqmask[] = { + [RXMODE_SINGLE] = IRQ_LORA_RXDONE_MASK|IRQ_LORA_RXTOUT_MASK, + [RXMODE_SCAN] = IRQ_LORA_RXDONE_MASK, + [RXMODE_RSSI] = 0x00, +}; + +// start LoRa receiver (time=LMIC.rxtime, timeout=LMIC.rxsyms, result=LMIC.frame[LMIC.dataLen]) +static void rxlora (u1_t rxmode) { + // select LoRa modem (from sleep mode) + opmodeLora(); + ASSERT((readReg(RegOpMode) & OPMODE_LORA) != 0); + // enter standby mode (warm up)) + opmode(OPMODE_STANDBY); + // don't use MAC settings at startup + if(rxmode == RXMODE_RSSI) { // use fixed settings for rssi scan + writeReg(LORARegModemConfig1, RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG1); + writeReg(LORARegModemConfig2, RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG2); + } else { // single or continuous rx mode + // configure LoRa modem (cfg1, cfg2) + configLoraModem(); + // configure frequency + configChannel(); + } + // set LNA gain + writeReg(RegLna, LNA_RX_GAIN); + // set max payload size + writeReg(LORARegPayloadMaxLength, 64); +#if !defined(DISABLE_INVERT_IQ_ON_RX) + // use inverted I/Q signal (prevent mote-to-mote communication) + + // XXX: use flag to switch on/off inversion + if (LMIC.noRXIQinversion) { + writeReg(LORARegInvertIQ, readReg(LORARegInvertIQ) & ~(1<<6)); + } else { + writeReg(LORARegInvertIQ, readReg(LORARegInvertIQ)|(1<<6)); + } +#endif + // set symbol timeout (for single rx) + writeReg(LORARegSymbTimeoutLsb, LMIC.rxsyms); + // set sync word + writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE); + + // configure DIO mapping DIO0=RxDone DIO1=RxTout DIO2=NOP + writeReg(RegDioMapping1, MAP_DIO0_LORA_RXDONE|MAP_DIO1_LORA_RXTOUT|MAP_DIO2_LORA_NOP); + // clear all radio IRQ flags + writeReg(LORARegIrqFlags, 0xFF); + // enable required radio IRQs + writeReg(LORARegIrqFlagsMask, ~rxlorairqmask[rxmode]); + + // enable antenna switch for RX + hal_pin_rxtx(0); + + // now instruct the radio to receive + if (rxmode == RXMODE_SINGLE) { // single rx + hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time + opmode(OPMODE_RX_SINGLE); + } else { // continous rx (scan or rssi) + opmode(OPMODE_RX); + } + +#if LMIC_DEBUG_LEVEL > 0 + if (rxmode == RXMODE_RSSI) { + lmic_printf("RXMODE_RSSI\n"); + } else { + u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 + u1_t bw = getBw(LMIC.rps); + u1_t cr = getCr(LMIC.rps); + lmic_printf("%lu: %s, freq=%lu, SF=%d, BW=%d, CR=4/%d, IH=%d\n", + os_getTime(), + rxmode == RXMODE_SINGLE ? "RXMODE_SINGLE" : (rxmode == RXMODE_SCAN ? "RXMODE_SCAN" : "UNKNOWN_RX"), + LMIC.freq, sf, + bw == BW125 ? 125 : (bw == BW250 ? 250 : 500), + cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)), + getIh(LMIC.rps) + ); + } +#endif +} + +static void rxfsk (u1_t rxmode) { + // only single rx (no continuous scanning, no noise sampling) + ASSERT( rxmode == RXMODE_SINGLE ); + // select FSK modem (from sleep mode) + //writeReg(RegOpMode, 0x00); // (not LoRa) + opmodeFSK(); + ASSERT((readReg(RegOpMode) & OPMODE_LORA) == 0); + // enter standby mode (warm up)) + opmode(OPMODE_STANDBY); + // configure frequency + configChannel(); + // set LNA gain + //writeReg(RegLna, 0x20|0x03); // max gain, boost enable + writeReg(RegLna, LNA_RX_GAIN); + // configure receiver + writeReg(FSKRegRxConfig, 0x1E); // AFC auto, AGC, trigger on preamble?!? + // set receiver bandwidth + writeReg(FSKRegRxBw, 0x0B); // 50kHz SSb + // set AFC bandwidth + writeReg(FSKRegAfcBw, 0x12); // 83.3kHz SSB + // set preamble detection + writeReg(FSKRegPreambleDetect, 0xAA); // enable, 2 bytes, 10 chip errors + // set sync config + writeReg(FSKRegSyncConfig, 0x12); // no auto restart, preamble 0xAA, enable, fill FIFO, 3 bytes sync + // set packet config + writeReg(FSKRegPacketConfig1, 0xD8); // var-length, whitening, crc, no auto-clear, no adr filter + writeReg(FSKRegPacketConfig2, 0x40); // packet mode + // set sync value + writeReg(FSKRegSyncValue1, 0xC1); + writeReg(FSKRegSyncValue2, 0x94); + writeReg(FSKRegSyncValue3, 0xC1); + // set preamble timeout + writeReg(FSKRegRxTimeout2, 0xFF);//(LMIC.rxsyms+1)/2); + // set bitrate + writeReg(FSKRegBitrateMsb, 0x02); // 50kbps + writeReg(FSKRegBitrateLsb, 0x80); + // set frequency deviation + writeReg(FSKRegFdevMsb, 0x01); // +/- 25kHz + writeReg(FSKRegFdevLsb, 0x99); + + // configure DIO mapping DIO0=PayloadReady DIO1=NOP DIO2=TimeOut + writeReg(RegDioMapping1, MAP_DIO0_FSK_READY|MAP_DIO1_FSK_NOP|MAP_DIO2_FSK_TIMEOUT); + + // enable antenna switch for RX + hal_pin_rxtx(0); + + // now instruct the radio to receive + hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time + opmode(OPMODE_RX); // no single rx mode available in FSK +} + +static void startrx (u1_t rxmode) { + ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); + if(getSf(LMIC.rps) == FSK) { // FSK modem + rxfsk(rxmode); + } else { // LoRa modem + rxlora(rxmode); + } + // the radio will go back to STANDBY mode as soon as the RX is finished + // or timed out, and the corresponding IRQ will inform us about completion. +} + +// get random seed from wideband noise rssi +void radio_init () { + hal_disableIRQs(); + + // manually reset radio +#ifdef CFG_sx1276_radio + hal_pin_rst(0); // drive RST pin low +#else + hal_pin_rst(1); // drive RST pin high +#endif + hal_waitUntil(os_getTime()+ms2osticks(1)); // wait >100us + hal_pin_rst(2); // configure RST pin floating! + hal_waitUntil(os_getTime()+ms2osticks(5)); // wait 5ms + + opmode(OPMODE_SLEEP); + + // some sanity checks, e.g., read version number + u1_t v = readReg(RegVersion); +#ifdef CFG_sx1276_radio + ASSERT(v == 0x12 ); +#elif CFG_sx1272_radio + ASSERT(v == 0x22); +#else +#error Missing CFG_sx1272_radio/CFG_sx1276_radio +#endif + // seed 15-byte randomness via noise rssi + rxlora(RXMODE_RSSI); + while( (readReg(RegOpMode) & OPMODE_MASK) != OPMODE_RX ); // continuous rx + for(int i=1; i<16; i++) { + for(int j=0; j<8; j++) { + u1_t b; // wait for two non-identical subsequent least-significant bits + while( (b = readReg(LORARegRssiWideband) & 0x01) == (readReg(LORARegRssiWideband) & 0x01) ); + randbuf[i] = (randbuf[i] << 1) | b; + } + } + randbuf[0] = 16; // set initial index + +#ifdef CFG_sx1276mb1_board + // chain calibration + writeReg(RegPaConfig, 0); + + // Launch Rx chain calibration for LF band + writeReg(FSKRegImageCal, (readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_MASK)|RF_IMAGECAL_IMAGECAL_START); + while((readReg(FSKRegImageCal)&RF_IMAGECAL_IMAGECAL_RUNNING) == RF_IMAGECAL_IMAGECAL_RUNNING){ ; } + + // Sets a Frequency in HF band + u4_t frf = 868000000; + writeReg(RegFrfMsb, (u1_t)(frf>>16)); + writeReg(RegFrfMid, (u1_t)(frf>> 8)); + writeReg(RegFrfLsb, (u1_t)(frf>> 0)); + + // Launch Rx chain calibration for HF band + writeReg(FSKRegImageCal, (readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_MASK)|RF_IMAGECAL_IMAGECAL_START); + while((readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_RUNNING) == RF_IMAGECAL_IMAGECAL_RUNNING) { ; } +#endif /* CFG_sx1276mb1_board */ + + opmode(OPMODE_SLEEP); + + hal_enableIRQs(); +} + +// return next random byte derived from seed buffer +// (buf[0] holds index of next byte to be returned) +u1_t radio_rand1 () { + u1_t i = randbuf[0]; + ASSERT( i != 0 ); + if( i==16 ) { + os_aes(AES_ENC, randbuf, 16); // encrypt seed with any key + i = 0; + } + u1_t v = randbuf[i++]; + randbuf[0] = i; + return v; +} + +u1_t radio_rssi () { + hal_disableIRQs(); + u1_t r = readReg(LORARegRssiValue); + hal_enableIRQs(); + return r; +} + +static const u2_t LORA_RXDONE_FIXUP[] = { + [FSK] = us2osticks(0), // ( 0 ticks) + [SF7] = us2osticks(0), // ( 0 ticks) + [SF8] = us2osticks(1648), // ( 54 ticks) + [SF9] = us2osticks(3265), // ( 107 ticks) + [SF10] = us2osticks(7049), // ( 231 ticks) + [SF11] = us2osticks(13641), // ( 447 ticks) + [SF12] = us2osticks(31189), // (1022 ticks) +}; + +// called by hal ext IRQ handler +// (radio goes to stanby mode after tx/rx operations) +void radio_irq_handler (u1_t dio, ostime_t t) { + if( (readReg(RegOpMode) & OPMODE_LORA) != 0) { // LORA modem + u1_t flags = readReg(LORARegIrqFlags); +#if LMIC_DEBUG_LEVEL > 1 + lmic_printf("%lu: irq: dio: 0x%x flags: 0x%x\n", t, dio, flags); +#endif + if( flags & IRQ_LORA_TXDONE_MASK ) { + // save exact tx time + LMIC.txend = t - us2osticks(43); // TXDONE FIXUP + } else if( flags & IRQ_LORA_RXDONE_MASK ) { + // save exact rx time + if(getBw(LMIC.rps) == BW125) { + t -= LORA_RXDONE_FIXUP[getSf(LMIC.rps)]; + } + LMIC.rxtime = t; + // read the PDU and inform the MAC that we received something + LMIC.dataLen = (readReg(LORARegModemConfig1) & SX1272_MC1_IMPLICIT_HEADER_MODE_ON) ? + readReg(LORARegPayloadLength) : readReg(LORARegRxNbBytes); + // set FIFO read address pointer + writeReg(LORARegFifoAddrPtr, readReg(LORARegFifoRxCurrentAddr)); + // now read the FIFO + readBuf(RegFifo, LMIC.frame, LMIC.dataLen); + // read rx quality parameters + LMIC.snr = readReg(LORARegPktSnrValue); // SNR [dB] * 4 + LMIC.rssi = readReg(LORARegPktRssiValue) - 125 + 64; // RSSI [dBm] (-196...+63) + } else if( flags & IRQ_LORA_RXTOUT_MASK ) { + // indicate timeout + LMIC.dataLen = 0; + } + // mask all radio IRQs + writeReg(LORARegIrqFlagsMask, 0xFF); + // clear radio IRQ flags + writeReg(LORARegIrqFlags, 0xFF); + } else { // FSK modem + u1_t flags1 = readReg(FSKRegIrqFlags1); + u1_t flags2 = readReg(FSKRegIrqFlags2); + if( flags2 & IRQ_FSK2_PACKETSENT_MASK ) { + // save exact tx time + LMIC.txend = t; + } else if( flags2 & IRQ_FSK2_PAYLOADREADY_MASK ) { + // save exact rx time + LMIC.rxtime = t; + // read the PDU and inform the MAC that we received something + LMIC.dataLen = readReg(FSKRegPayloadLength); + // now read the FIFO + readBuf(RegFifo, LMIC.frame, LMIC.dataLen); + // read rx quality parameters + LMIC.snr = 0; // determine snr + LMIC.rssi = 0; // determine rssi + } else if( flags1 & IRQ_FSK1_TIMEOUT_MASK ) { + // indicate timeout + LMIC.dataLen = 0; + } else { + ASSERT(0); + } + } + // go from stanby to sleep + opmode(OPMODE_SLEEP); + // run os job (use preset func ptr) + os_setCallback(&LMIC.osjob, LMIC.osjob.func); +} + +void os_radio (u1_t mode) { + hal_disableIRQs(); + switch (mode) { + case RADIO_RST: + // put radio to sleep + opmode(OPMODE_SLEEP); + break; + + case RADIO_TX: + // transmit frame now + starttx(); // buf=LMIC.frame, len=LMIC.dataLen + break; + + case RADIO_RX: + // receive frame now (exactly at rxtime) + startrx(RXMODE_SINGLE); // buf=LMIC.frame, time=LMIC.rxtime, timeout=LMIC.rxsyms + break; + + case RADIO_RXON: + // start scanning for beacon now + startrx(RXMODE_SCAN); // buf=LMIC.frame + break; + } + hal_enableIRQs(); +}