diff --git a/examples/monitoring/CMakeLists.txt b/examples/monitoring/CMakeLists.txt new file mode 100644 index 0000000..d78434d --- /dev/null +++ b/examples/monitoring/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# Update the below line to match the path to the ttn-esp32 library, +# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32") +list(APPEND EXTRA_COMPONENT_DIRS "../..") + +project(monitoring) diff --git a/examples/monitoring/Makefile b/examples/monitoring/Makefile new file mode 100644 index 0000000..f87472e --- /dev/null +++ b/examples/monitoring/Makefile @@ -0,0 +1,5 @@ +PROJECT_NAME := monitoring + +EXTRA_COMPONENT_DIRS := $(abspath ../..) + +include $(IDF_PATH)/make/project.mk diff --git a/examples/monitoring/main/CMakeLists.txt b/examples/monitoring/main/CMakeLists.txt new file mode 100644 index 0000000..8aa291e --- /dev/null +++ b/examples/monitoring/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRCS "main.cpp" + INCLUDE_DIRS "." + REQUIRES ttn-esp32) diff --git a/examples/monitoring/main/component.mk b/examples/monitoring/main/component.mk new file mode 100644 index 0000000..e69de29 diff --git a/examples/monitoring/main/main.cpp b/examples/monitoring/main/main.cpp new file mode 100644 index 0000000..5454c6f --- /dev/null +++ b/examples/monitoring/main/main.cpp @@ -0,0 +1,140 @@ +/******************************************************************************* + * + * ttn-esp32 - The Things Network device library for ESP-IDF / SX127x + * + * Copyright (c) 2018 Manuel Bleichenbacher + * + * Licensed under MIT License + * https://opensource.org/licenses/MIT + * + * Sample program showing how to send and receive messages. + *******************************************************************************/ + +#include "freertos/FreeRTOS.h" +#include "esp_event.h" +#include "driver/gpio.h" +#include "nvs_flash.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 hex string from the "Device EUI" field +// on your device's overview page in the TTN console. +const char *devEui = "????????????????"; + +// Copy the below two lines from bottom of the same page +const char *appEui = "????????????????"; +const char *appKey = "????????????????????????????????"; + +// Pins and other resources +#define TTN_SPI_HOST HSPI_HOST +#define TTN_SPI_DMA_CHAN 1 +#define TTN_PIN_SPI_SCLK 5 +#define TTN_PIN_SPI_MOSI 27 +#define TTN_PIN_SPI_MISO 19 +#define TTN_PIN_NSS 18 +#define TTN_PIN_RXTX TTN_NOT_CONNECTED +#define TTN_PIN_RST 14 +#define TTN_PIN_DIO0 26 +#define TTN_PIN_DIO1 35 + +static TheThingsNetwork ttn; + +const unsigned TX_INTERVAL = 30; +static uint8_t msgData[] = "Hello, world"; + + +void printRFSettings(const char* window, const TTNRFSettings& settings) +{ + int bw = (1 << (static_cast(settings.bandwidth) - 1)) * 125; + int sf = static_cast(settings.spreadingFactor) + 5; + + if (settings.spreadingFactor == kTTNSFNone) + { + printf("%s: not used\n", window); + } + else if (settings.spreadingFactor == kTTNFSK) + { + printf("%s: FSK, BW %dkHz, %d.%d MHz\n", + window, bw, settings.frequency / 1000000, (settings.frequency % 1000000 + 50000) / 100000); + } + else + { + printf("%s: SF%d, BW %dkHz, %d.%d MHz\n", + window, sf, bw, settings.frequency / 1000000, (settings.frequency % 1000000 + 50000) / 100000); + } +} + +void printAllRFSettings() +{ + printRFSettings("TX ", ttn.txSettings()); + printRFSettings("RX1", ttn.rx1Settings()); + printRFSettings("RX2", ttn.rx2Settings()); +} + +void sendMessages(void* pvParameter) +{ + while (1) { + printf("Sending message...\n"); + TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1); + printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n"); + printAllRFSettings(); + + vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000)); + } +} + +void messageReceived(const uint8_t* message, size_t length, port_t port) +{ + printf("Message of %d bytes received on port %d:", length, port); + for (int i = 0; i < length; i++) + printf(" %02x", message[i]); + printf("\n"); +} + +extern "C" void app_main(void) +{ + esp_err_t err; + // Initialize the GPIO ISR handler service + err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM); + ESP_ERROR_CHECK(err); + + // Initialize the NVS (non-volatile storage) for saving and restoring the keys + err = nvs_flash_init(); + ESP_ERROR_CHECK(err); + + // Initialize SPI bus + spi_bus_config_t spi_bus_config; + spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO; + spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI; + spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK; + spi_bus_config.quadwp_io_num = -1; + spi_bus_config.quadhd_io_num = -1; + spi_bus_config.max_transfer_sz = 0; + err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN); + ESP_ERROR_CHECK(err); + + // Configure the SX127x pins + ttn.configurePins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1); + + // The below line can be commented after the first run as the data is saved in NVS + ttn.provision(devEui, appEui, appKey); + + // Register callback for received messages + ttn.onMessage(messageReceived); + + printf("Joining...\n"); + if (ttn.join()) + { + printf("Joined.\n"); + printAllRFSettings(); + xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, nullptr); + } + else + { + printf("Join failed. Goodbye\n"); + } +} diff --git a/include/TheThingsNetwork.h b/include/TheThingsNetwork.h index 592a4bb..f057cb6 100644 --- a/include/TheThingsNetwork.h +++ b/include/TheThingsNetwork.h @@ -29,12 +29,121 @@ typedef uint8_t port_t; */ enum TTNResponseCode { - kTTNErrorTransmissionFailed = -1, - kTTNErrorUnexpected = -10, - kTTNSuccessfulTransmission = 1, - kTTNSuccessfulReceive = 2 + kTTNErrorTransmissionFailed = -1, + kTTNErrorUnexpected = -10, + kTTNSuccessfulTransmission = 1, + kTTNSuccessfulReceive = 2 }; + +/** + * @brief RX/TX window + */ +enum TTNRxTxWindow +{ + /** + * @brief Outside RX/TX window + */ + kTTNIdleWindow = 0, + /** + * @brief Transmission window (up to RX1 window) + */ + kTTNTxWindow = 1, + /** + * @brief Reception window 1 (up to RX2 window) + */ + kTTNRx1Window = 2, + /** + * @brief Reception window 2 + */ + kTTNRx2Window = 3 +}; + + +/** + * @brief Spreading Factor + */ +enum TTNSpreadingFactor +{ + /** + * @brief Unused / undefined spreading factor + */ + kTTNSFNone = 0, + /** + * @brief Frequency Shift Keying (FSK) + */ + kTTNFSK = 1, + /** + * @brief Spreading Factor 7 (SF7) + */ + kTTNSF7 = 2, + /** + * @brief Spreading Factor 8 (SF8) + */ + kTTNSF8 = 3, + /** + * @brief Spreading Factor 9 (SF9) + */ + kTTNSF9 = 4, + /** + * @brief Spreading Factor 10 (SF10) + */ + kTTNSF10 = 5, + /** + * @brief Spreading Factor 11 (SF11) + */ + kTTNSF11 = 6, + /** + * @brief Spreading Factor 12 (SF12) + */ + kTTNSF12 = 7 +}; + + +/** + * @brief Bandwidth + */ +enum TTNBandwidth +{ + /** + * @brief Undefined/unused bandwidth + */ + kTTNBWNone = 0, + /** + * @brief Bandwidth of 125 kHz + */ + kTTNBW125 = 1, + /** + * @brief Bandwidth of 250 kHz + */ + kTTNBW250 = 2, + /** + * @brief Bandwidth of 500 kHz + */ + kTTNBW500 = 3 +}; + + +/** + * @brief RF settings for TX or RX + */ +struct TTNRFSettings +{ + /** + * @brief Spreading Factor (SF) + */ + TTNSpreadingFactor spreadingFactor; + /** + * @brief Bandwidth (BW) + */ + TTNBandwidth bandwidth; + /** + * @brief Frequency, in Hz + */ + uint32_t frequency; +}; + + /** * @brief Callback for recieved messages * @@ -200,7 +309,7 @@ public: * 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' or 'poll'. The callback is called + * 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. * @@ -261,6 +370,36 @@ public: */ void startup(); + /** + * @brief Gets currentRX/TX window + * @return window + */ + TTNRxTxWindow rxTxWindow(); + + /** + * @brief Gets the RF settings for the specified window + * @param window RX/TX windows (valid values are `kTTNTxWindow`, `kTTNRx1Window` and `kTTNRx2Window`) + */ + TTNRFSettings getRFSettings(TTNRxTxWindow window); + + /** + * @brief Gets the RF settings of the last (or ongoing) transmission. + * @return RF settings + */ + TTNRFSettings txSettings(); + + /** + * @brief Gets the RF settings of the last (or ongoing) reception of RX window 1. + * @return RF settings + */ + TTNRFSettings rx1Settings(); + + /** + * @brief Gets the RF settings of the last (or ongoing) reception of RX window 2. + * @return RF settings + */ + TTNRFSettings rx2Settings(); + private: TTNMessageCallback messageCallback; diff --git a/src/TheThingsNetwork.cpp b/src/TheThingsNetwork.cpp index 60d9302..28f0651 100644 --- a/src/TheThingsNetwork.cpp +++ b/src/TheThingsNetwork.cpp @@ -63,10 +63,14 @@ static TTNProvisioning provisioning; #if LMIC_ENABLE_event_logging static TTNLogging* logging; #endif +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() @@ -110,6 +114,7 @@ void TheThingsNetwork::reset() { ttn_hal.enterCriticalSection(); LMIC_reset(); + LMIC_setClockError(MAX_CLOCK_ERROR * 4 / 100); waitingReason = eWaitingNone; ttn_hal.leaveCriticalSection(); } @@ -289,6 +294,32 @@ 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; +} + // --- Callbacks --- @@ -300,6 +331,34 @@ const char *eventNames[] = { LMIC_EVENT_NAME_TABLE__INIT }; // 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 logging->logEvent(event, eventNames[event], 0); #elif CONFIG_LOG_DEFAULT_LEVEL >= 3 @@ -345,3 +404,19 @@ void messageTransmittedCallback(void *userData, int success) 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)); +}