diff --git a/.vscode/settings.json b/.vscode/settings.json index 85be09c..5cb4ebd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,19 +1,11 @@ { "files.associations": { - "provisioning.h": "c", - "config.h": "c", "sdkconfig.h": "c", "oslmic.h": "c", "hal_esp32.h": "c", "esp_log.h": "c", - "nvs_flash.h": "c", - "__config": "c", - "__nullptr": "c", - "stdint.h": "c", - "*.ipp": "c", - "algorithm": "c", - "random": "c", - "complex": "c", - "valarray": "c" + "lmic.h": "c", + "ttn_provisioning.h": "c", + "lorabase.h": "c" } } \ No newline at end of file diff --git a/include/TheThingsNetwork.h b/include/TheThingsNetwork.h index bb87fa3..64bdd22 100644 --- a/include/TheThingsNetwork.h +++ b/include/TheThingsNetwork.h @@ -2,37 +2,30 @@ * * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x * - * Copyright (c) 2018 Manuel Bleichenbacher + * Copyright (c) 2018-2021 Manuel Bleichenbacher * * Licensed under MIT License * https://opensource.org/licenses/MIT * - * High-level API for ttn-esp32. + * High-level C++ API for ttn-esp32. *******************************************************************************/ #ifndef _THETHINGSNETWORK_H_ #define _THETHINGSNETWORK_H_ -#include -#include "driver/spi_master.h" +#include "ttn.h" -/** - * @brief Constant for indicating that a pin is not connected - */ -#define TTN_NOT_CONNECTED 0xff - - -typedef uint8_t port_t; +typedef ttn_port_t port_t; /** * @brief Response codes */ enum TTNResponseCode { - kTTNErrorTransmissionFailed = -1, - kTTNErrorUnexpected = -10, - kTTNSuccessfulTransmission = 1, - kTTNSuccessfulReceive = 2 + kTTNErrorTransmissionFailed = TTN_ERROR_TRANSMISSION_FAILED, + kTTNErrorUnexpected = TTN_ERROR_UNEXPECTED, + kTTNSuccessfulTransmission = TTN_SUCCESSFUL_TRANSMISSION, + kTTNSuccessfulReceive = TTN_SUCCESSFUL_RECEIVE }; @@ -44,19 +37,19 @@ enum TTNRxTxWindow /** * @brief Outside RX/TX window */ - kTTNIdleWindow = 0, + kTTNIdleWindow = TTN_WINDOW_IDLE, /** * @brief Transmission window (up to RX1 window) */ - kTTNTxWindow = 1, + kTTNTxWindow = TTN_WINDOW_TX, /** * @brief Reception window 1 (up to RX2 window) */ - kTTNRx1Window = 2, + kTTNRx1Window = TTN_WINDOW_RX1, /** * @brief Reception window 2 */ - kTTNRx2Window = 3 + kTTNRx2Window = TTN_WINDOW_RX2 }; @@ -68,35 +61,35 @@ enum TTNSpreadingFactor /** * @brief Unused / undefined spreading factor */ - kTTNSFNone = 0, + kTTNSFNone = TTN_SF_NONE, /** * @brief Frequency Shift Keying (FSK) */ - kTTNFSK = 1, + kTTNFSK = TTN_FSK, /** * @brief Spreading Factor 7 (SF7) */ - kTTNSF7 = 2, + kTTNSF7 = TTN_SF7, /** * @brief Spreading Factor 8 (SF8) */ - kTTNSF8 = 3, + kTTNSF8 = TTN_SF8, /** * @brief Spreading Factor 9 (SF9) */ - kTTNSF9 = 4, + kTTNSF9 = TTN_SF9, /** * @brief Spreading Factor 10 (SF10) */ - kTTNSF10 = 5, + kTTNSF10 = TTN_SF10, /** * @brief Spreading Factor 11 (SF11) */ - kTTNSF11 = 6, + kTTNSF11 = TTN_SF11, /** * @brief Spreading Factor 12 (SF12) */ - kTTNSF12 = 7 + kTTNSF12 = TTN_SF12 }; @@ -108,19 +101,19 @@ enum TTNBandwidth /** * @brief Undefined/unused bandwidth */ - kTTNBWNone = 0, + kTTNBWNone = TTN_BW_NONE, /** * @brief Bandwidth of 125 kHz */ - kTTNBW125 = 1, + kTTNBW125 = TTN_BW125, /** * @brief Bandwidth of 250 kHz */ - kTTNBW250 = 2, + kTTNBW250 = TTN_BW250, /** * @brief Bandwidth of 500 kHz */ - kTTNBW500 = 3 + kTTNBW500 = TTN_BW500 }; @@ -167,12 +160,12 @@ public: /** * @brief Constructs a new The Things Network device instance. */ - TheThingsNetwork(); + TheThingsNetwork() { ttn_init(); } /** * @brief Destroys the The Things Network device instance. */ - ~TheThingsNetwork(); + ~TheThingsNetwork() { } /** * @brief Resets the LoRaWAN radio. @@ -180,7 +173,7 @@ public: * To restart communication, join() must be called. * It neither clears the provisioned keys nor the configured pins. */ - void reset(); + void reset() { ttn_reset(); } /** * @brief Configures the pins used to communicate with the LoRaWAN radio chip. @@ -195,7 +188,10 @@ public: * @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); + void configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1) + { + ttn_configure_pins(spi_host, nss, rxtx, rst, dio0, dio1); + } /** * @brief Sets the credentials needed to activate the device via OTAA, without activating it. @@ -210,7 +206,7 @@ public: * @param appKey App Key of the device (32 character string with hexadecimal data) * @return `true` if the provisioning was successful, `false` if the provisioning failed */ - bool provision(const char *devEui, const char *appEui, const char *appKey); + bool provision(const char *devEui, const char *appEui, const char *appKey) { return ttn_provision(devEui, appEui, appKey); } /** * @brief Sets the information needed to activate the device via OTAA, using the MAC to generate the device EUI @@ -234,14 +230,14 @@ public: * @param appKey App Key of the device (32 character string with hexadecimal data) * @return `true` if the provisioning was successful, `false` if the provisioning failed */ - bool provisionWithMAC(const char *appEui, const char *appKey); + bool provisionWithMAC(const char *appEui, const char *appKey) { return ttn_provision_with_mac(appEui, appKey); } /** * @brief Starts task listening on configured UART for AT commands. * * Run `make menuconfig` to configure it. */ - void startProvisioningTask(); + void startProvisioningTask() { ttn_start_provisioning_task(); } /** * @brief Waits until the device EUI, app EUI and app key have been provisioned @@ -251,7 +247,7 @@ public: * or call of join(const char*, const char*, const char*), this function * immediately returns. */ - void waitForProvisioning(); + void waitForProvisioning() { ttn_wait_for_provisioning(); } /** * @brief Activates the device via OTAA. @@ -263,7 +259,7 @@ public: * * @return `true` if the activation was succesful, `false` if the activation failed */ - bool join(); + bool join() { return ttn_join_provisioned(); } /** * @brief Sets the device EUI, app EUI and app key and activate the device via OTAA. @@ -277,7 +273,7 @@ public: * @param appKey App Key of the device (32 character string with hexadecimal data) * @return `true` if the activation was succesful, `false` if the activation failed */ - bool join(const char *devEui, const char *appEui, const char *appKey); + bool join(const char *devEui, const char *appEui, const char *appKey) { return ttn_join(devEui, appEui, appKey); } /** * @brief Transmits a message @@ -292,7 +288,10 @@ public: * @param confirm flag indicating if a confirmation should be requested. Defaults to `false` * @return `kTTNSuccessfulTransmission` for successful transmission, `kTTNErrorTransmissionFailed` for failed transmission, `kTTNErrorUnexpected` for unexpected error */ - TTNResponseCode transmitMessage(const uint8_t *payload, size_t length, port_t port = 1, bool confirm = false); + TTNResponseCode transmitMessage(const uint8_t *payload, size_t length, port_t port = 1, bool confirm = false) + { + return static_cast(ttn_transmit_message(payload, length, port, confirm)); + } /** * @brief Sets the function to be called when a message is received @@ -308,7 +307,7 @@ public: * * @param callback the callback function */ - void onMessage(TTNMessageCallback callback); + void onMessage(TTNMessageCallback callback) { ttn_on_message(callback); } /** * @brief Checks if device EUI, app EUI and app key have been stored in non-volatile storage @@ -316,7 +315,7 @@ public: * * @return `true` if they are stored, complete and of the correct size, `false` otherwise */ - bool isProvisioned(); + bool isProvisioned() { return ttn_is_provisioned(); } /** * @brief Sets the RSSI calibration value for LBT (Listen Before Talk). @@ -327,14 +326,14 @@ public: * * @param rssiCal RSSI calibration value, in dB */ - void setRSSICal(int8_t rssiCal); + void setRSSICal(int8_t rssiCal) { ttn_set_rssi_cal(rssiCal); } /** * Returns whether Adaptive Data Rate (ADR) is enabled. * * @return `true` if enabled, `false` if disabled */ - bool adrEnabled(); + bool adrEnabled() { return ttn_adr_enabled(); } /** * @brief Enables or disabled Adaptive Data Rate (ADR). @@ -344,7 +343,7 @@ public: * * @param enabled `true` to enable, `false` to disable */ - void setAdrEnabled(bool enabled); + void setAdrEnabled(bool enabled) { ttn_set_adr_enabled(enabled); } /** * @brief Stops all activies and shuts down the RF module and the background tasks. @@ -352,20 +351,20 @@ public: * To restart communication, startup() and join() must be called. * it neither clears the provisioned keys nor the configured pins. */ - void shutdown(); + void shutdown() { ttn_shutdown(); } /** * @brief Restarts the background tasks and RF module. * * This member function must only be called after a call to shutdowna(). */ - void startup(); + void startup() { ttn_startup(); } /** * @brief Gets current RX/TX window * @return window */ - TTNRxTxWindow rxTxWindow(); + TTNRxTxWindow rxTxWindow() { return static_cast(ttn_rx_tx_window()); } /** * @brief Gets the RF settings for the specified window @@ -377,19 +376,19 @@ public: * @brief Gets the RF settings of the last (or ongoing) transmission. * @return RF settings */ - TTNRFSettings txSettings(); + TTNRFSettings txSettings() { return getRFSettings(kTTNTxWindow); } /** * @brief Gets the RF settings of the last (or ongoing) reception of RX window 1. * @return RF settings */ - TTNRFSettings rx1Settings(); + TTNRFSettings rx1Settings() { return getRFSettings(kTTNRx1Window); } /** * @brief Gets the RF settings of the last (or ongoing) reception of RX window 2. * @return RF settings */ - TTNRFSettings rx2Settings(); + TTNRFSettings rx2Settings() { return getRFSettings(kTTNRx2Window); } /** * @brief Gets the received signal strength indicator (RSSI). @@ -398,12 +397,7 @@ public: * * @return RSSI, in dBm */ - int rssi(); - -private: - TTNMessageCallback messageCallback; - - bool joinCore(); + int rssi() { return ttn_rssi(); } }; #endif diff --git a/include/ttn.h b/include/ttn.h new file mode 100644 index 0000000..d8b189d --- /dev/null +++ b/include/ttn.h @@ -0,0 +1,400 @@ +/******************************************************************************* + * + * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x + * + * Copyright (c) 2018-2021 Manuel Bleichenbacher + * + * Licensed under MIT License + * https://opensource.org/licenses/MIT + * + * High-level C API for ttn-esp32. + *******************************************************************************/ + +#ifndef TTN_C_H +#define TTN_C_H + +#include +#include "driver/spi_master.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Constant for indicating that a pin is not connected + */ +#define TTN_NOT_CONNECTED 0xff + + +typedef uint8_t ttn_port_t; + +/** + * @brief Response codes + */ +typedef enum +{ + TTN_ERROR_TRANSMISSION_FAILED = -1, + TTN_ERROR_UNEXPECTED = -10, + TTN_SUCCESSFUL_TRANSMISSION = 1, + TTN_SUCCESSFUL_RECEIVE = 2 +} ttn_response_code_t; + + +/** + * @brief RX/TX window + */ +typedef enum +{ + /** + * @brief Outside RX/TX window + */ + TTN_WINDOW_IDLE = 0, + /** + * @brief Transmission window (up to RX1 window) + */ + TTN_WINDOW_TX = 1, + /** + * @brief Reception window 1 (up to RX2 window) + */ + TTN_WINDOW_RX1 = 2, + /** + * @brief Reception window 2 + */ + TTN_WINDOW_RX2 = 3 +} ttn_rx_tx_window_t; + + +/** + * @brief Spreading Factor + */ +typedef enum +{ + /** + * @brief Unused / undefined spreading factor + */ + TTN_SF_NONE = 0, + /** + * @brief Frequency Shift Keying (FSK) + */ + TTN_FSK = 1, + /** + * @brief Spreading Factor 7 (SF7) + */ + TTN_SF7 = 2, + /** + * @brief Spreading Factor 8 (SF8) + */ + TTN_SF8 = 3, + /** + * @brief Spreading Factor 9 (SF9) + */ + TTN_SF9 = 4, + /** + * @brief Spreading Factor 10 (SF10) + */ + TTN_SF10 = 5, + /** + * @brief Spreading Factor 11 (SF11) + */ + TTN_SF11 = 6, + /** + * @brief Spreading Factor 12 (SF12) + */ + TTN_SF12 = 7 +} ttn_spreading_factor_t; + + +/** + * @brief Bandwidth + */ +typedef enum +{ + /** + * @brief Undefined/unused bandwidth + */ + TTN_BW_NONE = 0, + /** + * @brief Bandwidth of 125 kHz + */ + TTN_BW125 = 1, + /** + * @brief Bandwidth of 250 kHz + */ + TTN_BW250 = 2, + /** + * @brief Bandwidth of 500 kHz + */ + TTN_BW500 = 3 +} ttn_bandwidth_t; + + +/** + * @brief RF settings for TX or RX + */ +typedef struct +{ + /** + * @brief Spreading Factor (SF) + */ + ttn_spreading_factor_t spreading_factor; + /** + * @brief Bandwidth (BW) + */ + ttn_bandwidth_t bandwidth; + /** + * @brief Frequency, in Hz + */ + uint32_t frequency; +} ttn_rf_settings_t; + + +/** + * @brief Callback for recieved messages + * + * @param payload pointer to the received bytes + * @param length number of received bytes + * @param port port the message was received on + */ +typedef void (*ttn_message_cb)(const uint8_t* payload, size_t length, ttn_port_t port); + + +/** + * @brief Constructs a new The Things Network device instance. + */ +void ttn_init(void); + +/** + * @brief Resets the LoRaWAN radio. + * + * To restart communication, join() must be called. + * It neither clears the provisioned keys nor the configured pins. + */ +void ttn_reset(void); + +/** + * @brief Configures the pins used to communicate with the LoRaWAN radio chip. + * + * Before calling this member function, the SPI bus needs to be configured using `spi_bus_initialize()`. + * Additionally, `gpio_install_isr_service()` must have been called to initialize the GPIO ISR handler service. + * + * @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 ttn_configure_pins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1); + +/** + * @brief Sets the credentials needed to activate the device via OTAA, without activating it. + * + * The provided device EUI, app EUI and app key are saved in non-volatile memory. Before + * this function is called, `nvs_flash_init()` must have been called once. + * + * Call join() to activate the device. + * + * @param dev_eui Device EUI (16 character string with hexadecimal data) + * @param app_eui Application EUI of the device (16 character string with hexadecimal data) + * @param app_key App Key of the device (32 character string with hexadecimal data) + * @return `true` if the provisioning was successful, `false` if the provisioning failed + */ +bool ttn_provision(const char *dev_eui, const char *app_eui, const char *app_key); + +/** + * @brief Sets the information needed to activate the device via OTAA, using the MAC to generate the device EUI + * and without activating it. + * + * The generated device EUI and the provided app EUI and app key are saved in non-volatile memory. Before + * this function is called, 'nvs_flash_init' must have been called once. + * + * The device EUI is generated by retrieving the ESP32's WiFi MAC address and expanding it into a device EUI + * by adding FFFE in the middle. So the MAC address A0:B1:C2:01:02:03 becomes the EUI A0B1C2FFFE010203. + * This hexadecimal data can be entered into the Device EUI field in the TTN console. + * + * Generating the device EUI from the MAC address allows to flash the same app EUI and app key to a batch of + * devices. However, using the same app key for multiple devices is insecure. Only use this approach if + * it is okay for that the LoRa communication of your application can easily be intercepted and that + * forged data can be injected. + * + * Call join() to activate. + * + * @param app_eui Application EUI of the device (16 character string with hexadecimal data) + * @param app_key App Key of the device (32 character string with hexadecimal data) + * @return `true` if the provisioning was successful, `false` if the provisioning failed + */ +bool ttn_provision_with_mac(const char *app_eui, const char *app_key); + +/** + * @brief Starts task listening on configured UART for AT commands. + * + * Run `make menuconfig` to configure it. + */ +void ttn_start_provisioning_task(void); + +/** + * @brief Waits until the device EUI, app EUI and app key have been provisioned + * by the provisioning task. + * + * If the device has already been provisioned (stored data in NVS, call of provision() + * or call of join(const char*, const char*, const char*), this function + * immediately returns. + */ +void ttn_wait_for_provisioning(void); + + /** + * @brief Activates the device via OTAA. + * + * The app EUI, app key and dev EUI must have already been provisioned by a call to provision(). + * Before this function is called, `nvs_flash_init()` must have been called once. + * + * The function blocks until the activation has completed or failed. + * + * @return `true` if the activation was succesful, `false` if the activation failed + */ +bool ttn_join_provisioned(void); + +/** + * @brief Sets the device EUI, app EUI and app key and activate the device via OTAA. + * + * The device EUI, app EUI and app key are NOT saved in non-volatile memory. + * + * The function blocks until the activation has completed or failed. + * + * @param dev_eui Device EUI (16 character string with hexadecimal data) + * @param app_eui Application EUI of the device (16 character string with hexadecimal data) + * @param app_key App Key of the device (32 character string with hexadecimal data) + * @return `true` if the activation was succesful, `false` if the activation failed + */ +bool ttn_join(const char *dev_eui, const char *app_eui, const char *app_key); + +/** + * @brief Transmits a message + * + * The function blocks until the message could be transmitted and a message has been received + * in the subsequent receive window (or the window expires). Additionally, the function will + * first wait until the duty cycle allows a transmission (enforcing the duty cycle limits). + * + * @param payload bytes to be transmitted + * @param length number of bytes to be transmitted + * @param port port (use 1 as default) + * @param confirm flag indicating if a confirmation should be requested (use `false` as default) + * @return `kTTNSuccessfulTransmission` for successful transmission, `kTTNErrorTransmissionFailed` for failed transmission, `kTTNErrorUnexpected` for unexpected error + */ +ttn_response_code_t ttn_transmit_message(const uint8_t *payload, size_t length, ttn_port_t port, bool confirm); + +/** + * @brief Sets the function to be called when a message is received + * + * When a message is received, the specified function is called. The + * message, its length and the port number are provided as + * parameters. The values are only valid during the duration of the + * callback. So they must be immediately processed or copied. + * + * Messages are received as a result of transmitMessage(). The callback is called + * in the task that called any of these functions and it occurs before these functions + * return control to the caller. + * + * @param callback the callback function + */ +void ttn_on_message(ttn_message_cb callback); + +/** + * @brief Checks if device EUI, app EUI and app key have been stored in non-volatile storage + * or have been provided as by a call to join(const char*, const char*, const char*). + * + * @return `true` if they are stored, complete and of the correct size, `false` otherwise + */ +bool ttn_is_provisioned(void); + +/** + * @brief Sets the RSSI calibration value for LBT (Listen Before Talk). + * + * This value is added to RSSI measured prior to decision. It must include the guardband. + * Ignored in US, EU, IN and other countries where LBT is not required. + * Defaults to 10 dB. + * + * @param rssi_cal RSSI calibration value, in dB + */ +void ttn_set_rssi_cal(int8_t rssi_cal); + +/** + * Returns whether Adaptive Data Rate (ADR) is enabled. + * + * @return `true` if enabled, `false` if disabled + */ +bool ttn_adr_enabled(void); + +/** + * @brief Enables or disabled Adaptive Data Rate (ADR). + * + * ADR is enabled by default. It optimizes data rate, airtime and energy consumption + * for devices with stable RF conditions. It should be turned off for mobile devices. + * + * @param enabled `true` to enable, `false` to disable + */ +void ttn_set_adr_enabled(bool enabled); + +/** + * @brief Stops all activies and shuts down the RF module and the background tasks. + * + * To restart communication, startup() and join() must be called. + * it neither clears the provisioned keys nor the configured pins. + */ +void ttn_shutdown(void); + +/** + * @brief Restarts the background tasks and RF module. + * + * This member function must only be called after a call to shutdowna(). + */ +void ttn_startup(void); + +/** + * @brief Gets current RX/TX window + * @return window + */ +ttn_rx_tx_window_t ttn_rx_tx_window(void); + +/** + * @brief Gets the RF settings for the specified window + * @param window RX/TX windows (valid values are `kTTNTxWindow`, `kTTNRx1Window` and `kTTNRx2Window`) + */ +ttn_rf_settings_t ttn_get_rf_settings(ttn_rx_tx_window_t window); + +/** + * @brief Gets the RF settings of the last (or ongoing) transmission. + * @return RF settings + */ +ttn_rf_settings_t ttn_tx_settings(void); + +/** + * @brief Gets the RF settings of the last (or ongoing) reception of RX window 1. + * @return RF settings + */ +ttn_rf_settings_t ttn_rx1_settings(void); + +/** + * @brief Gets the RF settings of the last (or ongoing) reception of RX window 2. + * @return RF settings + */ +ttn_rf_settings_t ttn_rx2_settings(void); + +/** + * @brief Gets the received signal strength indicator (RSSI). + * + * RSSI is the measured signal strength of the last recevied message (incl. join responses). + * + * @return RSSI, in dBm + */ +int ttn_rssi(); + + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/src/TheThingsNetwork.cpp b/src/TheThingsNetwork.cpp index cf7b78b..bfdfe61 100644 --- a/src/TheThingsNetwork.cpp +++ b/src/TheThingsNetwork.cpp @@ -2,422 +2,23 @@ * * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x * - * Copyright (c) 2018-2019 Manuel Bleichenbacher + * Copyright (c) 2018-2021 Manuel Bleichenbacher * * Licensed under MIT License * https://opensource.org/licenses/MIT * - * High-level API for ttn-esp32. + * High-level C++ API for ttn-esp32. *******************************************************************************/ -#include "freertos/FreeRTOS.h" -#include "esp_event.h" -#include "esp_log.h" -#include "hal/hal_esp32.h" -#include "lmic/lmic.h" #include "TheThingsNetwork.h" -#include "ttn_provisioning.h" -#include "ttn_logging.h" -/** - * @brief Reason the user code is waiting - */ -enum TTNWaitingReason -{ - eWaitingNone, - eWaitingForJoin, - eWaitingForTransmission -}; - -/** - * @brief Event type - */ -enum TTNEvent { - eEvtNone, - eEvtJoinCompleted, - eEvtJoinFailed, - eEvtMessageReceived, - eEvtTransmissionCompleted, - eEvtTransmissionFailed -}; - -/** - * @brief Event message sent from LMIC task to waiting client task - */ -struct TTNLmicEvent { - TTNLmicEvent(TTNEvent ev = eEvtNone): event(ev) { } - - TTNEvent event; - uint8_t port; - const uint8_t* message; - size_t messageSize; -}; - -static const char *TAG = "ttn"; - -static TheThingsNetwork* ttnInstance; -static QueueHandle_t lmicEventQueue = nullptr; -static TTNWaitingReason waitingReason = eWaitingNone; -static TTNRFSettings lastRfSettings[4]; -static TTNRxTxWindow currentWindow; - -static void eventCallback(void* userData, ev_t event); -static void messageReceivedCallback(void *userData, uint8_t port, const uint8_t *message, size_t messageSize); -static void messageTransmittedCallback(void *userData, int success); -static void saveRFSettings(TTNRFSettings& rfSettings); -static void clearRFSettings(TTNRFSettings& rfSettings); - - -TheThingsNetwork::TheThingsNetwork() - : messageCallback(nullptr) -{ -#if defined(TTN_IS_DISABLED) - ESP_LOGE(TAG, "TTN is disabled. Configure a frequency plan using 'make menuconfig'"); - ASSERT(0); -#endif - - ASSERT(ttnInstance == nullptr); - ttnInstance = this; - hal_esp32_init_critical_section(); -} - -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) -{ - hal_esp32_configure_pins(spi_host, nss, rxtx, rst, dio0, dio1); - -#if LMIC_ENABLE_event_logging - ttn_log_init(); -#endif - - LMIC_registerEventCb(eventCallback, nullptr); - LMIC_registerRxMessageCb(messageReceivedCallback, nullptr); - - os_init_ex(nullptr); - reset(); - - lmicEventQueue = xQueueCreate(4, sizeof(TTNLmicEvent)); - ASSERT(lmicEventQueue != nullptr); - hal_esp32_start_lmic_task(); -} - -void TheThingsNetwork::reset() -{ - hal_esp32_enter_critical_section(); - LMIC_reset(); - LMIC_setClockError(MAX_CLOCK_ERROR * 4 / 100); - waitingReason = eWaitingNone; - hal_esp32_leave_critical_section(); -} - -void TheThingsNetwork::shutdown() -{ - hal_esp32_enter_critical_section(); - LMIC_shutdown(); - hal_esp32_stop_lmic_task(); - waitingReason = eWaitingNone; - hal_esp32_leave_critical_section(); -} - -void TheThingsNetwork::startup() -{ - hal_esp32_enter_critical_section(); - LMIC_reset(); - hal_esp32_start_lmic_task(); - hal_esp32_leave_critical_section(); -} - -bool TheThingsNetwork::provision(const char *devEui, const char *appEui, const char *appKey) -{ - if (!ttn_provisioning_decode_keys(devEui, appEui, appKey)) - return false; - - return ttn_provisioning_save_keys(); -} - -bool TheThingsNetwork::provisionWithMAC(const char *appEui, const char *appKey) -{ - if (!ttn_provisioning_from_mac(appEui, appKey)) - return false; - - return ttn_provisioning_save_keys(); -} - - -void TheThingsNetwork::startProvisioningTask() -{ -#if defined(TTN_HAS_AT_COMMANDS) - ttn_provisioning_start_task(); -#else - ESP_LOGE(TAG, "AT commands are disabled. Change the configuration using 'make menuconfig'"); - ASSERT(0); - esp_restart(); -#endif -} - -void TheThingsNetwork::waitForProvisioning() -{ -#if defined(TTN_HAS_AT_COMMANDS) - if (isProvisioned()) - { - ESP_LOGI(TAG, "Device is already provisioned"); - return; - } - - while (!ttn_provisioning_have_keys()) - vTaskDelay(pdMS_TO_TICKS(1000)); - - ESP_LOGI(TAG, "Device successfully provisioned"); -#else - ESP_LOGE(TAG, "AT commands are disabled. Change the configuration using 'make menuconfig'"); - ASSERT(0); - esp_restart(); -#endif -} - -bool TheThingsNetwork::join(const char *devEui, const char *appEui, const char *appKey) -{ - if (!ttn_provisioning_decode_keys(devEui, appEui, appKey)) - return false; - - return joinCore(); -} - -bool TheThingsNetwork::join() -{ - if (!ttn_provisioning_have_keys()) - { - if (!ttn_provisioning_restore_keys(false)) - return false; - } - - return joinCore(); -} - -bool TheThingsNetwork::joinCore() -{ - if (!ttn_provisioning_have_keys()) - { - ESP_LOGW(TAG, "Device EUI, App EUI and/or App key have not been provided"); - return false; - } - - hal_esp32_enter_critical_section(); - xQueueReset(lmicEventQueue); - waitingReason = eWaitingForJoin; - LMIC_startJoining(); - hal_esp32_wake_up(); - hal_esp32_leave_critical_section(); - - TTNLmicEvent event; - xQueueReceive(lmicEventQueue, &event, portMAX_DELAY); - return event.event == eEvtJoinCompleted; -} - -TTNResponseCode TheThingsNetwork::transmitMessage(const uint8_t *payload, size_t length, port_t port, bool confirm) -{ - hal_esp32_enter_critical_section(); - if (waitingReason != eWaitingNone || (LMIC.opmode & OP_TXRXPEND) != 0) - { - hal_esp32_leave_critical_section(); - return kTTNErrorTransmissionFailed; - } - - waitingReason = eWaitingForTransmission; - LMIC.client.txMessageCb = messageTransmittedCallback; - LMIC.client.txMessageUserData = nullptr; - LMIC_setTxData2(port, (xref2u1_t)payload, length, confirm); - hal_esp32_wake_up(); - hal_esp32_leave_critical_section(); - - while (true) - { - TTNLmicEvent result; - xQueueReceive(lmicEventQueue, &result, portMAX_DELAY); - - switch (result.event) - { - case eEvtMessageReceived: - if (messageCallback != nullptr) - messageCallback(result.message, result.messageSize, result.port); - break; - - case eEvtTransmissionCompleted: - return kTTNSuccessfulTransmission; - - case eEvtTransmissionFailed: - return kTTNErrorTransmissionFailed; - - default: - ASSERT(0); - } - } -} - -void TheThingsNetwork::onMessage(TTNMessageCallback callback) -{ - messageCallback = callback; -} - - -bool TheThingsNetwork::isProvisioned() -{ - if (ttn_provisioning_have_keys()) - return true; - - ttn_provisioning_restore_keys(true); - - return ttn_provisioning_have_keys(); -} - -void TheThingsNetwork::setRSSICal(int8_t rssiCal) -{ - hal_esp32_set_rssi_cal(rssiCal); -} - -bool TheThingsNetwork::adrEnabled() -{ - return LMIC.adrEnabled != 0; -} - -void TheThingsNetwork::setAdrEnabled(bool enabled) -{ - LMIC_setAdrMode(enabled); -} - TTNRFSettings TheThingsNetwork::getRFSettings(TTNRxTxWindow window) { - int index = static_cast(window) & 0x03; - return lastRfSettings[index]; -} - -TTNRFSettings TheThingsNetwork::txSettings() -{ - return lastRfSettings[static_cast(kTTNTxWindow)]; -} - -TTNRFSettings TheThingsNetwork::rx1Settings() -{ - return lastRfSettings[static_cast(kTTNRx1Window)]; -} - -TTNRFSettings TheThingsNetwork::rx2Settings() -{ - return lastRfSettings[static_cast(kTTNRx2Window)]; -} - -TTNRxTxWindow TheThingsNetwork::rxTxWindow() -{ - return currentWindow; -} - -int TheThingsNetwork::rssi() -{ - return LMIC.rssi; -} - - -// --- Callbacks --- - -#if CONFIG_LOG_DEFAULT_LEVEL >= 3 || LMIC_ENABLE_event_logging -const char *eventNames[] = { LMIC_EVENT_NAME_TABLE__INIT }; -#endif - - -// Called by LMIC when an LMIC event (join, join failed, reset etc.) occurs -void eventCallback(void* userData, ev_t event) -{ - // update monitoring information - switch(event) - { - case EV_TXSTART: - currentWindow = kTTNTxWindow; - saveRFSettings(lastRfSettings[static_cast(kTTNTxWindow)]); - clearRFSettings(lastRfSettings[static_cast(kTTNRx1Window)]); - clearRFSettings(lastRfSettings[static_cast(kTTNRx2Window)]); - break; - - case EV_RXSTART: - if (currentWindow != kTTNRx1Window) - { - currentWindow = kTTNRx1Window; - saveRFSettings(lastRfSettings[static_cast(kTTNRx1Window)]); - } - else - { - currentWindow = kTTNRx2Window; - saveRFSettings(lastRfSettings[static_cast(kTTNRx2Window)]); - } - break; - - default: - currentWindow = kTTNIdleWindow; - break; - }; - -#if LMIC_ENABLE_event_logging - ttn_log_event(event, eventNames[event], 0); -#elif CONFIG_LOG_DEFAULT_LEVEL >= 3 - ESP_LOGI(TAG, "event %s", eventNames[event]); -#endif - - TTNEvent ttnEvent = eEvtNone; - - if (waitingReason == eWaitingForJoin) - { - if (event == EV_JOINED) - { - ttnEvent = eEvtJoinCompleted; - } - else if (event == EV_REJOIN_FAILED || event == EV_RESET) - { - ttnEvent = eEvtJoinFailed; - } - } - - if (ttnEvent == eEvtNone) - return; - - TTNLmicEvent result(ttnEvent); - waitingReason = eWaitingNone; - xQueueSend(lmicEventQueue, &result, pdMS_TO_TICKS(100)); -} - -// Called by LMIC when a message has been received -void messageReceivedCallback(void *userData, uint8_t port, const uint8_t *message, size_t nMessage) -{ - TTNLmicEvent result(eEvtMessageReceived); - result.port = port; - result.message = message; - result.messageSize = nMessage; - xQueueSend(lmicEventQueue, &result, pdMS_TO_TICKS(100)); -} - -// Called by LMIC when a message has been transmitted (or the transmission failed) -void messageTransmittedCallback(void *userData, int success) -{ - waitingReason = eWaitingNone; - TTNLmicEvent result(success ? eEvtTransmissionCompleted : eEvtTransmissionFailed); - xQueueSend(lmicEventQueue, &result, pdMS_TO_TICKS(100)); -} - - -// --- Helpers - - -void saveRFSettings(TTNRFSettings& rfSettings) -{ - rfSettings.spreadingFactor = static_cast(getSf(LMIC.rps) + 1); - rfSettings.bandwidth = static_cast(getBw(LMIC.rps) + 1); - rfSettings.frequency = LMIC.freq; -} - -void clearRFSettings(TTNRFSettings& rfSettings) -{ - memset(&rfSettings, 0, sizeof(rfSettings)); + ttn_rf_settings_t settings = ttn_get_rf_settings(static_cast(window)); + TTNRFSettings result; + result.spreadingFactor = static_cast(settings.spreading_factor); + result.bandwidth = static_cast(settings.bandwidth); + result.frequency = settings.frequency; + return result; } diff --git a/src/ttn.c b/src/ttn.c new file mode 100644 index 0000000..f78463a --- /dev/null +++ b/src/ttn.c @@ -0,0 +1,422 @@ +/******************************************************************************* + * + * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x + * + * Copyright (c) 2018-2021 Manuel Bleichenbacher + * + * Licensed under MIT License + * https://opensource.org/licenses/MIT + * + * High-level C API for ttn-esp32. + *******************************************************************************/ + +#include "lmic/lmic.h" +#include "ttn.h" +#include "ttn_provisioning.h" +#include "ttn_logging.h" +#include "hal/hal_esp32.h" +#include "freertos/FreeRTOS.h" +#include "esp_event.h" +#include "esp_log.h" + + +#define TAG "ttn" + + +/** + * @brief Reason the user code is waiting + */ +typedef enum +{ + TTN_WAITING_NONE, + TTN_WAITING_FOR_JOIN, + TTN_WAITING_FOR_TRANSMISSION +} ttn_waiting_reason_t; + +/** + * @brief Event type + */ +typedef enum { + TTN_EVENT_NONE, + TTN_EVNT_JOIN_COMPLETED, + TTN_EVENT_JOIN_FAILED, + TTN_EVENT_MESSAGE_RECEIVED, + TTN_EVENT_TRANSMISSION_COMPLETED, + TTN_EVENT_TRANSMISSION_FAILED +} ttn_event_t; + +/** + * @brief Event message sent from LMIC task to waiting client task + */ +typedef struct { + ttn_event_t event; + uint8_t port; + const uint8_t* message; + size_t message_size; +} ttn_lmic_event_t; + +static QueueHandle_t lmic_event_queue; +static ttn_message_cb message_callback; +static ttn_waiting_reason_t waiting_reason = TTN_WAITING_NONE; +static ttn_rf_settings_t last_rf_settings[4]; +static ttn_rx_tx_window_t current_rx_tx_window; + +static bool join_core(void); +static void event_callback(void* user_data, ev_t event); +static void message_received_callback(void *user_data, uint8_t port, const uint8_t *message, size_t message_size); +static void message_transmitted_callback(void *user_data, int success); +static void save_rf_settings(ttn_rf_settings_t* rf_settings); +static void clear_rf_settings(ttn_rf_settings_t* rf_settings); + + +void ttn_init(void) +{ +#if defined(TTN_IS_DISABLED) + ESP_LOGE(TAG, "TTN is disabled. Configure a frequency plan using 'make menuconfig'"); + ASSERT(0); +#endif + + message_callback = NULL; + hal_esp32_init_critical_section(); +} + +void ttn_configure_pins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1) +{ + hal_esp32_configure_pins(spi_host, nss, rxtx, rst, dio0, dio1); + +#if LMIC_ENABLE_event_logging + ttn_log_init(); +#endif + + LMIC_registerEventCb(event_callback, NULL); + LMIC_registerRxMessageCb(message_received_callback, NULL); + + os_init_ex(NULL); + ttn_reset(); + + lmic_event_queue = xQueueCreate(4, sizeof(ttn_lmic_event_t)); + ASSERT(lmic_event_queue != NULL); + hal_esp32_start_lmic_task(); +} + +void ttn_reset(void) +{ + hal_esp32_enter_critical_section(); + LMIC_reset(); + LMIC_setClockError(MAX_CLOCK_ERROR * 4 / 100); + waiting_reason = TTN_WAITING_NONE; + hal_esp32_leave_critical_section(); +} + +void ttn_shutdown(void) +{ + hal_esp32_enter_critical_section(); + LMIC_shutdown(); + hal_esp32_stop_lmic_task(); + waiting_reason = TTN_WAITING_NONE; + hal_esp32_leave_critical_section(); +} + +void ttn_startup(void) +{ + hal_esp32_enter_critical_section(); + LMIC_reset(); + hal_esp32_start_lmic_task(); + hal_esp32_leave_critical_section(); +} + +bool ttn_provision(const char *dev_eui, const char *app_eui, const char *app_key) +{ + if (!ttn_provisioning_decode_keys(dev_eui, app_eui, app_key)) + return false; + + return ttn_provisioning_save_keys(); +} + +bool ttn_provision_with_mac(const char *app_eui, const char *app_key) +{ + if (!ttn_provisioning_from_mac(app_eui, app_key)) + return false; + + return ttn_provisioning_save_keys(); +} + + +void ttn_start_provisioning_task(void) +{ +#if defined(TTN_HAS_AT_COMMANDS) + ttn_provisioning_start_task(); +#else + ESP_LOGE(TAG, "AT commands are disabled. Change the configuration using 'make menuconfig'"); + ASSERT(0); + esp_restart(); +#endif +} + +void ttn_wait_for_provisioning(void) +{ +#if defined(TTN_HAS_AT_COMMANDS) + if (ttn_is_provisioned()) + { + ESP_LOGI(TAG, "Device is already provisioned"); + return; + } + + while (!ttn_provisioning_have_keys()) + vTaskDelay(pdMS_TO_TICKS(1000)); + + ESP_LOGI(TAG, "Device successfully provisioned"); +#else + ESP_LOGE(TAG, "AT commands are disabled. Change the configuration using 'make menuconfig'"); + ASSERT(0); + esp_restart(); +#endif +} + +bool ttn_join(const char *dev_eui, const char *app_eui, const char *app_key) +{ + if (!ttn_provisioning_decode_keys(dev_eui, app_eui, app_key)) + return false; + + return join_core(); +} + +bool ttn_join_provisioned(void) +{ + if (!ttn_provisioning_have_keys()) + { + if (!ttn_provisioning_restore_keys(false)) + return false; + } + + return join_core(); +} + +bool join_core() +{ + if (!ttn_provisioning_have_keys()) + { + ESP_LOGW(TAG, "Device EUI, App EUI and/or App key have not been provided"); + return false; + } + + hal_esp32_enter_critical_section(); + xQueueReset(lmic_event_queue); + waiting_reason = TTN_WAITING_FOR_JOIN; + LMIC_startJoining(); + hal_esp32_wake_up(); + hal_esp32_leave_critical_section(); + + ttn_lmic_event_t event; + xQueueReceive(lmic_event_queue, &event, portMAX_DELAY); + return event.event == TTN_EVNT_JOIN_COMPLETED; +} + +ttn_response_code_t ttn_transmit_message(const uint8_t *payload, size_t length, ttn_port_t port, bool confirm) +{ + hal_esp32_enter_critical_section(); + if (waiting_reason != TTN_WAITING_NONE || (LMIC.opmode & OP_TXRXPEND) != 0) + { + hal_esp32_leave_critical_section(); + return TTN_ERROR_TRANSMISSION_FAILED; + } + + waiting_reason = TTN_WAITING_FOR_TRANSMISSION; + LMIC.client.txMessageCb = message_transmitted_callback; + LMIC.client.txMessageUserData = NULL; + LMIC_setTxData2(port, (xref2u1_t)payload, length, confirm); + hal_esp32_wake_up(); + hal_esp32_leave_critical_section(); + + while (true) + { + ttn_lmic_event_t result; + xQueueReceive(lmic_event_queue, &result, portMAX_DELAY); + + switch (result.event) + { + case TTN_EVENT_MESSAGE_RECEIVED: + if (message_callback != NULL) + message_callback(result.message, result.message_size, result.port); + break; + + case TTN_EVENT_TRANSMISSION_COMPLETED: + return TTN_SUCCESSFUL_TRANSMISSION; + + case TTN_EVENT_TRANSMISSION_FAILED: + return TTN_ERROR_TRANSMISSION_FAILED; + + default: + ASSERT(0); + } + } +} + +void ttn_on_message(ttn_message_cb callback) +{ + message_callback = callback; +} + + +bool ttn_is_provisioned(void) +{ + if (ttn_provisioning_have_keys()) + return true; + + ttn_provisioning_restore_keys(true); + + return ttn_provisioning_have_keys(); +} + +void ttn_set_rssi_cal(int8_t rssi_cal) +{ + hal_esp32_set_rssi_cal(rssi_cal); +} + +bool ttn_adr_enabled(void) +{ + return LMIC.adrEnabled != 0; +} + +void ttn_set_adr_nabled(bool enabled) +{ + LMIC_setAdrMode(enabled); +} + +ttn_rf_settings_t ttn_getrf_settings(ttn_rx_tx_window_t window) +{ + int index = ((int)window) & 0x03; + return last_rf_settings[index]; +} + +ttn_rf_settings_t ttn_tx_settings(void) +{ + return last_rf_settings[TTN_WINDOW_TX]; +} + +ttn_rf_settings_t ttn_rx1_settings(void) +{ + return last_rf_settings[TTN_WINDOW_RX1]; +} + +ttn_rf_settings_t ttn_rx2_settings(void) +{ + return last_rf_settings[TTN_WINDOW_RX2]; +} + +ttn_rx_tx_window_t ttn_rx_tx_window(void) +{ + return current_rx_tx_window; +} + +int ttn_rssi(void) +{ + return LMIC.rssi; +} + + +// --- Callbacks --- + +#if CONFIG_LOG_DEFAULT_LEVEL >= 3 || LMIC_ENABLE_event_logging +static const char *event_names[] = { LMIC_EVENT_NAME_TABLE__INIT }; +#endif + + +// Called by LMIC when an LMIC event (join, join failed, reset etc.) occurs +void event_callback(void* user_data, ev_t event) +{ + // update monitoring information + switch(event) + { + case EV_TXSTART: + current_rx_tx_window = TTN_WINDOW_TX; + save_rf_settings(&last_rf_settings[TTN_WINDOW_TX]); + clear_rf_settings(&last_rf_settings[TTN_WINDOW_RX1]); + clear_rf_settings(&last_rf_settings[TTN_WINDOW_RX2]); + break; + + case EV_RXSTART: + if (current_rx_tx_window != TTN_WINDOW_RX1) + { + current_rx_tx_window = TTN_WINDOW_RX1; + save_rf_settings(&last_rf_settings[TTN_WINDOW_RX1]); + } + else + { + current_rx_tx_window = TTN_WINDOW_RX2; + save_rf_settings(&last_rf_settings[TTN_WINDOW_RX2]); + } + break; + + default: + current_rx_tx_window = TTN_WINDOW_IDLE; + break; + }; + +#if LMIC_ENABLE_event_logging + ttn_log_event(event, event_names[event], 0); +#elif CONFIG_LOG_DEFAULT_LEVEL >= 3 + ESP_LOGI(TAG, "event %s", event_names[event]); +#endif + + ttn_event_t ttn_event = TTN_EVENT_NONE; + + if (waiting_reason == TTN_WAITING_FOR_JOIN) + { + if (event == EV_JOINED) + { + ttn_event = TTN_EVNT_JOIN_COMPLETED; + } + else if (event == EV_REJOIN_FAILED || event == EV_RESET) + { + ttn_event = TTN_EVENT_JOIN_FAILED; + } + } + + if (ttn_event == TTN_EVENT_NONE) + return; + + ttn_lmic_event_t result = { + .event = ttn_event + }; + waiting_reason = TTN_WAITING_NONE; + xQueueSend(lmic_event_queue, &result, pdMS_TO_TICKS(100)); +} + +// Called by LMIC when a message has been received +void message_received_callback(void *user_data, uint8_t port, const uint8_t *message, size_t message_size) +{ + ttn_lmic_event_t result = { + .event = TTN_EVENT_MESSAGE_RECEIVED, + .port = port, + .message = message, + .message_size = message_size + }; + xQueueSend(lmic_event_queue, &result, pdMS_TO_TICKS(100)); +} + +// Called by LMIC when a message has been transmitted (or the transmission failed) +void message_transmitted_callback(void *user_data, int success) +{ + waiting_reason = TTN_WAITING_NONE; + ttn_lmic_event_t result = { + .event = success ? TTN_EVENT_TRANSMISSION_COMPLETED : TTN_EVENT_TRANSMISSION_FAILED + }; + xQueueSend(lmic_event_queue, &result, pdMS_TO_TICKS(100)); +} + + +// --- Helpers + + +void save_rf_settings(ttn_rf_settings_t* rf_settings) +{ + rf_settings->spreading_factor = (ttn_spreading_factor_t)(getSf(LMIC.rps) + 1); + rf_settings->bandwidth = (ttn_bandwidth_t)(getBw(LMIC.rps) + 1); + rf_settings->frequency = LMIC.freq; +} + +void clear_rf_settings(ttn_rf_settings_t* rf_settings) +{ + memset(rf_settings, 0, sizeof(*rf_settings)); +}