Compare commits

..

59 Commits

Author SHA1 Message Date
Manuel Bl
1057aa09de IRAM_ATTR for get_current_time() 2023-08-07 23:03:22 +02:00
Leif Jakob
61771c0bfa make this function IRAM_ATTR because it's used from IRQ -> otherwise "Cache disabled but cached memory region accessed" if flash is busy 2023-08-07 19:55:31 +02:00
Manuel Bl
89ad39d463 No LMIC clear if it cannot be restored from NVS 2023-06-14 22:07:08 +02:00
Manuel Bl
c32c6b4cd6 Robust struct init for C++ 2023-06-12 22:43:30 +02:00
Manuel Bl
569afe84e9 Weak references for more options to control low speed pins 2023-04-26 10:00:57 +02:00
Leif Jakob
4e0587bb8e use weak references for HAL functions that control low-speed pins - allows override with own symbols 2023-04-25 09:41:37 +02:00
Manuel Bleichenbacher
877784640d Increase version number to 4.2.0-1 2022-09-27 19:20:53 +02:00
Manuel Bleichenbacher
79727fdae2 Merge branch 'FrancoLiberali-master' 2022-09-21 21:00:15 +02:00
Manuel Bleichenbacher
50d45e7fb7 Upgrade to latest LMIC code (v4.2.0-1) 2022-09-21 20:57:23 +02:00
Manuel Bleichenbacher
bbdb44cdd6 Add backward compatibility with v4.x 2022-09-21 20:57:23 +02:00
niki aigner
e7d9caefe1 fix compilation issues 2022-09-21 20:57:23 +02:00
Manuel Bleichenbacher
68d5669833 Upgrade to latest LMIC code (v4.2.0-1) 2022-09-21 20:48:49 +02:00
Manuel Bleichenbacher
46ad09736b Fix for compilation issue in ESP-IDF v5.x 2022-09-21 20:32:46 +02:00
Manuel Bleichenbacher
1b7279518b Add backward compatibility with v4.x 2022-09-21 20:29:52 +02:00
niki aigner
c1517d3c82 fix compilation issues 2022-09-20 18:56:29 +02:00
Franco Liberali
ffcdd12d48 fix configPower for sx1272 2022-07-29 15:24:20 +02:00
Manuel Bleichenbacher
0a7353c146 Assert NSS from SPI callbacks 2022-04-15 12:17:26 +02:00
Manuel Bleichenbacher
54a91a27db Improve SPI robustness (fix incompatibility with ESP32-S2) 2022-03-26 20:06:03 +01:00
Manuel Bleichenbacher
ee8eead337 Improve compatibility with future ESP-IDF versions 2022-01-30 20:27:18 +01:00
Manuel Bl
f06be4c069 Update README with link to Wiki 2021-09-29 21:31:48 +02:00
Manuel Bl
5a7b3b0fcb Fix references in documentation 2021-09-29 19:44:16 +02:00
Manuel Bl
9fd24c65ec Enhanced deep sleep example 2021-09-29 19:40:57 +02:00
Manuel Bl
31b00f05a8 Mention deep sleep in README 2021-09-29 18:57:05 +02:00
Manuel Bl
ba481ceac5 Power off for C++ 2021-09-29 18:02:52 +02:00
Manuel Bl
b626ccb61a Improve robustness of restore from NVS 2021-09-29 17:46:04 +02:00
Manuel Bl
f433e826a7 Save and restore from power off 2021-09-29 16:48:59 +02:00
Manuel Bl
0130928601 Fix ttn_wait_for_idle for MAC cmd processing 2021-09-28 22:38:05 +02:00
Manuel Bl
e71d584fca Save and restore for deep sleep 2021-09-28 17:35:05 +02:00
Manuel Bl
de4297a8f4 Delete Makefiles 2021-09-28 10:17:37 +02:00
Manuel Bl
ee91ccc613 Rename ttn_join() 2021-09-28 10:14:32 +02:00
Manuel Bl
7df10bd6bc Wait for idle 2021-09-26 20:07:57 +02:00
Manuel Bl
bd728887cf Prepare for deep sleep functions 2021-09-26 16:35:42 +02:00
Manuel Bleichenbacher
1df2c50f6f Fix ttn_get_rf_settings() 2021-08-20 20:41:34 +02:00
Manuel Bleichenbacher
8fa345a5d4 Adapted code formatting 2021-07-31 17:03:00 +02:00
Manuel Bleichenbacher
e34dbcb467 Link to new API documentation 2021-07-31 12:51:41 +02:00
Manuel Bleichenbacher
72b878f1d9 Update README with info for v4.0 2021-07-31 12:36:47 +02:00
Manuel Bleichenbacher
17e432d714 Improved API documentation 2021-07-30 23:04:06 +02:00
Manuel Bleichenbacher
5c1db030f2 Improve documentation output 2021-07-29 23:06:13 +02:00
Manuel Bleichenbacher
eb5792fbb9 Default to subband 2 2021-07-28 23:52:10 +02:00
Manuel Bleichenbacher
37d7f8d517 Methods for setting data rate and TX power 2021-07-28 23:48:54 +02:00
Manuel Bleichenbacher
62e829b0d3 Example for C 2021-07-27 23:01:04 +02:00
Manuel Bleichenbacher
6df4b40122 Allow all zeroes for AppEUI/JoinEUI 2021-07-27 22:37:22 +02:00
Manuel Bleichenbacher
7fa43dbbdb Allow disabling subband seletion 2021-07-27 22:08:00 +02:00
Manuel Bleichenbacher
ba908c0b93 Configue sub-band 2021-07-26 22:29:20 +02:00
Manuel Bleichenbacher
281ba52155 C implementation for TheThingsNetwork 2021-07-25 23:53:54 +02:00
Manuel Bleichenbacher
36edf92944 Remove TTNProvisioning.h 2021-07-25 22:15:21 +02:00
Manuel Bleichenbacher
f421db44d7 Convert logging to C 2021-07-25 20:49:02 +02:00
Manuel Bleichenbacher
8e2886db27 Convert hal_esp32 to C 2021-07-25 20:03:16 +02:00
Manuel Bleichenbacher
973a7c41c8 Convert TTNProvisioning to C 2021-07-25 17:00:08 +02:00
Manuel Bleichenbacher
99bab17d4b Upgrade to mcci-catena/arduino-lmic 4.0.1-pre 2021-07-25 14:39:11 +02:00
Manuel Bl
1913840679 Prepare for release 3.3 2021-01-05 21:57:24 +01:00
Manuel Bl
5c695fd223 Upgrade to version 3.3 of arduino-lmic 2021-01-05 21:57:11 +01:00
Manuel Bl
3086b1bb58 Static word-aligned buffer for SPI 2021-01-05 21:26:36 +01:00
Manuel Bl
f57c505d7e Merge branch 'iram_isr_fix' of https://github.com/vladcebo/ttn-esp32 into vladcebo-iram_isr_fix 2021-01-05 21:04:57 +01:00
Manuel Bl
ba22c5a745 Fix excessiv writing of keys to NVS 2021-01-05 20:55:14 +01:00
Cebotari Vladislav
5b22f228da
Prevent hal_ticks() from going into .text region
This will fix an issue when the flash is disabled (due to OTA update for example) and the cpu crashes with cache disabled access from ISR (when TTN executes)
2021-01-02 17:32:30 +00:00
Markus Frey
84b2f88f0c Fix excessive writing of keys to NVS 2020-09-27 16:21:04 +02:00
Manuel Bl
fffe7bd8b3 Improve documentation 2020-08-02 19:53:02 +02:00
Manuel Bl
bb41c4960f README updated for 3.2.0 2020-08-02 10:19:58 +02:00
94 changed files with 9654 additions and 2334 deletions

3
.clang-format Normal file
View File

@ -0,0 +1,3 @@
BasedOnStyle: Microsoft
IndentWidth: 4
ColumnLimit: 120

View File

@ -22,14 +22,15 @@
"${workspaceRoot}/examples/send_recv/build/include",
"${workspaceRoot}/include",
"${workspaceRoot}/src"
],
],
"defines": [
"_DEBUG"
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++11",
"intelliSenseMode": "clang-x64"
"intelliSenseMode": "clang-x64",
"compileCommands": "${workspaceFolder}/examples/hello_world/build/compile_commands.json"
}
],
"version": 4

View File

@ -1,11 +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"
"lmic.h": "c",
"ttn_provisioning.h": "c",
"lorabase.h": "c"
}
}

View File

@ -9,6 +9,10 @@ set(COMPONENT_ADD_INCLUDEDIRS
)
set(COMPONENT_REQUIRES
nvs_flash
mbedtls
driver
esp_event
esp_timer
)
register_component()

View File

@ -51,7 +51,7 @@ endchoice
config TTN_SPI_FREQ
int "SPI frequency (in Hz)"
default 10000000
default 2000000
help
SPI frequency to communicate between ESP32 and SX127x radio chip

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018 Manuel Bleichenbacher
Copyright (c) 2018-2021 Manuel Bleichenbacher
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -2,15 +2,30 @@
**The Things Network device library for ESP-IDF (ESP32) supporting devices with Semtech SX127x chips**
This ESP32 component provides LoRaWAN communication with [The Things Network](https://www.thethingsnetwork.org/). It supports
This ESP32 component provides LoRaWAN communication with
[The Things Network](https://www.thethingsnetwork.org/). It supports:
- OTAA (over-the-air activation)
- uplink and downlink messages
- saving the EUIs and key in non-volatile memory
- [AT commands](https://github.com/manuelbl/ttn-esp32/wiki/AT-Commands) for provisioning EUIs and key (so the same code can be flashed to several devices)
- support for regions Eurpe, North America, Australia, Asia and India
- deep sleep and power off without the need for rejoining
- [AT commands](https://github.com/manuelbl/ttn-esp32/wiki/AT-Commands) for provisioning EUIs and key
(so the same code can be flashed to several devices)
- support for regions Europe, North and South America, Australia, Korea, Asia and India
- C and C++ API
The library is based on the LMIC library from IBM (specifically the well-maintained version by MCCI
see their [GitHub repository](https://github.com/mcci-catena/arduino-lmic)) and provides a high-level API specifically targeted at The Things Network.
## New in version 4.x
- Support for deep sleep and power off (see [Deep Sleep and Power Off](https://github.com/manuelbl/ttn-esp32/wiki/Deep-Sleep-and-Power-Off))
- Verified compatibility with ESP-IDF v4.3 and 5.0
- Upgraded underlying library mcci-catena/arduino-lmic to v4.2.0-1
- C API
- Support for sub-bands
- Dropped support for *Makefile* builds
The library is based on the LMIC library from IBM (specifically the version maintained by MCCI see their [GitHub repository](https://github.com/mcci-catena/arduino-lmic)) and provides a high-level API specifically targeted at The Things Network.
## Get Started
@ -18,11 +33,11 @@ Follow the detailed [Get Started Guide](https://github.com/manuelbl/ttn-esp32/wi
## Supported Boards
All boards with Semtech SX127x chips, RFM9x and compatibles are supported. It includes boards from ttgo, Heltec and HopeRF.
All boards with Semtech SX127x chips, RFM9x and compatibles are supported. It includes boards from ttgo, Heltec and HopeRF. For several of them, the [Pin Configuration](https://github.com/manuelbl/ttn-esp32/wiki/Boards-and-Pins) is described in detail.
## API Documentation
See the Wiki page: [API Documentation](https://github.com/manuelbl/ttn-esp32/wiki/API-Documentation)
See [API Documentation](https://codecrete.net/ttn-esp32/) for both the C ad C++ API
## More information

View File

@ -1,2 +0,0 @@
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := src src/aes src/hal src/lmic

5
doc/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
xml/
html/
api.md
node_modules/
ttn-esp32.zip

2619
doc/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

21
doc/doc.dox Normal file
View File

@ -0,0 +1,21 @@
/*!
\mainpage ttn-esp32 API Documentation
ttn-esp32 is a C and C++ library for the ESP-IDF (ESP32) framework
for The Things Network library end devices using Semtech SX127x chips.
\section cplusplus C++ API
\ref TheThingsNetwork class
\ref TTNRFSettings struct
\section c C API
\ref c_api
\ref ttn_rf_settings_t struct
*/

View File

@ -0,0 +1,40 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
@media screen and (min-width: 768px) {
#MSearchBox {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px);
}
#MSearchField {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height));
}
}

View File

@ -0,0 +1,107 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
html {
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
* Make sure it is wide enought to contain the page title (logo + title + version)
*/
--side-nav-fixed-width: 340px;
--menu-display: none;
--top-height: 120px;
}
@media screen and (min-width: 768px) {
html {
--searchbar-background: var(--page-background-color);
}
#side-nav {
min-width: var(--side-nav-fixed-width);
max-width: var(--side-nav-fixed-width);
top: var(--top-height);
}
#nav-tree, #side-nav {
height: calc(100vh - var(--top-height)) !important;
}
#nav-tree {
padding: 0;
}
#top {
display: block;
border-bottom: none;
height: var(--top-height);
margin-bottom: calc(0px - var(--top-height));
max-width: var(--side-nav-fixed-width);
background: var(--side-nav-background);
}
#main-nav {
float: left;
padding-right: 0;
}
.ui-resizable-handle {
cursor: default;
width: 1px !important;
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
}
#nav-path {
position: fixed;
right: 0;
left: var(--side-nav-fixed-width);
bottom: 0;
width: auto;
}
#doc-content {
height: calc(100vh - 31px) !important;
padding-bottom: calc(3 * var(--spacing-large));
padding-top: calc(var(--top-height) - 80px);
box-sizing: border-box;
margin-left: var(--side-nav-fixed-width) !important;
}
#MSearchBox {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
}
#MSearchField {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
}
#MSearchResultsWindow {
left: var(--spacing-medium) !important;
right: auto;
}
}

File diff suppressed because it is too large Load Diff

5
doc/generate.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
rm -rf html ttn-esp32.zip
doxygen Doxyfile
cd html
zip -r ../ttn-esp32.zip .

View File

@ -0,0 +1,10 @@
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 "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(deep_sleep)

View File

@ -0,0 +1,131 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Sample program sending messages and going to deep sleep in-between.
*******************************************************************************/
#include "driver/gpio.h"
#include "esp_event.h"
#include "esp_sleep.h"
#include "freertos/FreeRTOS.h"
#include "nvs_flash.h"
#include <stdio.h>
#include <string.h>
#include "TheThingsNetwork.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
// Go to Components / The Things Network, select the appropriate values and save.
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#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 = 60;
// This counter value is retained (in RTC memory) during deep sleep
RTC_DATA_ATTR int counter_in_rtc_mem;
void messageReceived(const uint8_t *message, size_t length, ttn_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;
memset(&spi_bus_config, 0, sizeof(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;
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);
// ttn.setAdrEnabled(false);
// ttn.setDataRate(kTTNDataRate_US915_SF7);
// ttn.setMaxTxPower(14);
if (ttn.resumeAfterDeepSleep())
{
printf("Resumed from deep sleep.\n");
}
else
{
printf("Joining...\n");
if (ttn.join())
{
printf("Joined.\n");
}
else
{
printf("Join failed. Goodbye\n");
return;
}
}
printf("Sending message...\n");
counter_in_rtc_mem++;
char message[20];
sprintf(message, "Hello %d", counter_in_rtc_mem);
TTNResponseCode res = ttn.transmitMessage((uint8_t *)message, strlen(message));
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
// Wait until TTN communication is idle and save state
ttn.waitForIdle();
ttn.prepareForDeepSleep();
// Schedule wake up
esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000LL);
printf("Going to deep sleep...\n");
esp_deep_sleep_start();
}

View File

@ -0,0 +1,10 @@
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 "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(deep_sleep)

View File

@ -0,0 +1,4 @@
idf_component_register(
SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES ttn-esp32)

View File

@ -0,0 +1,135 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Sample program (in C) sending messages and going to deep sleep in-between.
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include <stdio.h>
#include <string.h>
#include "ttn.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
// Go to Components / The Things Network, select the appropriate values and save.
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#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
#define TX_INTERVAL 60 // in sec
// This counter value is retained (in RTC memory) during deep sleep
RTC_DATA_ATTR int counter_in_rtc_mem;
void messageReceived(const uint8_t* message, size_t length, ttn_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");
}
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 = {
.miso_io_num = TTN_PIN_SPI_MISO,
.mosi_io_num = TTN_PIN_SPI_MOSI,
.sclk_io_num = TTN_PIN_SPI_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1
};
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
ESP_ERROR_CHECK(err);
// Initialize TTN
ttn_init();
// Configure the SX127x pins
ttn_configure_pins(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_on_message(messageReceived);
// ttn_set_adr_enabled(false);
// ttn_set_data_rate(TTN_DR_US915_SF7);
// ttn_set_max_tx_pow(14);
if (ttn_resume_after_deep_sleep()) {
printf("Resumed from deep sleep.\n");
} else {
printf("Joining...\n");
if (ttn_join())
{
printf("Joined.\n");
}
else
{
printf("Join failed. Goodbye\n");
return;
}
}
printf("Sending message...\n");
counter_in_rtc_mem++;
char message[20];
sprintf(message, "Hello %d", counter_in_rtc_mem);
ttn_response_code_t res = ttn_transmit_message((uint8_t *)message, strlen(message), 1, false);
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
// Wait until TTN communication is idle and save state
ttn_wait_for_idle();
ttn_prepare_for_deep_sleep();
// Schedule wake up
esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000LL);
printf("Going to deep sleep...\n");
esp_deep_sleep_start();
}

View File

@ -5,4 +5,6 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(hello_world)

View File

@ -1,5 +0,0 @@
PROJECT_NAME := hello_world
EXTRA_COMPONENT_DIRS := $(abspath ../..)
include $(IDF_PATH)/make/project.mk

View File

@ -14,24 +14,27 @@
#include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include <string.h>
#include "TheThingsNetwork.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'make menuconfig'.
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py 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 hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// Copy the below two lines from bottom of the same page
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST HSPI_HOST
#define TTN_SPI_DMA_CHAN 1
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19
@ -58,7 +61,7 @@ void sendMessages(void* pvParameter)
}
}
void messageReceived(const uint8_t* message, size_t length, port_t port)
void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
{
printf("Message of %d bytes received on port %d:", length, port);
for (int i = 0; i < length; i++)
@ -79,12 +82,10 @@ extern "C" void app_main(void)
// Initialize SPI bus
spi_bus_config_t spi_bus_config;
memset(&spi_bus_config, 0, sizeof(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);
@ -97,6 +98,10 @@ extern "C" void app_main(void)
// Register callback for received messages
ttn.onMessage(messageReceived);
// ttn.setAdrEnabled(false);
// ttn.setDataRate(kTTNDataRate_US915_SF7);
// ttn.setMaxTxPower(14);
printf("Joining...\n");
if (ttn.join())
{

View File

@ -0,0 +1,10 @@
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 "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(hello_world)

View File

@ -0,0 +1,4 @@
idf_component_register(
SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES ttn-esp32)

View File

@ -0,0 +1,117 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Sample program for C showing how to send and receive messages.
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "ttn.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
// Go to Components / The Things Network, select the appropriate values and save.
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#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
#define TX_INTERVAL 30
static uint8_t msgData[] = "Hello, world";
void sendMessages(void* pvParameter)
{
while (1) {
printf("Sending message...\n");
ttn_response_code_t res = ttn_transmit_message(msgData, sizeof(msgData) - 1, 1, false);
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
}
}
void messageReceived(const uint8_t* message, size_t length, ttn_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");
}
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 = {
.miso_io_num = TTN_PIN_SPI_MISO,
.mosi_io_num = TTN_PIN_SPI_MOSI,
.sclk_io_num = TTN_PIN_SPI_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1
};
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
ESP_ERROR_CHECK(err);
// Initialize TTN
ttn_init();
// Configure the SX127x pins
ttn_configure_pins(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_on_message(messageReceived);
// ttn_set_adr_enabled(false);
// ttn_set_data_rate(TTN_DR_US915_SF7);
// ttn_set_max_tx_pow(14);
printf("Joining...\n");
if (ttn_join())
{
printf("Joined.\n");
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, NULL);
}
else
{
printf("Join failed. Goodbye\n");
}
}

View File

@ -1,5 +0,0 @@
PROJECT_NAME := mac_address
EXTRA_COMPONENT_DIRS := $(abspath ../..)
include $(IDF_PATH)/make/project.mk

View File

@ -14,21 +14,28 @@
#include "driver/gpio.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include <string.h>
#include "TheThingsNetwork.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'make menuconfig'.
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
// Go to Components / The Things Network, select the appropriate values and save.
// Copy the below two lines from the bottom
// of your device's overview page in the TTN console.
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST HSPI_HOST
#define TTN_SPI_DMA_CHAN 1
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19
@ -68,12 +75,10 @@ extern "C" void app_main(void)
// Initialize SPI bus
spi_bus_config_t spi_bus_config;
memset(&spi_bus_config, 0, sizeof(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);

View File

@ -1,5 +0,0 @@
PROJECT_NAME := monitoring
EXTRA_COMPONENT_DIRS := $(abspath ../..)
include $(IDF_PATH)/make/project.mk

View File

@ -14,24 +14,29 @@
#include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include <string.h>
#include "TheThingsNetwork.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'make menuconfig'.
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py 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 hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// Copy the below two lines from bottom of the same page
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST HSPI_HOST
#define TTN_SPI_DMA_CHAN 1
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19
@ -58,12 +63,12 @@ void printRFSettings(const char* window, const TTNRFSettings& settings)
}
else if (settings.spreadingFactor == kTTNFSK)
{
printf("%s: FSK, BW %dkHz, %d.%d MHz\n",
printf("%s: FSK, BW %dkHz, %ld.%ld MHz\n",
window, bw, settings.frequency / 1000000, (settings.frequency % 1000000 + 50000) / 100000);
}
else
{
printf("%s: SF%d, BW %dkHz, %d.%d MHz\n",
printf("%s: SF%d, BW %dkHz, %ld.%ld MHz\n",
window, sf, bw, settings.frequency / 1000000, (settings.frequency % 1000000 + 50000) / 100000);
}
}
@ -87,7 +92,7 @@ void sendMessages(void* pvParameter)
}
}
void messageReceived(const uint8_t* message, size_t length, port_t port)
void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
{
printf("Message of %d bytes received on port %d:", length, port);
for (int i = 0; i < length; i++)
@ -109,12 +114,10 @@ extern "C" void app_main(void)
// Initialize SPI bus
spi_bus_config_t spi_bus_config;
memset(&spi_bus_config, 0, sizeof(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);

View File

@ -5,4 +5,6 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
project(shutdown)
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(power_off)

View File

@ -0,0 +1,4 @@
idf_component_register(
SRCS "main.cpp"
INCLUDE_DIRS "."
REQUIRES ttn-esp32)

View File

@ -1,98 +1,56 @@
/*******************************************************************************
*
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018 Manuel Bleichenbacher
*
*
* Copyright (c) 2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Sample program showing how to send and receive messages.
* Sample program sending messages and going to deep sleep in-between.
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "driver/gpio.h"
#include "esp_event.h"
#include "esp_sleep.h"
#include "freertos/FreeRTOS.h"
#include "nvs_flash.h"
#include <string.h>
#include "TheThingsNetwork.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'make menuconfig'.
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py 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 hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// Copy the below two lines from bottom of the same page
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
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
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#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;
const unsigned TX_INTERVAL = 60;
static uint8_t msgData[] = "Hello, world";
bool join()
{
printf("Joining...\n");
if (ttn.join())
{
printf("Joined.\n");
return true;
}
else
{
printf("Join failed. Goodbye\n");
return false;
}
}
void sendMessages(void* pvParameter)
{
while (1) {
// Send 2 messages
for (int i = 0; i < 2; i++)
{
printf("Sending message...\n");
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
}
// shutdown
ttn.shutdown();
// go to sleep
printf("Sleeping for 30s...\n");
vTaskDelay(pdMS_TO_TICKS(30000));
// startup
ttn.startup();
// join again
if (!join())
return;
}
}
void messageReceived(const uint8_t* message, size_t length, port_t port)
void messageReceived(const uint8_t *message, size_t length, ttn_port_t port)
{
printf("Message of %d bytes received on port %d:", length, port);
for (int i = 0; i < length; i++)
@ -106,19 +64,17 @@ extern "C" void app_main(void)
// 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;
memset(&spi_bus_config, 0, sizeof(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);
@ -128,10 +84,42 @@ extern "C" void app_main(void)
// 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);
if (join())
// ttn.setAdrEnabled(false);
// ttn.setDataRate(kTTNDataRate_US915_SF7);
// ttn.setMaxTxPower(14);
if (ttn.resumeAfterPowerOff(60))
{
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, nullptr);
printf("Resumed from power off.\n");
}
else
{
printf("Joining...\n");
if (ttn.join())
{
printf("Joined.\n");
}
else
{
printf("Join failed. Goodbye\n");
return;
}
}
printf("Sending message...\n");
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
// Wait until TTN communication is idle and save state
ttn.waitForIdle();
ttn.prepareForPowerOff();
printf("Power off...\n");
// Do whatever is needed to power off the device.
// For testing, press reset button to simulate power cycle.
while (true)
vTaskDelay(pdMS_TO_TICKS(1000));
}

View File

@ -0,0 +1,10 @@
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 "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(power_off)

View File

@ -0,0 +1,4 @@
idf_component_register(
SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES ttn-esp32)

View File

@ -0,0 +1,128 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Sample program (in C) sending messages and powering off in-between.
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "ttn.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
// Go to Components / The Things Network, select the appropriate values and save.
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#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
#define TX_INTERVAL 60 // in sec
static uint8_t msgData[] = "Hello, world";
void messageReceived(const uint8_t* message, size_t length, ttn_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");
}
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 = {
.miso_io_num = TTN_PIN_SPI_MISO,
.mosi_io_num = TTN_PIN_SPI_MOSI,
.sclk_io_num = TTN_PIN_SPI_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1
};
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
ESP_ERROR_CHECK(err);
// Initialize TTN
ttn_init();
// Configure the SX127x pins
ttn_configure_pins(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_on_message(messageReceived);
// ttn_set_adr_enabled(false);
// ttn_set_data_rate(TTN_DR_US915_SF7);
// ttn_set_max_tx_pow(14);
if (ttn_resume_after_power_off(60)) {
printf("Resumed from power off.\n");
} else {
printf("Joining...\n");
if (ttn_join())
{
printf("Joined.\n");
}
else
{
printf("Join failed. Goodbye\n");
return;
}
}
printf("Sending message...\n");
ttn_response_code_t res = ttn_transmit_message(msgData, sizeof(msgData) - 1, 1, false);
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
// Wait until TTN communication is idle and save state
ttn_wait_for_idle();
ttn_prepare_for_power_off();
printf("Power off...\n");
// Do whatever is needed to power off the device.
// For testing, press reset button to simulate power cycle.
while (true)
vTaskDelay(pdMS_TO_TICKS(1000));
}

View File

@ -1,5 +0,0 @@
PROJECT_NAME := provisioning
EXTRA_COMPONENT_DIRS := $(abspath ../..)
include $(IDF_PATH)/make/project.mk

View File

@ -14,16 +14,19 @@
#include "driver/gpio.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include <string.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.
// Pins and other resources
#define TTN_SPI_HOST HSPI_HOST
#define TTN_SPI_DMA_CHAN 1
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19
@ -63,12 +66,11 @@ extern "C" void app_main(void)
// Initialize SPI bus
spi_bus_config_t spi_bus_config;
memset(&spi_bus_config, 0, sizeof(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);

View File

@ -1,5 +0,0 @@
PROJECT_NAME := shutdown
EXTRA_COMPONENT_DIRS := $(abspath ../..)
include $(IDF_PATH)/make/project.mk

File diff suppressed because it is too large Load Diff

822
include/ttn.h Normal file
View File

@ -0,0 +1,822 @@
/*******************************************************************************
*
* 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.
*******************************************************************************/
/**
* @defgroup c_api C API
*/
#ifndef TTN_C_H
#define TTN_C_H
#include "driver/spi_master.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @addtogroup c_api
*
* @{
*/
/**
* @brief Constant for indicating that a pin is not connected
*/
#define TTN_NOT_CONNECTED 0xff
/**
* @brief Integer data type for specifiying the port of an uplink or downlink message.
*/
typedef uint8_t ttn_port_t;
/**
* @brief Response codes
*/
typedef enum
{
/** @brief Transmission failed error */
TTN_ERROR_TRANSMISSION_FAILED = -1,
/** @brief Unexpected or internal error */
TTN_ERROR_UNEXPECTED = -10,
/** @brief Successful transmission of an uplink message */
TTN_SUCCESSFUL_TRANSMISSION = 1,
/** @brief Successful receipt of a downlink message */
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_BW_125 = 1,
/**
* @brief Bandwidth of 250 kHz
*/
TTN_BW_250 = 2,
/**
* @brief Bandwidth of 500 kHz
*/
TTN_BW_500 = 3
} ttn_bandwidth_t;
/**
* @brief Data Rate
*
* Note that the spreading factor, bandwidth, bit rate and maximum message
* size associated with each data rate depends on the region.
*/
typedef enum
{
/**
* @brief Data rate for region AS923 using SF12 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF12 = 0,
/**
* @brief Data rate for region AS923 using SF11 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF11 = 1,
/**
* @brief Data rate for region AS923 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF10 = 2,
/**
* @brief Data rate for region AS923 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF9 = 3,
/**
* @brief Data rate for region AS923 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF8 = 4,
/**
* @brief Data rate for region AS923 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF7_BW125 = 5,
/**
* @brief Data rate for region AS923 using SF7 and 250 kHz bandwidth.
*/
TTN_DR_AS923_SF7_BW250 = 6,
/**
* @brief Data rate for region AS923 using FSK and 50 kpbs.
*/
TTN_DR_AS923_FSK = 7,
/**
* @brief Data rate for region AU915 using SF12 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF12 = 0,
/**
* @brief Data rate for region AU915 using SF11 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF11 = 1,
/**
* @brief Data rate for region AU915 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF10 = 2,
/**
* @brief Data rate for region AU915 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF9 = 3,
/**
* @brief Data rate for region AU915 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF8 = 4,
/**
* @brief Data rate for region AU915 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF7 = 5,
/**
* @brief Data rate for region AU915 using SF8 and 500 kHz bandwidth.
*/
TTN_DR_AU915_SF8_BW500 = 6,
/**
* @brief Data rate for region AU915 using SF12 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF12_BW500 = 8,
/**
* @brief Data rate for region AU915 using SF11 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF11_BW500 = 9,
/**
* @brief Data rate for region AU915 using SF10 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF10_BW500 = 10,
/**
* @brief Data rate for region AU915 using SF9 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF9_BW500 = 11,
/**
* @brief Data rate for region AU915 using SF8 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF8_BW500_DR12 = 12,
/**
* @brief Data rate for region AU915 using SF7 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF7_BW500 = 13,
/**
* @brief Data rate for region EU868 using SF12 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF12 = 0,
/**
* @brief Data rate for region EU868 using SF11 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF11 = 1,
/**
* @brief Data rate for region EU868 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF10 = 2,
/**
* @brief Data rate for region EU868 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF9 = 3,
/**
* @brief Data rate for region EU868 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF8 = 4,
/**
* @brief Data rate for region EU868 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF7_BW125 = 5,
/**
* @brief Data rate for region EU868 using SF7 and 250 kHz bandwidth.
*/
TTN_DR_EU868_SF7_BW250 = 6,
/**
* @brief Data rate for region EU868 using FSK and 50 kpbs.
*/
TTN_DR_EU868_FSK = 7,
/**
* @brief Data rate for region IN866 using SF12 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF12 = 0,
/**
* @brief Data rate for region IN866 using SF11 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF11 = 1,
/**
* @brief Data rate for region IN866 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF10 = 2,
/**
* @brief Data rate for region IN866 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF9 = 3,
/**
* @brief Data rate for region IN866 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF8 = 4,
/**
* @brief Data rate for region IN866 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF7 = 5,
/**
* @brief Data rate for region IN866 using FSK and 50 kpbs.
*/
TTN_DR_IN866_FSK = 7,
/**
* @brief Data rate for region KR920 using SF12 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF12 = 0,
/**
* @brief Data rate for region KR920 using SF11 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF11 = 1,
/**
* @brief Data rate for region KR920 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF10 = 2,
/**
* @brief Data rate for region KR920 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF9 = 3,
/**
* @brief Data rate for region KR920 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF8 = 4,
/**
* @brief Data rate for region KR920 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF7 = 5,
/**
* @brief Data rate for region US915 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_US915_SF10 = 0,
/**
* @brief Data rate for region US915 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_US915_SF9 = 1,
/**
* @brief Data rate for region US915 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_US915_SF8 = 2,
/**
* @brief Data rate for region US915 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_US915_SF7 = 3,
/**
* @brief Data rate for region US915 using SF8 and 500 kHz bandwidth.
*/
TTN_DR_US915_SF8_BW500 = 4,
/**
* @brief Data rate for region US915 using SF12 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF12_BW500 = 8,
/**
* @brief Data rate for region US915 using SF11 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF11_BW500 = 9,
/**
* @brief Data rate for region US915 using SF10 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF10_BW500 = 10,
/**
* @brief Data rate for region US915 using SF9 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF9_BW500 = 11,
/**
* @brief Data rate for region US915 using SF8 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF8_BW500_DR12 = 12,
/**
* @brief Data rate for region US915 using SF7 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF7_BW500 = 13,
/**
* @brief Default data rate for joining.
*/
TTN_DR_JOIN_DEFAULT = 255
} ttn_data_rate_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 Initializes The Things Network device instance.
*
* Call this function once at the start of the program and before all other TTN functions.
*/
void ttn_init(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.
*
* Call this function after @ref ttn_init() and before all other TTN functions.
*
* @param spi_host The SPI bus/peripherial to use (`SPI1_HOST`, `SPI2_HOST`, `SPI3_HOST`, `FSPI_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 (@ref TTN_NOT_CONNECTED if not
* connected)
* @param rst The GPIO pin number connected to the radio chip's RST pin (@ref 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 frequency sub-band to be used.
*
* For regions with sub-bands (USA, Australia), sets the sub-band to be used for uplink communication.
* For other regions, this function has no effect.
*
* The sub-band must be set before joining or sending the first message.
*
* If not set, it defaults to sub-band 2 as defined by TTN.
*
* @param band band (0 for all bands, or value between 1 and 8)
*/
void ttn_set_subband(int band);
/**
* @brief Sets the keys needed to activate the device via OTAA, without activating it.
*
* The provided DevEUI, AppEUI/JoinEUI and AppKey are saved in non-volatile memory. Before
* this function is called, `nvs_flash_init()` must have been called once.
*
* In order to reduce flash wear, this function detects if the keys have not changed
* and will not write them again.
*
* Call @ref ttn_join() to activate the device.
*
* @param dev_eui DevEUI (16 character string with hexadecimal data)
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param app_key AppKey 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 keys needed to activate the device via OTAA, without activating it.
*
* The provided DevEUI, AppEUI/JoinEUI and AppKey are only stored in RAM and will be lost
* when the device is powered off or put in deep sleep.
*
* Call @ref ttn_join() to activate the device.
*
* @param dev_eui DevEUI (16 character string with hexadecimal data)
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param app_key AppKey of the device (32 character string with hexadecimal data)
* @return `true` if the provisioning was successful, `false` if the provisioning failed
*/
bool ttn_provision_transiently(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 DevEUI
* and without activating it.
*
* The generated DevEUI and the provided AppEUI/JoinEUI and AppKey are saved in non-volatile memory. Before
* this function is called, `nvs_flash_init` must have been called once.
*
* In order to reduce flash wear, this function detects if the keys have not changed
* and will not write them again.
*
* The DevEUI is generated by retrieving the ESP32's WiFi MAC address and expanding it into a DevEUI
* 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 DevEUI field in the TTN console.
*
* Generating the DevEUI from the MAC address allows to flash the same AppEUI/JoinEUI and AppKey to a batch of
* devices. However, using the same AppKey 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 @ref ttn_join() to activate.
*
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param app_key AppKey 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 DevEUI, AppEUI/JoinEUI and AppKey have been provisioned
* by the provisioning task.
*
* If the device has already been provisioned (stored data in NVS, call of provision()
* or call of @ref ttn_join_with_keys(), this function immediately returns.
*/
void ttn_wait_for_provisioning(void);
/**
* @brief Activates the device via OTAA using previously provisioned keys.
*
* The DevEUI, AppEUI/JoinEUI and AppKey must have already been provisioned by a call
* to @ref ttn_provision() or @ref ttn_provision_with_mac().
* Before this function is called, `nvs_flash_init()` must have been called once.
*
* The RF module is initialized and the TTN background task is started.
*
* 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(void);
/**
* @brief Activates the device via OTAA using the provided keys.
*
* For the activation, the provided DevEUI, AppEUI/JoinEUI and AppKey are used.
* They are NOT saved in non-volatile memory.
*
* The RF module is initialized and the TTN background task is started.
*
* The function blocks until the activation has completed or failed.
*
* @param dev_eui DevEUI (16 character string with hexadecimal data)
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param app_key AppKey of the device (32 character string with hexadecimal data)
* @return `true` if the activation was succesful, `false` if the activation failed
*/
bool ttn_join_with_keys(const char *dev_eui, const char *app_eui, const char *app_key);
/**
* @brief Resumes TTN communication after deep sleep.
*
* The communcation state is restored from data previously saved in RTC memory.
* The RF module and the TTN background task are started.
*
* This function is called instead of @ref ttn_join_with_keys() or @ref ttn_join()
* to continue with the established communication and to avoid a further join procedure.
*
* @return `true` if the device was able to resume, `false` otherwise.
*/
bool ttn_resume_after_deep_sleep(void);
/**
* @brief Resumes TTN communication after power off.
*
* The communcation state is restored from data previously saved in NVS (non-volatile storage).
* The RF module and the TTN background task are started.
*
* This function is called instead of @ref ttn_join_with_keys() or @ref ttn_join()
* to continue with the established communication and to avoid a further join procedure.
*
* In order to advance the clock, the estimated duration the device was powered off has to
* be specified. As the exact duration is probably not known, an estimation of the shortest
* duration between power-off and next power-on can be used instead.
*
* If the device has access to the real time, set the system time (using `settimeofday()`)
* before calling this function (and before @ref ttn_join()) and pass 0 for `off_duration`.
*
* Before this function is called, `nvs_flash_init()` must have been called once.
*
* @param off_duration duration the device was powered off (in minutes)
* @return `true` if the device was able to resume, `false` otherwise.
*/
bool ttn_resume_after_power_off(int off_duration);
/**
* @brief Stops all activies and prepares for deep sleep.
*
* This function is called before entering deep sleep. It saves the current
* communication state in RTC memory and shuts down the RF module and the
* TTN background task.
*
* It neither clears the provisioned keys nor the configured pins
* but they will be lost if the device goes into deep sleep.
*
* Before calling this function, use @ref ttn_busy_duration() to check
* that the TTN device is idle and ready to go to deep sleep.
*
* To restart communication, @ref ttn_resume_after_deep_sleep() must be called.
*/
void ttn_prepare_for_deep_sleep(void);
/**
* @brief Stops all activies and prepares for power off.
*
* This function is called before powering off the device. It saves the current
* communication state in NVS (non-volatile storage) and shuts down the RF module
* and the TTN background task.
*
* It neither clears the provisioned keys nor the configured pins
* but they will be lost if the device is powered off.
*
* Before calling this function, use @ref ttn_busy_duration() to check
* that the TTN device is idle and ready to be powered off.
*
* To restart communication, @ref ttn_resume_after_power_off(int) must be called.
*
* Before this function is called, `nvs_flash_init()` must have been called once.
*/
void ttn_prepare_for_power_off(void);
/**
* @brief Waits until the TTN device is idle.
*
* If the TTN device is idle, the ESP32 can go into deep sleep mode
* or be powered off without disrupting an on-going communication.
*/
void ttn_wait_for_idle(void);
/**
* @brief Returns the minimum duration the TTN device will be busy.
*
* This function can be called to check whether the TTN device is
* still involved in communication or ready to go to deep sleep or
* to be powered off.
*
* If it returns 0, the TTN communication is idle and the device can go
* to deep sleep or can be powered off.
*
* If it returns a value different from 0, the value indicates the duration
* the device will be certainly busy. After that time, this function must be
* called again. It might still return a value different from 0.
*
* @return busy duration (in FreeRTOS ticks)
*/
TickType_t ttn_busy_duration(void);
/**
* @brief Stops all activies.
*
* This function shuts down the RF module and the TTN background task. It neither clears the
* provisioned keys nor the configured pins. The currentat device state (and activation)
* are lost.
*
* To restart communication, @ref ttn_join_with_keys() and @ref ttn_join() must be called.
*/
void ttn_shutdown(void);
/**
* @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 @ref TTN_SUCCESSFUL_TRANSMISSION for successful transmission, @ref TTN_ERROR_TRANSMISSION_FAILED for
* failed transmission, @ref TTN_ERROR_UNEXPECTED 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 @ref ttn_transmit_message(). The callback is called
* in the task that called this function and it occurs before this function
* returns control to the caller.
*
* @param callback the callback function
*/
void ttn_on_message(ttn_message_cb callback);
/**
* @brief Checks if DevEUI, AppEUI/JoinEUI and AppKey have been stored in non-volatile storage
* or have been provided by a call to @ref ttn_join_with_keys() or to @ref ttn_provision_transiently().
*
* @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 Sets the transmission data rate (i.e. the data rate for uplink messages).
*
* If ADR is enabled, it's is used as the initial data rate and later adjusted depending
* on the RF conditions. If ADR is disabled, it is used for all uplink messages.
*
* @param data_rate data rate (use constants of enum @ref ttn_data_rate_t)
*/
void ttn_set_data_rate(ttn_data_rate_t data_rate);
/**
* @brief Sets the maximum power for transmission
*
* The power is specified in dBm and sets the power emitted by the radio.
* If the antenna has a gain, it must be substracted from the specified value to
* achieve the correct transmission power.
*
* @param tx_pow power, in dBm
*/
void ttn_set_max_tx_pow(int tx_pow);
/**
* @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 window (valid values are @ref TTN_WINDOW_TX, @ref TTN_WINDOW_RX1 and @ref TTN_WINDOW_RX2
*/
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

View File

@ -13,7 +13,7 @@
"url": "https://github.com/manuelbl/ttn-esp32.git",
"branch": "master"
},
"version": "3.2.0",
"version": "4.2.0-1",
"license": "MIT License",
"export": {
"include": [

View File

@ -1,390 +0,0 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2019 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Circular buffer for detailed logging without affecting LMIC timing.
*******************************************************************************/
#if LMIC_ENABLE_event_logging
#include <string.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "lmic/lmic.h"
#include "TTNLogging.h"
#define NUM_RINGBUF_MSG 50
static const char* const TAG = "lmic";
static TTNLogging ttnLog;
/**
* @brief Message structure used in ring buffer
*
* The structure is sent from the LMIC task to the logging task.
*/
struct TTNLogMessage {
const char* message;
uint32_t datum;
ev_t event;
ostime_t time;
ostime_t txend;
ostime_t globalDutyAvail;
u4_t freq;
u2_t opmode;
u2_t fcntDn;
u2_t fcntUp;
u2_t rxsyms;
rps_t rps;
u1_t txChnl;
u1_t datarate;
u1_t txrxFlags;
u1_t saveIrqFlags;
};
// Constants for formatting LORA values
static const char* const SF_NAMES[] = { "FSK", "SF7", "SF8", "SF9", "SF10", "SF11", "SF12", "SFrfu" };
static const char* const BW_NAMES[] = { "BW125", "BW250", "BW500", "BWrfu" };
static const char* const CR_NAMES[] = { "CR 4/5", "CR 4/6", "CR 4/7", "CR 4/8" };
static const char* const CRC_NAMES[] = { "NoCrc", "Crc" };
static void printMessage(TTNLogMessage* log);
static void printFatalError(TTNLogMessage* log);
static void printEvent(TTNLogMessage* log);
static void printEvtJoined(TTNLogMessage* log);
static void printEvtJoinFailed(TTNLogMessage* log);
static void printEvtTxComplete(TTNLogMessage* log);
static void printEvtTxStart(TTNLogMessage* log);
static void printEvtRxStart(TTNLogMessage* log);
static void printEvtJoinTxComplete(TTNLogMessage* log);
static void bin2hex(const uint8_t* bin, unsigned len, char* buf, char sep = 0);
// Create singleton instance
TTNLogging* TTNLogging::initInstance()
{
ttnLog.init();
return &ttnLog;
}
// Initialize logging
void TTNLogging::init()
{
ringBuffer = xRingbufferCreate(NUM_RINGBUF_MSG * sizeof(TTNLogMessage), RINGBUF_TYPE_NOSPLIT);
if (ringBuffer == nullptr) {
ESP_LOGE(TAG, "Failed to create ring buffer");
ASSERT(0);
}
xTaskCreate(loggingTask, "ttn_log", 1024 * 4, ringBuffer, 4, nullptr);
hal_set_failure_handler(logFatal);
}
// Record a logging event for later output
void TTNLogging::logEvent(int event, const char* message, uint32_t datum)
{
if (ringBuffer == nullptr)
return;
TTNLogMessage log;
log.message = message;
log.datum = datum;
// capture state
log.time = os_getTime();
log.txend = LMIC.txend;
log.globalDutyAvail = LMIC.globalDutyAvail;
log.event = (ev_t)event;
log.freq = LMIC.freq;
log.opmode = LMIC.opmode;
log.fcntDn = (u2_t) LMIC.seqnoDn;
log.fcntUp = (u2_t) LMIC.seqnoUp;
log.rxsyms = LMIC.rxsyms;
log.rps = LMIC.rps;
log.txChnl = LMIC.txChnl;
log.datarate = LMIC.datarate;
log.txrxFlags = LMIC.txrxFlags;
log.saveIrqFlags = LMIC.saveIrqFlags;
xRingbufferSend(ringBuffer, &log, sizeof(log), 0);
}
// record a fatal event (failed assert) for later output
void TTNLogging::logFatal(const char* file, uint16_t line)
{
ttnLog.logEvent(-3, file, line);
}
// Record an informational message for later output
// The message must not be freed.
extern "C" void LMICOS_logEvent(const char *pMessage)
{
ttnLog.logEvent(-1, pMessage, 0);
}
// Record an information message with an integer value for later output
// The message must not be freed.
extern "C" void LMICOS_logEventUint32(const char *pMessage, uint32_t datum)
{
ttnLog.logEvent(-2, pMessage, datum);
}
// ---------------------------------------------------------------------------
// Log output
// Tasks that receiveds the recorded messages, formats and outputs them.
void TTNLogging::loggingTask(void* param)
{
RingbufHandle_t ringBuffer = (RingbufHandle_t)param;
while (true) {
size_t size;
TTNLogMessage* log = (TTNLogMessage*) xRingbufferReceive(ringBuffer, &size, portMAX_DELAY);
if (log == nullptr)
continue;
printMessage(log);
vRingbufferReturnItem(ringBuffer, log);
}
}
// Format and output a log message
void printMessage(TTNLogMessage* log)
{
switch((int)log->event)
{
case -1:
ESP_LOGI(TAG, "%u (%d ms) - %s: opmode=%x",
log->time, osticks2ms(log->time),
log->message, log->opmode
);
break;
case -2:
ESP_LOGI(TAG, "%u (%d ms) - %s: datum=0x%x, opmode=%x)",
log->time, osticks2ms(log->time),
log->message, log->datum, log->opmode
);
break;
case -3:
printFatalError(log);
break;
default:
printEvent(log);
break;
}
}
void printFatalError(TTNLogMessage* log)
{
ESP_LOGE(TAG, "%u (%d ms) - %s, %d",
log->time, osticks2ms(log->time),
log->message, log->datum
);
ESP_LOGE(TAG, "- freq=%d.%d, txend=%u, avail=%u, ch=%u",
log->freq / 1000000, (log->freq % 1000000) / 100000,
log->txend, log->globalDutyAvail,
(unsigned)log->txChnl
);
rps_t rps = log->rps;
ESP_LOGE(TAG, "- rps=0x%02x (%s, %s, %s, %s, IH=%d)",
rps,
SF_NAMES[getSf(rps)],
BW_NAMES[getBw(rps)],
CR_NAMES[getCr(rps)],
CRC_NAMES[getNocrc(rps)],
getIh(rps)
);
ESP_LOGE(TAG, "- opmode=%x, txrxFlags=0x%02x%s, saveIrqFlags=0x%02x",
log->opmode,
log->txrxFlags,
(log->txrxFlags & TXRX_ACK) != 0 ? "; received ack" : "",
log->saveIrqFlags
);
}
void printEvent(TTNLogMessage* log)
{
ESP_LOGI(TAG, "%u (%d ms) - %s",
log->time, osticks2ms(log->time),
log->message
);
switch((int)log->event)
{
case EV_JOINED:
printEvtJoined(log);
break;
case EV_JOIN_FAILED:
printEvtJoinFailed(log);
break;
case EV_TXCOMPLETE:
printEvtTxComplete(log);
break;
case EV_TXSTART:
printEvtTxStart(log);
break;
case EV_RXSTART:
printEvtRxStart(log);
break;
case EV_JOIN_TXCOMPLETE:
printEvtJoinTxComplete(log);
break;
default:
break;
};
}
// Format and output the detail of a successful network join
void printEvtJoined(TTNLogMessage* log)
{
ESP_LOGI(TAG, "- ch=%d", (unsigned)log->txChnl);
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
ESP_LOGI(TAG, "- netid: %d", netid);
ESP_LOGI(TAG, "- devaddr: %08x", devaddr);
char hexBuf[48];
bin2hex((uint8_t*)&artKey, sizeof(artKey), hexBuf, '-');
ESP_LOGI(TAG, "- artKey: %s", hexBuf);
bin2hex((uint8_t*)&nwkKey, sizeof(nwkKey), hexBuf, '-');
ESP_LOGI(TAG, "- nwkKey: %s", hexBuf);
}
// Format and output the detail of a failed network join
void printEvtJoinFailed(TTNLogMessage* log)
{
rps_t rps = log->rps;
ESP_LOGE(TAG, "- freq=%d.%d, opmode=%x, rps=0x%02x (%s, %s, %s, %s, IH=%d)",
log->freq / 1000000, (log->freq % 1000000) / 100000,
log->opmode,
rps,
SF_NAMES[getSf(rps)],
BW_NAMES[getBw(rps)],
CR_NAMES[getCr(rps)],
CRC_NAMES[getNocrc(rps)],
getIh(rps)
);
}
void printEvtTxComplete(TTNLogMessage* log)
{
rps_t rps = log->rps;
ESP_LOGI(TAG, "- ch=%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)",
(unsigned)log->txChnl,
rps,
SF_NAMES[getSf(rps)],
BW_NAMES[getBw(rps)],
CR_NAMES[getCr(rps)],
CRC_NAMES[getNocrc(rps)],
getIh(rps)
);
ESP_LOGI(TAG, "- txrxFlags=0x%02x%s, FcntUp=%04x, FcntDn=%04x, txend=%u",
log->txrxFlags,
(log->txrxFlags & TXRX_ACK) != 0 ? "; received ack" : "",
log->fcntUp, log->fcntDn,
log->txend
);
}
void printEvtTxStart(TTNLogMessage* log)
{
rps_t rps = log->rps;
ESP_LOGI(TAG, "- ch=%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)",
(unsigned)log->txChnl,
rps,
SF_NAMES[getSf(rps)],
BW_NAMES[getBw(rps)],
CR_NAMES[getCr(rps)],
CRC_NAMES[getNocrc(rps)],
getIh(rps)
);
ESP_LOGI(TAG, "- datarate=%u, opmode=%x, txend=%u",
log->datarate,
log->opmode,
log->txend
);
}
void printEvtRxStart(TTNLogMessage* log)
{
rps_t rps = log->rps;
ESP_LOGI(TAG, "- freq=%d.%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)",
log->freq / 1000000, (log->freq % 1000000) / 100000,
rps,
SF_NAMES[getSf(rps)],
BW_NAMES[getBw(rps)],
CR_NAMES[getCr(rps)],
CRC_NAMES[getNocrc(rps)],
getIh(rps)
);
ESP_LOGI(TAG, "- delta=%dms, rxsysm=%u",
osticks2ms(log->time - log->txend),
log->rxsyms
);
}
void printEvtJoinTxComplete(TTNLogMessage* log)
{
ESP_LOGI(TAG, "- saveIrqFlags=0x%02x",
log->saveIrqFlags
);
}
static const char* HEX_DIGITS = "0123456789ABCDEF";
/**
* @brief Convert binary data to hexadecimal representation.
*
* @param bin start of binary data
* @param len length of binary data (in bytes)
* @param buf buffer for hexadecimal result
* @param sep separator used between bytes (or 0 for none)
*/
void bin2hex(const uint8_t* bin, unsigned len, char* buf, char sep)
{
int tgt = 0;
for (int i = 0; i < len; i++) {
if (sep != 0 && i != 0)
buf[tgt++] = sep;
buf[tgt++] = HEX_DIGITS[bin[i] >> 4];
buf[tgt++] = HEX_DIGITS[bin[i] & 0xf];
}
buf[tgt] = 0;
}
#endif

View File

@ -1,55 +0,0 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2019 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Circular buffer for detailed logging without affecting LMIC timing.
*******************************************************************************/
#ifndef _ttnlogging_h_
#define _ttnlogging_h_
#if LMIC_ENABLE_event_logging
#include <freertos/FreeRTOS.h>
#include <freertos/ringbuf.h>
/**
* @brief Logging class.
*
* Logs internal information from LMIC in an asynchrnous fashion in order
* not to distrub the sensitive LORA timing.
*
* A ring buffer and a separate logging task is ued. The LMIC core records
* relevant values from the current LORA settings and writes them to a ring
* buffer. The logging tasks receives the message and the values, formats
* them and outputs them via the regular ESP-IDF logging mechanism.
*
* In order to activate the detailed logging, set the macro
* `LMIC_ENABLE_event_logging` to 1.
*
* This class is not to be used directly.
*/
class TTNLogging {
public:
static TTNLogging* initInstance();
void init();
void logEvent(int event, const char* message, uint32_t datum);
private:
static void loggingTask(void* param);
static void logFatal(const char* file, uint16_t line);
RingbufHandle_t ringBuffer;
};
#endif
#endif

View File

@ -1,73 +0,0 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2019 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Task listening on a UART port for provisioning commands.
*******************************************************************************/
#ifndef _ttnprovisioning_h_
#define _ttnprovisioning_h_
#include "lmic/oslmic.h"
#include "nvs_flash.h"
class TTNProvisioning
{
public:
TTNProvisioning();
bool haveKeys();
bool decodeKeys(const char *dev_eui, const char *app_eui, const char *app_key);
bool fromMAC(const char *app_eui, const char *app_key);
bool saveKeys();
bool restoreKeys(bool silent);
#if defined(TTN_HAS_AT_COMMANDS)
void startTask();
#endif
private:
bool decode(bool incl_dev_eui, const char *dev_eui, const char *app_eui, const char *app_key);
bool readNvsValue(nvs_handle handle, const char* key, uint8_t* data, size_t expected_length, bool silent);
bool writeNvsValue(nvs_handle handle, const char* key, const uint8_t* data, size_t len);
#if defined(TTN_HAS_AT_COMMANDS)
void provisioningTask();
void addLineData(int numBytes);
void detectLineEnd(int start_at);
void processLine();
#endif
#if defined(TTN_CONFIG_UART)
void configUART();
#endif
static bool hexStrToBin(const char *hex, uint8_t *buf, int len);
static int hexTupleToByte(const char *hex);
static int hexDigitToVal(char ch);
static void binToHexStr(const uint8_t* buf, int len, char* hex);
static char valToHexDigit(int val);
static void swapBytes(uint8_t* buf, int len);
static bool isAllZeros(const uint8_t* buf, int len);
private:
bool have_keys = false;
#if defined(TTN_HAS_AT_COMMANDS)
QueueHandle_t uart_queue;
char* line_buf;
int line_length;
uint8_t last_line_end_char;
bool quit_task;
friend void ttn_provisioning_task_caller(void* pvParameter);
#endif
};
#endif

View File

@ -1,427 +1,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 "TTNProvisioning.h"
#include "TTNLogging.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 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()
: 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;
ttn_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)
{
ttn_hal.configurePins(spi_host, nss, rxtx, rst, dio0, dio1);
#if LMIC_ENABLE_event_logging
logging = TTNLogging::initInstance();
#endif
LMIC_registerEventCb(eventCallback, nullptr);
LMIC_registerRxMessageCb(messageReceivedCallback, nullptr);
os_init_ex(nullptr);
reset();
lmicEventQueue = xQueueCreate(4, sizeof(TTNLmicEvent));
ASSERT(lmicEventQueue != nullptr);
ttn_hal.startLMICTask();
}
void TheThingsNetwork::reset()
{
ttn_hal.enterCriticalSection();
LMIC_reset();
LMIC_setClockError(MAX_CLOCK_ERROR * 4 / 100);
waitingReason = eWaitingNone;
ttn_hal.leaveCriticalSection();
}
void TheThingsNetwork::shutdown()
{
ttn_hal.enterCriticalSection();
LMIC_shutdown();
ttn_hal.stopLMICTask();
waitingReason = eWaitingNone;
ttn_hal.leaveCriticalSection();
}
void TheThingsNetwork::startup()
{
ttn_hal.enterCriticalSection();
LMIC_reset();
ttn_hal.startLMICTask();
ttn_hal.leaveCriticalSection();
}
bool TheThingsNetwork::provision(const char *devEui, const char *appEui, const char *appKey)
{
if (!provisioning.decodeKeys(devEui, appEui, appKey))
return false;
return provisioning.saveKeys();
}
bool TheThingsNetwork::provisionWithMAC(const char *appEui, const char *appKey)
{
if (!provisioning.fromMAC(appEui, appKey))
return false;
return provisioning.saveKeys();
}
void TheThingsNetwork::startProvisioningTask()
{
#if defined(TTN_HAS_AT_COMMANDS)
provisioning.startTask();
#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 (!provisioning.haveKeys())
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 (!provisioning.decodeKeys(devEui, appEui, appKey))
return false;
return joinCore();
}
bool TheThingsNetwork::join()
{
if (!provisioning.haveKeys())
{
if (!provisioning.restoreKeys(false))
return false;
}
return joinCore();
}
bool TheThingsNetwork::joinCore()
{
if (!provisioning.haveKeys())
{
ESP_LOGW(TAG, "Device EUI, App EUI and/or App key have not been provided");
return false;
}
ttn_hal.enterCriticalSection();
xQueueReset(lmicEventQueue);
waitingReason = eWaitingForJoin;
LMIC_startJoining();
ttn_hal.wakeUp();
ttn_hal.leaveCriticalSection();
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)
{
ttn_hal.enterCriticalSection();
if (waitingReason != eWaitingNone || (LMIC.opmode & OP_TXRXPEND) != 0)
{
ttn_hal.leaveCriticalSection();
return kTTNErrorTransmissionFailed;
}
waitingReason = eWaitingForTransmission;
LMIC.client.txMessageCb = messageTransmittedCallback;
LMIC.client.txMessageUserData = nullptr;
LMIC_setTxData2(port, (xref2u1_t)payload, length, confirm);
ttn_hal.wakeUp();
ttn_hal.leaveCriticalSection();
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 (provisioning.haveKeys())
return true;
provisioning.restoreKeys(true);
return provisioning.haveKeys();
}
void TheThingsNetwork::setRSSICal(int8_t rssiCal)
{
ttn_hal.rssiCal = 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<int>(window) & 0x03;
return lastRfSettings[index];
}
TTNRFSettings TheThingsNetwork::txSettings()
{
return lastRfSettings[static_cast<int>(kTTNTxWindow)];
}
TTNRFSettings TheThingsNetwork::rx1Settings()
{
return lastRfSettings[static_cast<int>(kTTNRx1Window)];
}
TTNRFSettings TheThingsNetwork::rx2Settings()
{
return lastRfSettings[static_cast<int>(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<int>(kTTNTxWindow)]);
clearRFSettings(lastRfSettings[static_cast<int>(kTTNRx1Window)]);
clearRFSettings(lastRfSettings[static_cast<int>(kTTNRx2Window)]);
break;
case EV_RXSTART:
if (currentWindow != kTTNRx1Window)
{
currentWindow = kTTNRx1Window;
saveRFSettings(lastRfSettings[static_cast<int>(kTTNRx1Window)]);
}
else
{
currentWindow = kTTNRx2Window;
saveRFSettings(lastRfSettings[static_cast<int>(kTTNRx2Window)]);
}
break;
default:
currentWindow = kTTNIdleWindow;
break;
};
#if LMIC_ENABLE_event_logging
logging->logEvent(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<TTNSpreadingFactor>(getSf(LMIC.rps) + 1);
rfSettings.bandwidth = static_cast<TTNBandwidth>(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<ttn_rx_tx_window_t>(window));
TTNRFSettings result;
result.spreadingFactor = static_cast<TTNSpreadingFactor>(settings.spreading_factor);
result.bandwidth = static_cast<TTNBandwidth>(settings.bandwidth);
result.frequency = settings.frequency;
return result;
}

View File

@ -34,9 +34,9 @@
// This should be defined elsewhere
void lmic_aes_encrypt(u1_t *data, u1_t *key);
// global area for passing parameters (aux, key) and for storing round keys
// global area for passing parameters (aux, key)
u4_t AESAUX[16/sizeof(u4_t)];
u4_t AESKEY[11*16/sizeof(u4_t)];
u4_t AESKEY[16/sizeof(u4_t)];
// Shift the given buffer left one bit
static void shift_left(xref2u1_t buf, u1_t len) {

View File

@ -1,9 +1,9 @@
/*******************************************************************************
*
*
* 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
*
@ -17,7 +17,8 @@
#elif defined(CONFIG_TTN_LORA_FREQ_US_915)
#define CFG_us915 1
#elif defined(CONFIG_TTN_LORA_FREQ_AU_921)
# warning "CONFIG_TTN_LORA_FREQ_AU_921 was deprecated in favour of CONFIG_TTN_LORA_FREQ_AU_921. Support for CONFIG_TTN_LORA_FREQ_AU_921 will be removed in the future."
#warning \
"CONFIG_TTN_LORA_FREQ_AU_921 was deprecated in favour of CONFIG_TTN_LORA_FREQ_AU_921. Support for CONFIG_TTN_LORA_FREQ_AU_921 will be removed in the future."
#define CFG_au915 1
#elif defined(CONFIG_TTN_LORA_FREQ_AU_915)
#define CFG_au915 1
@ -50,7 +51,6 @@
#endif
#endif
// 16 μs per tick
// LMIC requires ticks to be 15.5μs - 100 μs long
#define US_PER_OSTICK 16

611
src/hal/hal_esp32.c Executable file
View File

@ -0,0 +1,611 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2019 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF.
*******************************************************************************/
#include "hal_esp32.h"
#include "../lmic/lmic.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_timer.h"
#include "esp_log.h"
#include <time.h>
#include <sys/time.h>
#define LMIC_UNUSED_PIN 0xff
#define NOTIFY_BIT_DIO 1
#define NOTIFY_BIT_TIMER 2
#define NOTIFY_BIT_WAKEUP 4
#define NOTIFY_BIT_STOP 8
#define TAG "ttn_hal"
typedef enum {
WAIT_KIND_NONE = 0,
WAIT_KIND_CHECK_IO,
WAIT_KIND_WAIT_FOR_ANY_EVENT,
WAIT_KIND_WAIT_FOR_TIMER
} wait_kind_e;
static void lmic_background_task(void* pvParameter);
static void qio_irq_handler(void* arg);
static void timer_callback(void *arg);
static int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time);
static int64_t get_current_time();
static void init_io(void);
static void init_spi(void);
static void assert_nss(spi_transaction_t* trans);
static void deassert_nss(spi_transaction_t* trans);
static void init_timer(void);
static void set_next_alarm(int64_t time);
static void arm_timer(int64_t esp_now);
static void disarm_timer(void);
static bool wait(wait_kind_e wait_kind);
static spi_host_device_t spi_host;
static gpio_num_t pin_nss;
static gpio_num_t pin_rx_tx;
static gpio_num_t pin_rst;
static gpio_num_t pin_dio0;
static gpio_num_t pin_dio1;
static int8_t rssi_cal = 10;
static TaskHandle_t lmic_task;
static uint32_t dio_interrupt_time;
static uint8_t dio_num;
static spi_device_handle_t spi_handle;
static spi_transaction_t spi_transaction;
static SemaphoreHandle_t mutex;
static esp_timer_handle_t timer;
static int64_t time_offset;
static int32_t initial_time_offset;
static int64_t next_alarm;
static volatile bool run_background_task;
static volatile wait_kind_e current_wait_kind;
// -----------------------------------------------------------------------------
// I/O
void hal_esp32_configure_pins(spi_host_device_t host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1)
{
spi_host = host;
pin_nss = (gpio_num_t)nss;
pin_rx_tx = (gpio_num_t)rxtx;
pin_rst = (gpio_num_t)rst;
pin_dio0 = (gpio_num_t)dio0;
pin_dio1 = (gpio_num_t)dio1;
// Until the background process has been started, use the current task
// for supporting calls like `hal_waitUntil()`.
lmic_task = xTaskGetCurrentTaskHandle();
}
void IRAM_ATTR qio_irq_handler(void *arg)
{
dio_interrupt_time = hal_ticks();
dio_num = (u1_t)(long)arg;
BaseType_t higher_prio_task_woken = pdFALSE;
xTaskNotifyFromISR(lmic_task, NOTIFY_BIT_DIO, eSetBits, &higher_prio_task_woken);
if (higher_prio_task_woken)
portYIELD_FROM_ISR();
}
void init_io(void)
{
// pin_nss, pin_dio0 and pin_dio1 are required
ASSERT(pin_nss != LMIC_UNUSED_PIN);
ASSERT(pin_dio0 != LMIC_UNUSED_PIN);
ASSERT(pin_dio1 != LMIC_UNUSED_PIN);
gpio_config_t output_pin_config = {
.pin_bit_mask = BIT64(pin_nss),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = false,
.pull_down_en = false,
.intr_type = GPIO_INTR_DISABLE
};
if (pin_rx_tx != LMIC_UNUSED_PIN)
output_pin_config.pin_bit_mask |= BIT64(pin_rx_tx);
if (pin_rst != LMIC_UNUSED_PIN)
output_pin_config.pin_bit_mask |= BIT64(pin_rst);
gpio_config(&output_pin_config);
gpio_set_level(pin_nss, 1);
if (pin_rx_tx != LMIC_UNUSED_PIN)
gpio_set_level(pin_rx_tx, 0);
if (pin_rst != LMIC_UNUSED_PIN)
gpio_set_level(pin_rst, 0);
// DIO pins with interrupt handlers
gpio_config_t input_pin_config = {
.pin_bit_mask = BIT64(pin_dio0) | BIT64(pin_dio1),
.mode = GPIO_MODE_INPUT,
.pull_up_en = false,
.pull_down_en = true,
.intr_type = GPIO_INTR_POSEDGE,
};
gpio_config(&input_pin_config);
ESP_LOGI(TAG, "IO initialized");
}
__attribute__((weak)) // duplicate this symbol if your pin is controlled by e.g. I2C
void hal_pin_rxtx(u1_t val)
{
if (pin_rx_tx == LMIC_UNUSED_PIN)
return;
gpio_set_level(pin_rx_tx, val);
}
__attribute__((weak)) // duplicate this symbol if your pin is controlled by e.g. I2C
void hal_pin_rst(u1_t val)
{
if (pin_rst == LMIC_UNUSED_PIN)
return;
if (val == 0 || val == 1)
{
// drive pin
gpio_set_level(pin_rst, val);
gpio_set_direction(pin_rst, GPIO_MODE_OUTPUT);
}
else
{
#if defined(CONFIG_TTN_RESET_STATES_ASSERTED)
// drive up the pin because the hardware is nonstandard
gpio_set_level(pin_rst, 1);
gpio_set_direction(pin_rst, GPIO_MODE_OUTPUT);
#else
// keep pin floating
gpio_set_direction(pin_rst, GPIO_MODE_INPUT);
#endif
}
}
s1_t hal_getRssiCal(void)
{
return rssi_cal;
}
ostime_t hal_setModuleActive(bit_t val)
{
return 0;
}
bit_t hal_queryUsingTcxo(void)
{
return false;
}
uint8_t hal_getTxPowerPolicy(u1_t inputPolicy, s1_t requestedPower, u4_t frequency)
{
return LMICHAL_radio_tx_power_policy_paboost;
}
// -----------------------------------------------------------------------------
// SPI
void init_spi(void)
{
// init device
spi_device_interface_config_t spi_config = {
.mode = 0,
.clock_speed_hz = CONFIG_TTN_SPI_FREQ,
.command_bits = 0,
.address_bits = 8,
.spics_io_num = -1,
.queue_size = 1,
.pre_cb = assert_nss,
.post_cb = deassert_nss,
};
esp_err_t ret = spi_bus_add_device(spi_host, &spi_config, &spi_handle);
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "SPI initialized");
}
void hal_spi_write(u1_t cmd, const u1_t *buf, size_t len)
{
memset(&spi_transaction, 0, sizeof(spi_transaction));
spi_transaction.addr = cmd;
spi_transaction.length = 8 * len;
spi_transaction.tx_buffer = buf;
esp_err_t err = spi_device_transmit(spi_handle, &spi_transaction);
ESP_ERROR_CHECK(err);
}
void hal_spi_read(u1_t cmd, u1_t *buf, size_t len)
{
memset(buf, 0, len);
memset(&spi_transaction, 0, sizeof(spi_transaction));
spi_transaction.addr = cmd;
spi_transaction.length = 8 * len;
spi_transaction.rxlength = 8 * len;
spi_transaction.tx_buffer = buf;
spi_transaction.rx_buffer = buf;
esp_err_t err = spi_device_transmit(spi_handle, &spi_transaction);
ESP_ERROR_CHECK(err);
}
void IRAM_ATTR assert_nss(spi_transaction_t* trans)
{
gpio_set_level(pin_nss, 0);
}
void IRAM_ATTR deassert_nss(spi_transaction_t* trans)
{
gpio_set_level(pin_nss, 1);
}
// -----------------------------------------------------------------------------
// TIME
/*
* LIMIC uses a 32 bit time system (ostime_t) counting ticks. In this
* implementation, each tick is 16µs. It will wrap arounnd every 19 hours.
*
* The ESP32 has a 64 bit timer counting microseconds. It will wrap around
* every 584,000 years. So we don't need to bother.
*
* The time includes an offset initialized from `gettimeofday()`
* to ensure the time correctly advances during deep sleep.
*
* Based on this timer, future callbacks can be scheduled. This is used to
* schedule the next LMIC job.
*/
// Convert LMIC tick time (ostime_t) to ESP absolute time.
// `os_time` is assumed to be somewhere between one hour in the past and
// 18 hours into the future.
int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time)
{
int64_t esp_time;
uint32_t os_now = (uint32_t)(esp_now >> 4);
// unsigned difference:
// 0x00000000 - 0xefffffff: future (0 to about 18 hours)
// 0xf0000000 - 0xffffffff: past (about 1 to 0 hours)
uint32_t os_diff = os_time - os_now;
if (os_diff < 0xf0000000)
{
esp_time = esp_now + (((int64_t)os_diff) << 4);
}
else
{
os_diff = -os_diff;
esp_time = esp_now - (((int64_t)os_diff) << 4);
}
return esp_time;
}
int64_t IRAM_ATTR get_current_time()
{
return esp_timer_get_time() + time_offset;
}
void init_timer(void)
{
esp_timer_create_args_t timer_config = {
.callback = &timer_callback,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "lmic_job"
};
esp_err_t err = esp_timer_create(&timer_config, &timer);
ESP_ERROR_CHECK(err);
struct timeval now;
gettimeofday(&now, NULL);
time_offset += (int64_t)now.tv_sec * 1000000;
initial_time_offset = 0;
ESP_LOGI(TAG, "Timer initialized");
}
uint32_t hal_esp32_get_time(void)
{
struct timeval now;
gettimeofday(&now, NULL);
return now.tv_sec + initial_time_offset;
}
void hal_esp32_set_time(uint32_t time_val)
{
initial_time_offset = time_val;
time_offset = (int64_t)time_val * 1000000;
}
void set_next_alarm(int64_t time)
{
next_alarm = time;
}
void arm_timer(int64_t esp_now)
{
if (next_alarm == 0)
return;
int64_t timeout = next_alarm - get_current_time();
if (timeout < 0)
timeout = 10;
esp_timer_start_once(timer, timeout);
}
void disarm_timer(void)
{
esp_timer_stop(timer);
}
void timer_callback(void *arg)
{
xTaskNotify(lmic_task, NOTIFY_BIT_TIMER, eSetBits);
}
// Wait for the next external event. Either:
// - scheduled timer due to scheduled job or waiting for a given time
// - wake up event from the client code
// - I/O interrupt (DIO0 or DIO1 pin)
bool wait(wait_kind_e wait_kind)
{
TickType_t ticks_to_wait = wait_kind == WAIT_KIND_CHECK_IO ? 0 : portMAX_DELAY;
while (true)
{
current_wait_kind = wait_kind;
uint32_t bits = ulTaskNotifyTake(pdTRUE, ticks_to_wait);
current_wait_kind = WAIT_KIND_NONE;
if (bits == 0)
return false;
if ((bits & NOTIFY_BIT_STOP) != 0)
return false;
if ((bits & NOTIFY_BIT_WAKEUP) != 0)
{
if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER)
{
disarm_timer();
return true;
}
}
else if ((bits & NOTIFY_BIT_TIMER) != 0)
{
disarm_timer();
set_next_alarm(0);
if (wait_kind != WAIT_KIND_CHECK_IO)
return true;
}
else // IO interrupt
{
if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER)
disarm_timer();
hal_esp32_enter_critical_section();
radio_irq_handler_v2(dio_num, dio_interrupt_time);
hal_esp32_leave_critical_section();
if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER)
return true;
}
}
}
TickType_t hal_esp32_get_timer_duration(void)
{
wait_kind_e wait_kind = current_wait_kind;
int64_t alarm_time = next_alarm;
if (wait_kind == WAIT_KIND_NONE || wait_kind == WAIT_KIND_CHECK_IO)
return 1; // busy, not waiting
if (alarm_time != 0)
{
TickType_t dur = pdMS_TO_TICKS((alarm_time - get_current_time() + 999) / 1000);
if (dur > pdMS_TO_TICKS(30000))
dur = pdMS_TO_TICKS(200);
return dur;
}
return 0; // waiting indefinitely
}
// Gets current time in LMIC ticks
u4_t IRAM_ATTR hal_ticks(void)
{
// LMIC tick unit: 16µs
// esp_timer unit: 1µs
return (u4_t)(get_current_time() >> 4);
}
// Wait until the specified time.
// Called if the LMIC code needs to wait for a precise time.
// All other events are ignored and will be served later.
u4_t hal_waitUntil(u4_t time)
{
int64_t esp_now = get_current_time();
int64_t esp_time = os_time_to_esp_time(esp_now, time);
set_next_alarm(esp_time);
arm_timer(esp_now);
wait(WAIT_KIND_WAIT_FOR_TIMER);
u4_t os_now = hal_ticks();
u4_t diff = os_now - time;
return diff < 0x80000000U ? diff : 0;
}
// Called by client code to wake up LMIC to do something,
// e.g. send a submitted messages.
void hal_esp32_wake_up(void)
{
xTaskNotify(lmic_task, NOTIFY_BIT_WAKEUP, eSetBits);
}
// Check if the specified time has been reached or almost reached.
// Otherwise, save it as alarm time.
// LMIC calls this function with the scheduled time of the next job
// in the queue. If the job is not due yet, LMIC will go to sleep.
u1_t hal_checkTimer(uint32_t time)
{
int64_t esp_now = get_current_time();
int64_t esp_time = os_time_to_esp_time(esp_now, time);
int64_t diff = esp_time - esp_now;
if (diff < 100)
return 1; // timer has expired or will expire very soon
set_next_alarm(esp_time);
return 0;
}
// Go to sleep until next event.
// Called when LMIC is not busy and not job is due to be executed.
void hal_sleep(void)
{
if (wait(WAIT_KIND_CHECK_IO))
return;
arm_timer(get_current_time());
wait(WAIT_KIND_WAIT_FOR_ANY_EVENT);
}
// -----------------------------------------------------------------------------
// IRQ
void hal_disableIRQs(void)
{
// nothing to do as interrupt handlers post message to queue
// and don't access any shared data structures
}
void hal_enableIRQs(void)
{
// nothing to do as interrupt handlers post message to queue
// and don't access any shared data structures
}
void hal_processPendingIRQs(void)
{
// nothing to do as interrupt handlers post message to queue
// and don't access any shared data structures
}
// -----------------------------------------------------------------------------
// Synchronization between application code and background task
void hal_esp32_init_critical_section(void)
{
mutex = xSemaphoreCreateRecursiveMutex();
}
void hal_esp32_enter_critical_section(void)
{
xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
}
void hal_esp32_leave_critical_section(void)
{
xSemaphoreGiveRecursive(mutex);
}
// -----------------------------------------------------------------------------
void lmic_background_task(void* pvParameter)
{
while (run_background_task)
os_runloop_once();
vTaskDelete(NULL);
}
void hal_init_ex(const void *pContext)
{
// configure radio I/O and interrupt handler
init_io();
// configure radio SPI
init_spi();
// configure timer and alarm callback
init_timer();
}
void hal_esp32_start_lmic_task(void)
{
run_background_task = true;
xTaskCreate(lmic_background_task, "ttn_lmic", 1024 * 4, NULL, CONFIG_TTN_BG_TASK_PRIO, &lmic_task);
// enable interrupts
gpio_isr_handler_add(pin_dio0, qio_irq_handler, (void *)0);
gpio_isr_handler_add(pin_dio1, qio_irq_handler, (void *)1);
}
void hal_esp32_stop_lmic_task(void)
{
run_background_task = false;
gpio_isr_handler_remove(pin_dio0);
gpio_isr_handler_remove(pin_dio1);
disarm_timer();
set_next_alarm(0);
xTaskNotify(lmic_task, NOTIFY_BIT_STOP, eSetBits);
lmic_task = xTaskGetCurrentTaskHandle();
}
// -----------------------------------------------------------------------------
// Fatal failure
static hal_failure_handler_t* custom_hal_failure_handler = NULL;
void hal_set_failure_handler(hal_failure_handler_t* const handler)
{
custom_hal_failure_handler = handler;
}
void hal_failed(const char *file, u2_t line)
{
if (custom_hal_failure_handler != NULL)
(*custom_hal_failure_handler)(file, line);
ESP_LOGE(TAG, "LMIC failed and stopped: %s:%d", file, line);
// go to sleep forever
while (true)
{
vTaskDelay(portMAX_DELAY);
}
}
// -----------------------------------------------------------------------------
// RSSI
void hal_esp32_set_rssi_cal(int8_t cal)
{
rssi_cal = cal;
}

View File

@ -1,529 +0,0 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2019 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF.
*******************************************************************************/
#include "../lmic/lmic.h"
#include "../hal/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
#define NOTIFY_BIT_DIO 1
#define NOTIFY_BIT_TIMER 2
#define NOTIFY_BIT_WAKEUP 4
#define NOTIFY_BIT_STOP 8
static const char* const TAG = "ttn_hal";
HAL_ESP32 ttn_hal;
TaskHandle_t HAL_ESP32::lmicTask = nullptr;
uint32_t HAL_ESP32::dioInterruptTime = 0;
uint8_t HAL_ESP32::dioNum = 0;
// -----------------------------------------------------------------------------
// Constructor
HAL_ESP32::HAL_ESP32()
: rssiCal(10), nextAlarm(0)
{
}
// -----------------------------------------------------------------------------
// I/O
void HAL_ESP32::configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1)
{
spiHost = spi_host;
pinNSS = (gpio_num_t)nss;
pinRxTx = (gpio_num_t)rxtx;
pinRst = (gpio_num_t)rst;
pinDIO0 = (gpio_num_t)dio0;
pinDIO1 = (gpio_num_t)dio1;
// Until the background process has been started, use the current task
// for supporting calls like `hal_waitUntil()`.
lmicTask = xTaskGetCurrentTaskHandle();
}
void IRAM_ATTR HAL_ESP32::dioIrqHandler(void *arg)
{
dioInterruptTime = hal_ticks();
dioNum = (u1_t)(long)arg;
BaseType_t higherPrioTaskWoken = pdFALSE;
xTaskNotifyFromISR(lmicTask, NOTIFY_BIT_DIO, eSetBits, &higherPrioTaskWoken);
if (higherPrioTaskWoken)
portYIELD_FROM_ISR();
}
void HAL_ESP32::ioInit()
{
// pinNSS and pinDIO0 and pinDIO1 are required
ASSERT(pinNSS != LMIC_UNUSED_PIN);
ASSERT(pinDIO0 != LMIC_UNUSED_PIN);
ASSERT(pinDIO1 != LMIC_UNUSED_PIN);
gpio_pad_select_gpio(pinNSS);
gpio_set_level(pinNSS, 0);
gpio_set_direction(pinNSS, GPIO_MODE_OUTPUT);
if (pinRxTx != LMIC_UNUSED_PIN)
{
gpio_pad_select_gpio(pinRxTx);
gpio_set_level(pinRxTx, 0);
gpio_set_direction(pinRxTx, GPIO_MODE_OUTPUT);
}
if (pinRst != LMIC_UNUSED_PIN)
{
gpio_pad_select_gpio(pinRst);
gpio_set_level(pinRst, 0);
gpio_set_direction(pinRst, GPIO_MODE_OUTPUT);
}
// DIO pins with interrupt handlers
gpio_pad_select_gpio(pinDIO0);
gpio_set_direction(pinDIO0, GPIO_MODE_INPUT);
gpio_set_intr_type(pinDIO0, GPIO_INTR_POSEDGE);
gpio_pad_select_gpio(pinDIO1);
gpio_set_direction(pinDIO1, GPIO_MODE_INPUT);
gpio_set_intr_type(pinDIO1, GPIO_INTR_POSEDGE);
ESP_LOGI(TAG, "IO initialized");
}
void hal_pin_rxtx(u1_t val)
{
if (ttn_hal.pinRxTx == LMIC_UNUSED_PIN)
return;
gpio_set_level(ttn_hal.pinRxTx, val);
}
void hal_pin_rst(u1_t val)
{
if (ttn_hal.pinRst == LMIC_UNUSED_PIN)
return;
if (val == 0 || val == 1)
{
// drive pin
gpio_set_level(ttn_hal.pinRst, val);
gpio_set_direction(ttn_hal.pinRst, GPIO_MODE_OUTPUT);
}
else
{
#if defined(CONFIG_TTN_RESET_STATES_ASSERTED)
// drive up the pin because the hardware is nonstandard
gpio_set_level(ttn_hal.pinRst, 1);
gpio_set_direction(ttn_hal.pinRst, GPIO_MODE_OUTPUT);
#else
// keep pin floating
gpio_set_level(ttn_hal.pinRst, val);
gpio_set_direction(ttn_hal.pinRst, GPIO_MODE_INPUT);
#endif
}
}
s1_t hal_getRssiCal (void)
{
return ttn_hal.rssiCal;
}
ostime_t hal_setModuleActive (bit_t val)
{
return 0;
}
bit_t hal_queryUsingTcxo(void)
{
return false;
}
uint8_t hal_getTxPowerPolicy(u1_t inputPolicy, s1_t requestedPower, u4_t frequency)
{
return LMICHAL_radio_tx_power_policy_paboost;
}
// -----------------------------------------------------------------------------
// SPI
void HAL_ESP32::spiInit()
{
// init device
spi_device_interface_config_t spiConfig;
memset(&spiConfig, 0, sizeof(spiConfig));
spiConfig.mode = 1;
spiConfig.clock_speed_hz = CONFIG_TTN_SPI_FREQ;
spiConfig.command_bits = 0;
spiConfig.address_bits = 8;
spiConfig.spics_io_num = pinNSS;
spiConfig.queue_size = 1;
spiConfig.cs_ena_posttrans = 2;
esp_err_t ret = spi_bus_add_device(spiHost, &spiConfig, &spiHandle);
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "SPI initialized");
}
void hal_spi_write(u1_t cmd, const u1_t *buf, size_t len)
{
ttn_hal.spiWrite(cmd, buf, len);
}
void HAL_ESP32::spiWrite(uint8_t cmd, const uint8_t *buf, size_t len)
{
memset(&spiTransaction, 0, sizeof(spiTransaction));
spiTransaction.addr = cmd;
spiTransaction.length = 8 * len;
spiTransaction.tx_buffer = buf;
esp_err_t err = spi_device_transmit(spiHandle, &spiTransaction);
ESP_ERROR_CHECK(err);
}
void hal_spi_read(u1_t cmd, u1_t *buf, size_t len)
{
ttn_hal.spiRead(cmd, buf, len);
}
void HAL_ESP32::spiRead(uint8_t cmd, uint8_t *buf, size_t len)
{
memset(buf, 0, len);
memset(&spiTransaction, 0, sizeof(spiTransaction));
spiTransaction.addr = cmd;
spiTransaction.length = 8 * len;
spiTransaction.rxlength = 8 * len;
spiTransaction.tx_buffer = buf;
spiTransaction.rx_buffer = buf;
esp_err_t err = spi_device_transmit(spiHandle, &spiTransaction);
ESP_ERROR_CHECK(err);
}
// -----------------------------------------------------------------------------
// TIME
/*
* LIMIC uses a 32 bit time system (ostime_t) counting ticks. In this
* implementation each tick is 16µs. It will wrap arounnd every 19 hours.
*
* The ESP32 has a 64 bit timer counting microseconds. It will wrap around
* every 584,000 years. So we don't need to bother.
*
* Based on this timer, future callbacks can be scheduled. This is used to
* schedule the next LMIC job.
*/
// Convert LMIC tick time (ostime_t) to ESP absolute time.
// `osTime` is assumed to be somewhere between one hour in the past and
// 18 hours into the future.
int64_t HAL_ESP32::osTimeToEspTime(int64_t espNow, uint32_t osTime)
{
int64_t espTime;
uint32_t osNow = (uint32_t)(espNow >> 4);
// unsigned difference:
// 0x00000000 - 0xefffffff: future (0 to about 18 hours)
// 0xf0000000 - 0xffffffff: past (about 1 to 0 hours)
uint32_t osDiff = osTime - osNow;
if (osDiff < 0xf0000000)
{
espTime = espNow + (((int64_t)osDiff) << 4);
}
else
{
// one's complement instead of two's complement:
// off by 1 µs and ignored
osDiff = ~osDiff;
espTime = espNow - (((int64_t)osDiff) << 4);
}
return espTime;
}
void HAL_ESP32::timerInit()
{
esp_timer_create_args_t timerConfig = {
.callback = &timerCallback,
.arg = nullptr,
.dispatch_method = ESP_TIMER_TASK,
.name = "lmic_job"
};
esp_err_t err = esp_timer_create(&timerConfig, &timer);
ESP_ERROR_CHECK(err);
ESP_LOGI(TAG, "Timer initialized");
}
void HAL_ESP32::setNextAlarm(int64_t time)
{
nextAlarm = time;
}
void HAL_ESP32::armTimer(int64_t espNow)
{
if (nextAlarm == 0)
return;
int64_t timeout = nextAlarm - esp_timer_get_time();
if (timeout < 0)
timeout = 10;
esp_timer_start_once(timer, timeout);
}
void HAL_ESP32::disarmTimer()
{
esp_timer_stop(timer);
}
void HAL_ESP32::timerCallback(void *arg)
{
xTaskNotify(lmicTask, NOTIFY_BIT_TIMER, eSetBits);
}
// Wait for the next external event. Either:
// - scheduled timer due to scheduled job or waiting for a given time
// - wake up event from the client code
// - I/O interrupt (DIO0 or DIO1 pin)
bool HAL_ESP32::wait(WaitKind waitKind)
{
TickType_t ticksToWait = waitKind == CHECK_IO ? 0 : portMAX_DELAY;
while (true)
{
uint32_t bits = ulTaskNotifyTake(pdTRUE, ticksToWait);
if (bits == 0)
return false;
if ((bits & NOTIFY_BIT_STOP) != 0)
return false;
if ((bits & NOTIFY_BIT_WAKEUP) != 0)
{
if (waitKind != WAIT_FOR_TIMER)
{
disarmTimer();
return true;
}
}
else if ((bits & NOTIFY_BIT_TIMER) != 0)
{
disarmTimer();
setNextAlarm(0);
if (waitKind != CHECK_IO)
return true;
}
else // IO interrupt
{
if (waitKind != WAIT_FOR_TIMER)
disarmTimer();
enterCriticalSection();
radio_irq_handler_v2(dioNum, dioInterruptTime);
leaveCriticalSection();
if (waitKind != WAIT_FOR_TIMER)
return true;
}
}
}
// Gets current time in LMIC ticks
u4_t hal_ticks()
{
// LMIC tick unit: 16µs
// esp_timer unit: 1µs
return (u4_t)(esp_timer_get_time() >> 4);
}
// Wait until the specified time.
// Called if the LMIC code needs to wait for a precise time.
// All other events are ignored and will be served later.
u4_t hal_waitUntil(u4_t time)
{
return ttn_hal.waitUntil(time);
}
uint32_t HAL_ESP32::waitUntil(uint32_t osTime)
{
int64_t espNow = esp_timer_get_time();
int64_t espTime = osTimeToEspTime(espNow, osTime);
setNextAlarm(espTime);
armTimer(espNow);
wait(WAIT_FOR_TIMER);
u4_t osNow = hal_ticks();
u4_t diff = osNow - osTime;
return diff < 0x80000000U ? diff : 0;
}
// Called by client code to wake up LMIC to do something,
// e.g. send a submitted messages.
void HAL_ESP32::wakeUp()
{
xTaskNotify(lmicTask, NOTIFY_BIT_WAKEUP, eSetBits);
}
// Check if the specified time has been reached or almost reached.
// Otherwise, save it as alarm time.
// LMIC calls this function with the scheduled time of the next job
// in the queue. If the job is not due yet, LMIC will go to sleep.
u1_t hal_checkTimer(uint32_t time)
{
return ttn_hal.checkTimer(time);
}
uint8_t HAL_ESP32::checkTimer(u4_t osTime)
{
int64_t espNow = esp_timer_get_time();
int64_t espTime = osTimeToEspTime(espNow, osTime);
int64_t diff = espTime - espNow;
if (diff < 100)
return 1; // timer has expired or will expire very soon
setNextAlarm(espTime);
return 0;
}
// Go to sleep until next event.
// Called when LMIC is not busy and not job is due to be executed.
void hal_sleep()
{
ttn_hal.sleep();
}
void HAL_ESP32::sleep()
{
if (wait(CHECK_IO))
return;
armTimer(esp_timer_get_time());
wait(WAIT_FOR_ANY_EVENT);
}
// -----------------------------------------------------------------------------
// 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_processPendingIRQs()
{
// nothing to do as interrupt handlers post message to queue
// and don't access any shared data structures
}
// -----------------------------------------------------------------------------
// Synchronization between application code and background task
void HAL_ESP32::initCriticalSection()
{
mutex = xSemaphoreCreateRecursiveMutex();
}
void HAL_ESP32::enterCriticalSection()
{
xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
}
void HAL_ESP32::leaveCriticalSection()
{
xSemaphoreGiveRecursive(mutex);
}
// -----------------------------------------------------------------------------
void HAL_ESP32::lmicBackgroundTask(void* pvParameter)
{
HAL_ESP32* instance = (HAL_ESP32*)pvParameter;
while (instance->runBackgroundTask)
os_runloop_once();
vTaskDelete(nullptr);
}
void hal_init_ex(const void *pContext)
{
ttn_hal.init();
}
void HAL_ESP32::init()
{
// configure radio I/O and interrupt handler
ioInit();
// configure radio SPI
spiInit();
// configure timer and alarm callback
timerInit();
}
void HAL_ESP32::startLMICTask()
{
runBackgroundTask = true;
xTaskCreate(lmicBackgroundTask, "ttn_lmic", 1024 * 4, this, CONFIG_TTN_BG_TASK_PRIO, &lmicTask);
// enable interrupts
gpio_isr_handler_add(pinDIO0, dioIrqHandler, (void *)0);
gpio_isr_handler_add(pinDIO1, dioIrqHandler, (void *)1);
}
void HAL_ESP32::stopLMICTask()
{
runBackgroundTask = false;
gpio_isr_handler_remove(pinDIO0);
gpio_isr_handler_remove(pinDIO1);
disarmTimer();
xTaskNotify(lmicTask, NOTIFY_BIT_STOP, eSetBits);
}
// -----------------------------------------------------------------------------
// Fatal failure
static hal_failure_handler_t* custom_hal_failure_handler = nullptr;
void hal_set_failure_handler(const hal_failure_handler_t* const handler)
{
custom_hal_failure_handler = handler;
}
void hal_failed(const char *file, u2_t line)
{
if (custom_hal_failure_handler != nullptr)
(*custom_hal_failure_handler)(file, line);
ESP_LOGE(TAG, "LMIC failed and stopped: %s:%d", file, line);
// go to sleep forever
while (true)
{
vTaskDelay(portMAX_DELAY);
}
}

View File

@ -2,7 +2,7 @@
*
* 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
@ -10,85 +10,55 @@
* Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF.
*******************************************************************************/
#ifndef _hal_esp32_h_
#define _hal_esp32_h_
#ifndef HAL_ESP32_H
#define HAL_ESP32_H
#include <stdint.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <driver/gpio.h>
#include <driver/spi_master.h>
#include <esp_timer.h>
#include "freertos/FreeRTOS.h"
#include "driver/spi_master.h"
enum WaitKind {
CHECK_IO,
WAIT_FOR_ANY_EVENT,
WAIT_FOR_TIMER
};
#ifdef __cplusplus
extern "C" {
#endif
void hal_esp32_configure_pins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1);
void hal_esp32_start_lmic_task(void);
void hal_esp32_stop_lmic_task(void);
class HAL_ESP32
{
public:
HAL_ESP32();
void hal_esp32_wake_up(void);
void hal_esp32_init_critical_section(void);
void hal_esp32_enter_critical_section(void);
void hal_esp32_leave_critical_section(void);
void configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1);
void init();
void startLMICTask();
void stopLMICTask();
void wakeUp();
void initCriticalSection();
void enterCriticalSection();
void leaveCriticalSection();
void hal_esp32_set_rssi_cal(int8_t rssi_cal);
void spiWrite(uint8_t cmd, const uint8_t *buf, size_t len);
void spiRead(uint8_t cmd, uint8_t *buf, size_t len);
uint8_t checkTimer(uint32_t osTime);
void sleep();
uint32_t waitUntil(uint32_t osTime);
TickType_t hal_esp32_get_timer_duration(void);
spi_host_device_t spiHost;
gpio_num_t pinNSS;
gpio_num_t pinRxTx;
gpio_num_t pinRst;
gpio_num_t pinDIO0;
gpio_num_t pinDIO1;
int8_t rssiCal;
/**
* Gets the time.
*
* The time is relative to boot time of the
* run when the device joined the TTN network.
*
* @return time (in seconds)
*/
uint32_t hal_esp32_get_time(void);
private:
static void lmicBackgroundTask(void* pvParameter);
static void dioIrqHandler(void* arg);
static void timerCallback(void *arg);
static int64_t osTimeToEspTime(int64_t espNow, uint32_t osTime);
void ioInit();
void spiInit();
void timerInit();
void setNextAlarm(int64_t time);
void armTimer(int64_t espNow);
void disarmTimer();
bool wait(WaitKind waitKind);
static TaskHandle_t lmicTask;
static uint32_t dioInterruptTime;
static uint8_t dioNum;
spi_device_handle_t spiHandle;
spi_transaction_t spiTransaction;
SemaphoreHandle_t mutex;
esp_timer_handle_t timer;
int64_t nextAlarm;
volatile bool runBackgroundTask;
};
extern HAL_ESP32 ttn_hal;
/**
* Sets the time.
*
* The time is relative to boot time of the
* run when the device joined the TTN network.
*
* @param time_val time (in seconds)
*/
void hal_esp32_set_time(uint32_t time_val);
#endif // _hal_esp32_h_
#ifdef __cplusplus
}
#endif
#endif // HAL_ESP32_H

View File

@ -169,7 +169,7 @@
// enable support for MCMD_DeviceTimeReq and MCMD_DeviceTimeAns
// this is always defined, and non-zero to enable it.
#if !defined(LMIC_ENABLE_DeviceTimeReq)
# define LMIC_ENABLE_DeviceTimeReq 0
# define LMIC_ENABLE_DeviceTimeReq 1
#endif
// LMIC_ENABLE_user_events
@ -187,9 +187,17 @@
#endif
// LMIC_ENABLE_long_messages
// LMIC certification requires that this be enabled.
#if !defined(LMIC_ENABLE_long_messages)
# define LMIC_ENABLE_long_messages 1 /* PARAM */
// LMIC certification requires full-length 255 frames, but to save RAM,
// a shorter maximum can be set. This controls both RX and TX buffers,
// so reducing this by 1 saves 2 bytes of RAM.
#if defined(LMIC_ENABLE_long_messages) && defined(LMIC_MAX_FRAME_LENGTH)
#error "Use only one of LMIC_ENABLE_long_messages or LMIC_MAX_FRAME_LENGTH"
#elif defined(LMIC_ENABLE_long_messages) && LMIC_ENABLE_long_messages == 0
# define LMIC_MAX_FRAME_LENGTH 64
#elif !defined(LMIC_MAX_FRAME_LENGTH)
# define LMIC_MAX_FRAME_LENGTH 255
#elif LMIC_MAX_FRAME_LENGTH > 255
#error "LMIC_MAX_FRAME_LENGTH cannot be larger than 255"
#endif
// LMIC_ENABLE_event_logging

View File

@ -133,7 +133,8 @@ void hal_failed (const char *file, u2_t line);
* set a custom hal failure handler routine. The default behaviour, defined in
* hal_failed(), is to halt by looping infintely.
*/
void hal_set_failure_handler(const hal_failure_handler_t* const);
void hal_set_failure_handler(hal_failure_handler_t* const);
// ttn-esp32: too much const
/*
* get the calibration value for radio_rssi
@ -173,7 +174,7 @@ void hal_pollPendingIRQs_helper();
void hal_processPendingIRQs(void);
/// \brief check for any pending interrupts: stub if interrupts are enabled.
static void inline hal_pollPendingIRQs(void)
static inline void hal_pollPendingIRQs(void)
{
#if !defined(LMIC_USE_INTERRUPTS)
hal_pollPendingIRQs_helper();

View File

@ -119,7 +119,7 @@ void os_wmsbf4 (xref2u1_t buf, u4_t v) {
#if !defined(os_getBattLevel)
u1_t os_getBattLevel (void) {
return MCMD_DEVS_BATT_NOINFO;
return LMIC.client.devStatusAns_battery;
}
#endif
@ -764,6 +764,9 @@ applyAdrRequests(
p4 = opts[oidx+4]; // ChMaskCtl, NbTrans
u1_t chpage = p4 & MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK; // channel page
// notice that we ignore map_ok except on the last setting.
// so LMICbandplan_mapChannels should report failure status, but do
// the work; if it fails, we'll back it out.
map_ok = LMICbandplan_mapChannels(chpage, chmap);
LMICOS_logEventUint32("applyAdrRequests: mapChannels", ((u4_t)chpage << 16)|(chmap << 0));
}
@ -1476,7 +1479,12 @@ ostime_t LMICcore_adjustForDrift (ostime_t delay, ostime_t hsym, rxsyms_t rxsyms
// a compile-time configuration. (In other words, assume that millis()
// clock is accurate to 0.1%.) You should never use clockerror to
// compensate for system-late problems.
u2_t const maxError = LMIC_kMaxClockError_ppm * MAX_CLOCK_ERROR / (1000 * 1000);
// note about compiler: The initializer for maxError is coded for
// maximum portability. On 16-bit systems, some compilers complain
// if we write x / (1000 * 1000). x / 1000 / 1000 uses constants,
// is generally acceptable so it can be optimized in compiler's own
// way.
u2_t const maxError = LMIC_kMaxClockError_ppm * MAX_CLOCK_ERROR / 1000 / 1000;
if (! LMIC_ENABLE_arbitrary_clock_error && clockerr > maxError)
{
clockerr = maxError;
@ -1616,21 +1624,9 @@ static bit_t processJoinAccept (void) {
// initDefaultChannels(0) for EU-like, nothing otherwise
LMICbandplan_joinAcceptChannelClear();
if (!LMICbandplan_hasJoinCFlist() && dlen > LEN_JA) {
// if no JoinCFList, we're supposed to continue
// the join per 2.2.5 of LoRaWAN regional 2.2.4
// https://github.com/mcci-catena/arduino-lmic/issues/19
} else if ( LMICbandplan_hasJoinCFlist() && dlen > LEN_JA ) {
dlen = OFF_CFLIST;
for( u1_t chidx=3; chidx<8; chidx++, dlen+=3 ) {
u4_t freq = LMICbandplan_convFreq(&LMIC.frame[dlen]);
if( freq ) {
LMIC_setupChannel(chidx, freq, 0, -1);
#if LMIC_DEBUG_LEVEL > 1
LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel, idx=%d, freq=%"PRIu32"\n", os_getTime(), chidx, freq);
#endif
}
}
// process the CFList if present
if (dlen == LEN_JAEXT) {
LMICbandplan_processJoinAcceptCFList();
}
// already incremented when JOIN REQ got sent off
@ -2822,6 +2818,7 @@ void LMIC_reset (void) {
void LMIC_init (void) {
LMIC.opmode = OP_SHUTDOWN;
LMIC.client.devStatusAns_battery = MCMD_DEVS_BATT_NOINFO;
LMICbandplan_init();
}
@ -2882,7 +2879,11 @@ dr_t LMIC_feasibleDataRateForFrame(dr_t dr, u1_t payloadSize) {
}
static bit_t isTxPathBusy(void) {
return (LMIC.opmode & (OP_TXDATA|OP_JOINING)) != 0;
return (LMIC.opmode & (OP_POLL | OP_TXDATA | OP_JOINING | OP_TXRXPEND)) != 0;
}
bit_t LMIC_queryTxReady (void) {
return ! isTxPathBusy();
}
static bit_t adjustDrForFrameIfNotBusy(u1_t len) {
@ -2902,6 +2903,10 @@ void LMIC_setTxData (void) {
}
void LMIC_setTxData_strict (void) {
if (isTxPathBusy()) {
return;
}
LMICOS_logEventUint32(__func__, ((u4_t)LMIC.pendTxPort << 24u) | ((u4_t)LMIC.pendTxConf << 16u) | (LMIC.pendTxLen << 0u));
LMIC.opmode |= OP_TXDATA;
if( (LMIC.opmode & OP_JOINING) == 0 ) {
@ -2920,7 +2925,7 @@ lmic_tx_error_t LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t conf
// send a message w/o callback; do not adjust data rate
lmic_tx_error_t LMIC_setTxData2_strict (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) {
if ( LMIC.opmode & OP_TXDATA ) {
if (isTxPathBusy()) {
// already have a message queued
return LMIC_ERROR_TX_BUSY;
}
@ -2940,7 +2945,7 @@ lmic_tx_error_t LMIC_setTxData2_strict (u1_t port, xref2u1_t data, u1_t dlen, u1
return LMIC_ERROR_TX_FAILED;
}
}
return 0;
return LMIC_ERROR_SUCCESS;
}
// send a message with callback; try to adjust data rate
@ -3101,3 +3106,47 @@ int LMIC_getNetworkTimeReference(lmic_time_reference_t *pReference) {
#endif // LMIC_ENABLE_DeviceTimeReq
return 0;
}
///
/// \brief set battery level to be returned by `DevStatusAns`.
///
/// \param uBattLevel is the 8-bit value to be returned. Per LoRaWAN 1.0.3 line 769,
/// this is \c MCMD_DEVS_EXT_POWER (0) if on external power,
/// \c MCMD_DEVS_NOINFO (255) if not able to measure battery level,
/// or a value in [ \c MCMD_DEVS_BATT_MIN, \c MCMD_DEVS_BATT_MAX ], numerically
/// [1, 254], to represent the charge state of the battery. Note that
/// this is not millivolts.
///
/// \returns
/// This function returns the previous value of the battery level.
///
/// \details
/// The LMIC maintains an idea of the current battery state, initially set to
/// \c MCMD_DEVS_NOINFO after the call to LMIC_init(). The appplication then calls
/// this function from time to time to update the battery level.
///
/// It is possible (in non-Arduino environments) to supply a local implementation
/// of os_getBatteryLevel(). In that case, it's up to the implementation to decide
/// whether to use the value supplied by this API.
///
/// This implementation was chosen to minimize the risk of a battery measurement
/// introducting breaking delays into the LMIC.
///
u1_t LMIC_setBatteryLevel(u1_t uBattLevel) {
const u1_t result = LMIC.client.devStatusAns_battery;
LMIC.client.devStatusAns_battery = uBattLevel;
return result;
}
///
/// \brief get battery level that is to be returned by `DevStatusAns`.
///
/// \returns
/// This function returns the saved value of the battery level.
///
/// \see LMIC_setBatteryLevel()
///
u1_t LMIC_getBatteryLevel(void) {
return LMIC.client.devStatusAns_battery;
}

View File

@ -1,7 +1,7 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2016 Matthijs Kooijman.
* Copyright (c) 2016-2020 MCCI Corporation.
* Copyright (c) 2016-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -96,7 +96,7 @@
extern "C"{
#endif
// LMIC version -- this is ths IBM LMIC version
// LMIC version -- this is the IBM LMIC version
#define LMIC_VERSION_MAJOR 1
#define LMIC_VERSION_MINOR 6
#define LMIC_VERSION_BUILD 1468577746
@ -105,7 +105,8 @@ extern "C"{
#define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \
((((major)*UINT32_C(1)) << 24) | (((minor)*UINT32_C(1)) << 16) | (((patch)*UINT32_C(1)) << 8) | (((local)*UINT32_C(1)) << 0))
#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(3, 2, 0, 0) /* v3.2.0 */
#define ARDUINO_LMIC_VERSION \
ARDUINO_LMIC_VERSION_CALC(4, 2, 0, 1) /* 4.2.0-1 */
#define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \
((((v)*UINT32_C(1)) >> 24u) & 0xFFu)
@ -119,10 +120,35 @@ extern "C"{
#define ARDUINO_LMIC_VERSION_GET_LOCAL(v) \
((v) & 0xFFu)
/// \brief convert a semantic version to an ordinal integer.
#define ARDUINO_LMIC_VERSION_TO_ORDINAL(v) \
(((v) & 0xFFFFFF00u) | (((v) - 1) & 0xFFu))
/// \brief compare two semantic versions
/// \return \c true if \p a is less than \p b (as a semantic version).
#define ARDUINO_LMIC_VERSION_COMPARE_LT(a, b) \
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) < ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
/// \brief compare two semantic versions
/// \return \c true if \p a is less than or equal to \p b (as a semantic version).
#define ARDUINO_LMIC_VERSION_COMPARE_LE(a, b) \
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) <= ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
/// \brief compare two semantic versions
/// \return \c true if \p a is greater than \p b (as a semantic version).
#define ARDUINO_LMIC_VERSION_COMPARE_GT(a, b) \
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) > ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
/// \brief compare two semantic versions
/// \return \c true if \p a is greater than or equal to \p b (as a semantic version).
#define ARDUINO_LMIC_VERSION_COMPARE_GE(a, b) \
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) >= ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
//! Only For Antenna Tuning Tests !
//#define CFG_TxContinuousMode 1
// since this was annouunced as the API variable, we keep it. But it's not used,
// since this was announced as the API variable, we keep it. But it's not used,
// MAX_LEN_FRAME is what the code uses.
enum { MAX_FRAME_LEN = MAX_LEN_FRAME }; //!< Library cap on max frame length
@ -131,10 +157,10 @@ enum { MAX_MISSED_BCNS = (2 * 60 * 60 + 127) / 128 }; //!< threshold for d
// note that we need 100 ppm timing accuracy for
// this, to keep the timing error to +/- 700ms.
enum { MAX_RXSYMS = 350 }; // Stop tracking beacon if sync error grows beyond this. A 0.4% clock error
// at SF9.125k means 512 ms; one sybol is 4.096 ms,
// at SF9.125k means 512 ms; one symbol is 4.096 ms,
// so this needs to be at least 125 for an STM32L0.
// And for 100ppm clocks and 2 hours of beacon misses,
// this needs to accomodate 1.4 seconds of error at
// this needs to accommodate 1.4 seconds of error at
// 4.096 ms/sym or at least 342 symbols.
enum { LINK_CHECK_CONT = 0 , // continue with this after reported dead link
@ -161,7 +187,7 @@ 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
ostime_t avail; // band is blocked until this time
};
TYPEDEF_xref2band_t; //!< \internal
@ -172,10 +198,8 @@ struct lmic_saved_adr_state_s {
#elif CFG_LMIC_US_like // US915 spectrum =================================================
enum { MAX_XCHANNELS = 2 }; // extra channels in RAM, channels 0-71 are immutable
struct lmic_saved_adr_state_s {
u2_t channelMap[(72+MAX_XCHANNELS+15)/16]; // enabled bits
u2_t channelMap[(72+15)/16]; // enabled bits
u2_t activeChannels125khz;
u2_t activeChannels500khz;
};
@ -435,7 +459,7 @@ struct lmic_client_data_s {
u2_t clockError; //! Inaccuracy in the clock. CLOCK_ERROR_MAX represents +/-100% error
/* finally, things that are (u)int8_t */
/* none at the moment */
u1_t devStatusAns_battery; //!< value to report in MCMD_DevStatusAns message.
};
/*
@ -531,10 +555,10 @@ struct lmic_t {
// bit map of enabled datarates for each channel
u2_t channelDrMap[MAX_CHANNELS];
u2_t channelMap;
u2_t channelShuffleMap;
#elif CFG_LMIC_US_like
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 channelMap[(72+15)/16]; // enabled bits
u2_t channelShuffleMap[(72+15)/16]; // enabled bits
u2_t activeChannels125khz;
u2_t activeChannels500khz;
#endif
@ -569,7 +593,10 @@ struct lmic_t {
u1_t txChnl; // channel for next TX
u1_t globalDutyRate; // max rate: 1/2^k
#if CFG_LMIC_US_like
u1_t txChnl_125kHz; ///< during joins on 500 kHz, the 125 kHz channel
/// that was last used.
#endif
u1_t upRepeat; // configured up repeat
s1_t adrTxPow; // ADR adjusted TX power
u1_t datarate; // current data rate
@ -659,6 +686,12 @@ bit_t LMIC_enableChannel(u1_t channel);
bit_t LMIC_disableSubBand(u1_t band);
bit_t LMIC_selectSubBand(u1_t band);
//! \brief get the number of (fixed) default channels before the programmable channels.
u1_t LMIC_queryNumDefaultChannels(void);
//! \brief check whether the LMIC is ready for a transmit packet
bit_t LMIC_queryTxReady(void);
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)
@ -705,6 +738,11 @@ int LMIC_getNetworkTimeReference(lmic_time_reference_t *pReference);
int LMIC_registerRxMessageCb(lmic_rxmessage_cb_t *pRxMessageCb, void *pUserData);
int LMIC_registerEventCb(lmic_event_cb_t *pEventCb, void *pUserData);
int LMIC_findNextChannel(uint16_t *, const uint16_t *, uint16_t, int);
u1_t LMIC_getBatteryLevel(void);
u1_t LMIC_setBatteryLevel(u1_t /* uBattLevel */);
// APIs for client half of compliance.
typedef u1_t lmic_compliance_rx_action_t;

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -35,6 +35,11 @@
//
// BEG: AS923 related stuff
//
enum {
AS923_REGION_TX_EIRP_MAX_DBM =
(LMIC_COUNTRY_CODE == LMIC_COUNTRY_CODE_JP) ? AS923_JP_TX_EIRP_MAX_DBM
: AS923_TX_EIRP_MAX_DBM
};
// see table in section 2.7.3
CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
@ -50,6 +55,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS
};
bit_t
LMICas923_validDR(dr_t dr) {
// use subtract here to avoid overflow
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
return 0;
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
}
// see table in 2.7.6 -- this assumes UplinkDwellTime = 0.
static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
59+5, // [0]
@ -122,7 +135,7 @@ static CONST_TABLE(s1_t, TXMAXEIRP)[16] = {
static int8_t LMICas923_getMaxEIRP(uint8_t mcmd_txparam) {
// if uninitialized, return default.
if (mcmd_txparam == 0xFF)
return AS923_TX_EIRP_MAX_DBM;
return AS923_REGION_TX_EIRP_MAX_DBM;
else
return TABLE_GET_S1(
TXMAXEIRP,
@ -191,7 +204,7 @@ void LMICas923_initDefaultChannels(bit_t join) {
}
LMIC.bands[BAND_CENTI].txcap = AS923_TX_CAP;
LMIC.bands[BAND_CENTI].txpow = AS923_TX_EIRP_MAX_DBM;
LMIC.bands[BAND_CENTI].txpow = AS923_REGION_TX_EIRP_MAX_DBM;
LMIC.bands[BAND_CENTI].lastchnl = os_getRndU1() % MAX_CHANNELS;
LMIC.bands[BAND_CENTI].avail = os_getTime();
}
@ -226,17 +239,29 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
return 1;
}
///
/// \brief query number of default channels.
///
u1_t LMIC_queryNumDefaultChannels() {
return NUM_DEFAULT_CHANNELS;
}
///
/// \brief LMIC_setupChannel for EU 868
///
/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
/// This routine is used internally for MAC commands, so we enforce
/// this for the extenal API as well.
///
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
// zero the band bits in freq, just in case.
freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel.
if (freq == 0)
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
// can't do anything to a default channel.
return 0;
}
bit_t fEnable = (freq != 0);
if (chidx >= MAX_CHANNELS)
@ -315,36 +340,109 @@ void LMICas923_setRx1Params(void) {
LMIC.rps = dndr2rps(LMIC.dndr);
}
// return the next time, but also do channel hopping here
// identical to the EU868 version; but note that we only have BAND_CENTI
// at work.
///
/// \brief change the TX channel given the desired tx time.
///
/// \param [in] now is the time at which we want to transmit. In fact, it's always
/// the current time.
///
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
/// selected channel.
///
/// \details
/// We scan all the bands, creating a mask of all enabled channels that are
/// feasible at the earliest possible time. We then randomly choose one from
/// that, updating the shuffle mask.
///
/// \note
/// identical to the EU868 version; but note that we only have BAND_CENTI
/// in AS923.
///
ostime_t LMICas923_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 << bi)) && mintime - LMIC.bands[bi].avail > 0)
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; ci++) {
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
chnl -= MAX_CHANNELS;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
return mintime;
}
}
if ((bmap &= ~(1 << band)) == 0) {
// No feasible channel found!
return mintime;
}
} while (1);
ostime_t mintime = now + /*8h*/sec2osticks(28800);
u2_t availMap;
u2_t feasibleMap;
u1_t bandMap;
// set mintime to the earliest time of all enabled channels
// (can't just look at bands); and for a given channel, we
// can't tell if we're ready till we've checked all possible
// avail times.
bandMap = 0;
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
u2_t chnlBit = 1 << chnl;
// none at any higher numbers?
if (LMIC.channelMap < chnlBit)
break;
// not enabled?
if ((LMIC.channelMap & chnlBit) == 0)
continue;
// not feasible?
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
continue;
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
u1_t const thisBandBit = 1 << band;
// already considered?
if ((bandMap & thisBandBit) != 0)
continue;
// consider this band.
bandMap |= thisBandBit;
// enabled, not considered, feasible: adjust the min time.
if ((s4_t)(mintime - LMIC.bands[band].avail) > 0)
mintime = LMIC.bands[band].avail;
}
// make a mask of candidates available for use
availMap = 0;
feasibleMap = 0;
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
u2_t chnlBit = 1 << chnl;
// none at any higher numbers?
if (LMIC.channelMap < chnlBit)
break;
// not enabled?
if ((LMIC.channelMap & chnlBit) == 0)
continue;
// not feasible?
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
continue;
// This channel is feasible. But might not be available.
feasibleMap |= chnlBit;
// not available yet?
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
if ((s4_t)(LMIC.bands[band].avail - mintime) > 0)
continue;
// ok: this is a candidate.
availMap |= chnlBit;
}
// find the next available chennel.
u2_t saveShuffleMap = LMIC.channelShuffleMap;
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availMap, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
// restore bits in the shuffleMap that were on, but might have reset
// if availMap was used to refresh shuffleMap. These are channels that
// are feasble but not yet candidates due to band saturation
LMIC.channelShuffleMap |= saveShuffleMap & feasibleMap & ~availMap;
if (candidateCh >= 0) {
// update the channel; otherwise we'll just use the
// most recent one.
LMIC.txChnl = candidateCh;
}
return mintime;
}
#if !defined(DISABLE_BEACONS)
@ -364,7 +462,7 @@ ostime_t LMICas923_nextJoinState(void) {
void
LMICas923_initJoinLoop(void) {
// LMIC.txParam is set to 0xFF by the central code at init time.
LMICeulike_initJoinLoop(NUM_DEFAULT_CHANNELS, /* adr dBm */ AS923_TX_EIRP_MAX_DBM);
LMICeulike_initJoinLoop(NUM_DEFAULT_CHANNELS, /* adr dBm */ AS923_REGION_TX_EIRP_MAX_DBM);
}
void

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -55,6 +55,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS
};
bit_t
LMICau915_validDR(dr_t dr) {
// use subtract here to avoid overflow
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
return 0;
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
}
static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 0,
61+5, 137+5, 250+5, 250+5, 250+5, 250+5 };
@ -144,7 +152,24 @@ u4_t LMICau915_convFreq(xref2cu1_t ptr) {
return freq;
}
// au915: no support for xchannels.
///
/// \brief query number of default channels.
///
/// \note
/// For AU, we have no programmable channels; all channels
/// are fixed. Return the total channel count.
///
u1_t LMIC_queryNumDefaultChannels() {
return 64 + 8;
}
///
/// \brief LMIC_setupChannel for AU915
///
/// \note there are no progammable channels for US915, so this API
/// always returns FALSE.
///
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
LMIC_API_PARAMETER(chidx);
LMIC_API_PARAMETER(freq);

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -166,6 +166,14 @@
# error "LMICbandplan_isDataRateFeasible() not defined by bandplan"
#endif
#if !defined(LMICbandplan_validDR)
# error "LMICbandplan_validDR() not defined by bandplan"
#endif
#if !defined(LMICbandplan_processJoinAcceptCFList)
# error "LMICbandplan_processJoinAcceptCFList() not defined by bandplan"
#endif
//
// Things common to lmic.c code
//
@ -228,4 +236,22 @@ ostime_t LMICcore_rndDelay(u1_t secSpan);
void LMICcore_setDrJoin(u1_t reason, u1_t dr);
ostime_t LMICcore_adjustForDrift(ostime_t delay, ostime_t hsym, rxsyms_t rxsyms_in);
// this has been exported to clients forever by lmic.h. including lorabase.h;
// but with multiband lorabase can't really safely do this; it's really an LMIC-ism.
// As are the rest of the static inlines..
///< \brief return non-zero if given DR is valid for this region.
static inline bit_t validDR (dr_t dr) { return LMICbandplan_validDR(dr); } // in range
///< \brief region-specific table mapping DR to RPS/CRC bits; index by dr+1
extern CONST_TABLE(u1_t, _DR2RPS_CRC)[];
static inline rps_t updr2rps (dr_t dr) { return (rps_t)TABLE_GET_U1(_DR2RPS_CRC, dr+1); }
static inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); }
static inline dr_t incDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+2)==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate
static inline dr_t decDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr )==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate
static inline dr_t assertDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)==ILLEGAL_RPS ? (dr_t)DR_DFLTMIN : dr; } // force into a valid DR
static inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps
#endif // _lmic_bandplan_h_

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -108,4 +108,8 @@ void LMICas923_updateTx(ostime_t txbeg);
ostime_t LMICas923_nextJoinTime(ostime_t now);
#define LMICbandplan_nextJoinTime(now) LMICas923_nextJoinTime(now)
#undef LMICbandplan_validDR
bit_t LMICas923_validDR(dr_t dr);
#define LMICbandplan_validDR(dr) LMICas923_validDR(dr)
#endif // _lmic_bandplan_as923_h_

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -66,4 +66,8 @@ void LMICau915_setRx1Params(void);
void LMICau915_updateTx(ostime_t txbeg);
#define LMICbandplan_updateTx(txbeg) LMICau915_updateTx(txbeg)
#undef LMICbandplan_validDR
bit_t LMICau915_validDR(dr_t dr);
#define LMICbandplan_validDR(dr) LMICau915_validDR(dr)
#endif // _lmic_bandplan_au915_h_

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -88,4 +88,8 @@ ostime_t LMICeu868_nextJoinTime(ostime_t now);
void LMICeu868_setRx1Params(void);
#define LMICbandplan_setRx1Params() LMICeu868_setRx1Params()
#undef LMICbandplan_validDR
bit_t LMICeu868_validDR(dr_t dr);
#define LMICbandplan_validDR(dr) LMICeu868_validDR(dr)
#endif // _lmic_eu868_h_

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -81,4 +81,8 @@ void LMICin866_initDefaultChannels(bit_t join);
void LMICin866_setRx1Params(void);
#define LMICbandplan_setRx1Params() LMICin866_setRx1Params()
#undef LMICbandplan_validDR
bit_t LMICin866_validDR(dr_t dr);
#define LMICbandplan_validDR(dr) LMICin866_validDR(dr)
#endif // _lmic_bandplan_in866_h_

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -88,4 +88,8 @@ void LMICkr920_setRx1Params(void);
void LMICkr920_updateTx(ostime_t txbeg);
#define LMICbandplan_updateTx(t) LMICkr920_updateTx(t)
#undef LMICbandplan_validDR
bit_t LMICkr920_validDR(dr_t dr);
#define LMICbandplan_validDR(dr) LMICkr920_validDR(dr)
#endif // _lmic_kr920_h_

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -66,4 +66,8 @@ void LMICus915_setRx1Params(void);
void LMICus915_updateTx(ostime_t txbeg);
#define LMICbandplan_updateTx(txbeg) LMICus915_updateTx(txbeg)
#undef LMICbandplan_validDR
bit_t LMICus915_validDR(dr_t dr);
#define LMICbandplan_validDR(dr) LMICus915_validDR(dr)
#endif // _lmic_bandplan_us915_h_

View File

@ -0,0 +1,217 @@
/*
Module: lmic_channelshuffle.c
Function:
Channel scheduling without replacement.
Copyright and License:
This file copyright (C) 2021 by
MCCI Corporation
3520 Krums Corners Road
Ithaca, NY 14850
See accompanying LICENSE file for copyright and license information.
Author:
Terry Moore, MCCI Corporation April 2021
*/
#include "lmic.h"
#include <string.h>
/****************************************************************************\
|
| Manifest constants and local declarations.
|
\****************************************************************************/
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries);
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum);
/****************************************************************************\
|
| Read-only data.
|
\****************************************************************************/
/****************************************************************************\
|
| Variables.
|
\****************************************************************************/
/*
Name: LMIC_findNextChannel()
Function:
Scan a shuffle mask, and select a channel (without replacement).
Definition:
int LMIC_findNextChannel(
uint16_t *pShuffleMask,
const uint16_t *pEnableMask,
uint16_t nEntries,
int lastChannel
);
Description:
pShuffleMask and pEnableMask are bit vectors. Channels correspond to
bits in little-endian order; entry [0] has channels 0 through 15, entry
[1] channels 16 through 31, and so forth. nEntries specifies the number
of entries in the mask vectors. The enable mask is 1 for a given channel
if that channel is eligible for selection, 0 otherwise.
This routine selects channels from the shuffle mask until all entries
are exhausted; it then refreshes the shuffle mask from the enable mask.
If it refreshes the channel mask, lastChannel is taken as a channel number
that is to be avoided in the next selection. (This is to avoid back-to-back
use of a channel across a refresh boundary.) Otherwise lastChannel is
ignored. This avoidance can be suppresed by setting lastChannel to -1.
If only one channel is enabled, lastChannel is also ignored. If lastChannel
is actually disabled, lastChannel is also ignored.
Returns:
A channel number, in 0 .. nEntries-1, or -1 if the enable mask is
identically zero.
Notes:
This routine is somewhat optimized for AVR processors, which don't have
multi-bit shifts.
*/
int LMIC_findNextChannel(
uint16_t *pShuffleMask,
const uint16_t *pEnableMask,
uint16_t nEntries,
int lastChannel
) {
unsigned nSet16;
uint16_t saveLastChannelVal;
// in case someone has changed the enable mask, update
// the shuffle mask so there are no disable bits set.
for (unsigned i = 0; i < nEntries; ++i) {
pShuffleMask[i] &= pEnableMask[i];
}
// count the set bits in the shuffle mask (with a factor of 16 for speed)
nSet16 = sidewaysSum16(pShuffleMask, nEntries);
// if zero, copy the enable mask to the shuffle mask, and recount
if (nSet16 == 0) {
memcpy(pShuffleMask, pEnableMask, nEntries * sizeof(*pShuffleMask));
nSet16 = sidewaysSum16(pShuffleMask, nEntries);
} else {
// don't try to skip the last channel because it can't be chosen.
lastChannel = -1;
}
// if still zero, return -1.
if (nSet16 == 0) {
return -1;
}
// if we have to skip a channel, and we have more than one choice, turn off
// the last channel bit. Post condition: if we really clered a bit,
// saveLastChannelVal will be non-zero.
saveLastChannelVal = 0;
if (nSet16 > 16 && lastChannel >= 0 && lastChannel <= (int)(nEntries * 16)) {
uint16_t const saveLastChannelMask = (1 << (lastChannel & 0xF));
saveLastChannelVal = pShuffleMask[lastChannel >> 4] & saveLastChannelMask;
pShuffleMask[lastChannel >> 4] &= ~saveLastChannelMask;
// if we cleared a bit, reduce the count.
if (saveLastChannelVal > 0)
nSet16 -= 16;
}
if (saveLastChannelVal == 0) {
// We didn't eliminate a channel, so we don't have to worry.
lastChannel = -1;
}
// get a random number
unsigned choice = os_getRndU2() % ((uint16_t)nSet16 >> 4);
// choose a bit based on set bit
unsigned channel = findNthSetBit(pShuffleMask, choice);
pShuffleMask[channel / 16] ^= (1 << (channel & 0xF));
// handle channel skip
if (lastChannel >= 0) {
pShuffleMask[lastChannel >> 4] |= saveLastChannelVal;
}
return channel;
}
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries) {
unsigned result;
result = 0;
for (; nEntries > 0; --nEntries, ++pMask)
{
uint16_t v = *pMask;
// the following is an adaptation of Knuth 7.1.3 (62). To avoid
// lots of shifts (slow on AVR, and code intensive) and table lookups,
// we sum popc * 16, then divide by 16.
// sum adjacent bits, making a series of 2-bit sums
v = v - ((v >> 1) & 0x5555u);
v = (v & 0x3333u) + ((v >> 2) & 0x3333u);
// this assumes multiplies are essentialy free;
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u);
// Accumulate result, but note it's times 16.
// AVR compiler should optimize the x8 shift.
result += (v & 0xFF) + (v >> 8);
}
//
return result;
}
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum) {
unsigned result;
result = 0;
bitnum = bitnum * 16;
for (;; result += 16) {
uint16_t m = *pMask++;
if (m == 0)
continue;
uint16_t v = m - ((m >> 1) & 0x5555u);
v = (v & 0x3333u) + ((v >> 2) & 0x3333u);
// this assumes multiplies are essentialy free;
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u);
// Accumulate result, but note it's times 16.
// AVR compiler should optimize the x8 shift.
v = (v & 0xFF) + (v >> 8);
if (v <= bitnum)
bitnum -= v;
else {
// the selected bit is in this word. We need to count.
while (bitnum > 0) {
m &= m - 1;
bitnum -= 16;
}
// now the lsb of m is our choice.
// get a mask, then use Knuth 7.1.3 (59) to find the
// bit number.
m &= -m;
result += ((m & 0x5555u) ? 0 : 1)
+ ((m & 0x3333u) ? 0 : 2)
+ ((m & 0x0F0Fu) ? 0 : 4)
+ ((m & 0x00FFu) ? 0 : 8)
;
break;
}
}
return result;
}

View File

@ -284,7 +284,7 @@ static void evMessage(
break;
}
case LORAWAN_COMPLIANCE_CMD_LINK: {
// not clear what this request does.
// we are required to initiate a Link
break;
}
case LORAWAN_COMPLIANCE_CMD_JOIN: {

View File

@ -118,30 +118,6 @@ Revision history:
(1 << LMIC_REGION_in866) | \
0)
//
// Our input is a -D of one of CFG_eu868, CFG_us915, CFG_as923, CFG_au915, CFG_in866
// More will be added in the the future. So at this point we create CFG_region with
// following values. These are in order of the sections in the manual. Not all of the
// below are supported yet.
//
// CFG_as923jp is treated as a special case of CFG_as923, so it's not included in
// the below.
//
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
//
# define CFG_LMIC_REGION_MASK \
((defined(CFG_eu868) << LMIC_REGION_eu868) | \
(defined(CFG_us915) << LMIC_REGION_us915) | \
(defined(CFG_cn783) << LMIC_REGION_cn783) | \
(defined(CFG_eu433) << LMIC_REGION_eu433) | \
(defined(CFG_au915) << LMIC_REGION_au915) | \
(defined(CFG_cn490) << LMIC_REGION_cn490) | \
(defined(CFG_as923) << LMIC_REGION_as923) | \
(defined(CFG_kr920) << LMIC_REGION_kr920) | \
(defined(CFG_in866) << LMIC_REGION_in866) | \
0)
// the selected region.
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
@ -171,11 +147,94 @@ Revision history:
# define CFG_region 0
#endif
// a bitmask of EU-like regions -- these are regions which have up to 16
// channels indidually programmable via downloink.
//
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
// LMIC_CFG_*_ENA -- either 1 or 0 based on whether that region
// is enabled. Note: these must be after the code that special-cases
// CFG_as923jp.
#if defined(CFG_eu868)
# define LMIC_CFG_eu868_ENA 1
#else
# define LMIC_CFG_eu868_ENA 0
#endif
#if defined(CFG_us915)
# define LMIC_CFG_us915_ENA 1
#else
# define LMIC_CFG_us915_ENA 0
#endif
#if defined(CFG_cn783)
# define LMIC_CFG_cn783_ENA 1
#else
# define LMIC_CFG_cn783_ENA 0
#endif
#if defined(CFG_eu433)
# define LMIC_CFG_eu433_ENA 1
#else
# define LMIC_CFG_eu433_ENA 0
#endif
#if defined(CFG_au915)
# define LMIC_CFG_au915_ENA 1
#else
# define LMIC_CFG_au915_ENA 0
#endif
#if defined(CFG_cn490)
# define LMIC_CFG_cn490_ENA 1
#else
# define LMIC_CFG_cn490_ENA 0
#endif
#if defined(CFG_as923)
# define LMIC_CFG_as923_ENA 1
#else
# define LMIC_CFG_as923_ENA 0
#endif
#if defined(CFG_kr920)
# define LMIC_CFG_kr920_ENA 1
#else
# define LMIC_CFG_kr920_ENA 0
#endif
#if defined(CFG_in866)
# define LMIC_CFG_in866_ENA 1
#else
# define LMIC_CFG_in866_ENA 0
#endif
/// \brief Bitmask of configured regions
///
/// Our input is a -D of one of CFG_eu868, CFG_us915, CFG_as923, CFG_au915, CFG_in866
/// More will be added in the the future. So at this point we create CFG_region with
/// following values. These are in order of the sections in the manual. Not all of the
/// below are supported yet.
///
/// CFG_as923jp is treated as a special case of CFG_as923, so it's not included in
/// the below.
///
/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
/// user-editable.
///
# define CFG_LMIC_REGION_MASK \
((LMIC_CFG_eu868_ENA << LMIC_REGION_eu868) | \
(LMIC_CFG_us915_ENA << LMIC_REGION_us915) | \
(LMIC_CFG_cn783_ENA << LMIC_REGION_cn783) | \
(LMIC_CFG_eu433_ENA << LMIC_REGION_eu433) | \
(LMIC_CFG_au915_ENA << LMIC_REGION_au915) | \
(LMIC_CFG_cn490_ENA << LMIC_REGION_cn490) | \
(LMIC_CFG_as923_ENA << LMIC_REGION_as923) | \
(LMIC_CFG_kr920_ENA << LMIC_REGION_kr920) | \
(LMIC_CFG_in866_ENA << LMIC_REGION_in866) | \
0)
/// \brief a bitmask of EU-like regions
///
/// EU-like regions have up to 16 channels individually programmable via downlink.
///
/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
/// user-editable.
#define CFG_LMIC_EU_like_MASK ( \
(1 << LMIC_REGION_eu868) | \
/* (1 << LMIC_REGION_us915) | */ \
@ -188,12 +247,14 @@ Revision history:
(1 << LMIC_REGION_in866) | \
0)
// a bitmask of` US-like regions -- these are regions with 64 fixed 125 kHz channels
// overlaid by 8 500 kHz channels. The channel frequencies can't be changed, but
// subsets of channels can be selected via masks.
//
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
/// \brief bitmask of` US-like regions
///
/// US-like regions have 64 fixed 125-kHz channels overlaid by 8 500-kHz
/// channels. The channel frequencies can't be changed, but
/// subsets of channels can be selected via masks.
///
/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
/// user-editable.
#define CFG_LMIC_US_like_MASK ( \
/* (1 << LMIC_REGION_eu868) | */ \
(1 << LMIC_REGION_us915) | \
@ -211,16 +272,19 @@ Revision history:
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
//
/// \brief true if configured region is EU-like, false otherwise.
#define CFG_LMIC_EU_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_EU_like_MASK))
/// \brief true if configured region is US-like, false otherwise.
#define CFG_LMIC_US_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_US_like_MASK))
//
// The supported LMIC LoRaWAAN spec versions. These need to be numerically ordered,
// The supported LMIC LoRaWAN spec versions. These need to be numerically ordered,
// so that we can (for example) compare
//
// LMIC_LORAWAN_SPEC_VERSION < LMIC_LORAWAN_SPEC_VERSION_1_0_3.
//
#define LMIC_LORAWAN_SPEC_VERSION_1_0_2 0x01000200u
#define LMIC_LORAWAN_SPEC_VERSION_1_0_3 0x01000300u
#define LMIC_LORAWAN_SPEC_VERSION_1_0_2 0x01000200u /// Refers to LoRaWAN spec 1.0.2
#define LMIC_LORAWAN_SPEC_VERSION_1_0_3 0x01000300u /// Refers to LoRaWAN spec 1.0.3
#endif /* _LMIC_CONFIG_PRECONDITIONS_H_ */

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -49,6 +49,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS
};
bit_t
LMICeu868_validDR(dr_t dr) {
// use subtract here to avoid overflow
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
return 0;
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
}
static CONST_TABLE(u1_t, maxFrameLens)[] = {
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 250+5
};
@ -140,17 +148,28 @@ static CONST_TABLE(u4_t, bandAssignments)[] = {
865000000 /* .. 868400000 */ | BAND_CENTI,
};
///
/// \brief query number of default channels.
///
u1_t LMIC_queryNumDefaultChannels() {
return NUM_DEFAULT_CHANNELS;
}
///
/// \brief LMIC_setupChannel for EU 868
///
/// \note according to LoRaWAN 1.0.3 section 5.6, "the acceptable range
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
/// This routine is used internally for MAC commands, so we enforce
/// this for the extenal API as well.
///
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
// zero the band bits in freq, just in case.
freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel.
if (freq == 0)
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
// can't do anything to a default channel.
return 0;
}
bit_t fEnable = (freq != 0);
if (chidx >= MAX_CHANNELS)
@ -204,32 +223,111 @@ ostime_t LMICeu868_nextJoinTime(ostime_t time) {
return time;
}
///
/// \brief change the TX channel given the desired tx time.
///
/// \param [in] now is the time at which we want to transmit. In fact, it's always
/// the current time.
///
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
/// selected channel.
///
/// \details
/// We scan all the channels, creating a mask of all enabled channels that are
/// feasible at the earliest possible time. We then randomly choose one from
/// that, updating the shuffle mask.
///
/// One sublety is that we have to cope with an artifact of the shuffler.
/// It will zero out bits for candidates that are real candidates, but
/// not in the time window, and not consider them as early as it should.
/// So we keep a mask of all feasible channels, and make sure that they
/// remain set in the shuffle mask if appropriate.
///
ostime_t LMICeu868_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 << bi)) && mintime - LMIC.bands[bi].avail > 0)
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; ci++) {
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
chnl -= MAX_CHANNELS;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
return mintime;
}
}
if ((bmap &= ~(1 << band)) == 0) {
// No feasible channel found!
return mintime;
}
} while (1);
ostime_t mintime = now + /*8h*/sec2osticks(28800);
u2_t availMap;
u2_t feasibleMap;
u1_t bandMap;
// set mintime to the earliest time of all enabled channels
// (can't just look at bands); and for a given channel, we
// can't tell if we're ready till we've checked all possible
// avail times.
bandMap = 0;
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
u2_t chnlBit = 1 << chnl;
// none at any higher numbers?
if (LMIC.channelMap < chnlBit)
break;
// not enabled?
if ((LMIC.channelMap & chnlBit) == 0)
continue;
// not feasible?
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
continue;
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
u1_t const thisBandBit = 1 << band;
// already considered?
if ((bandMap & thisBandBit) != 0)
continue;
// consider this band.
bandMap |= thisBandBit;
// enabled, not considered, feasible: adjust the min time.
if ((s4_t)(mintime - LMIC.bands[band].avail) > 0)
mintime = LMIC.bands[band].avail;
}
// make a mask of candidates available for use
availMap = 0;
feasibleMap = 0;
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
u2_t chnlBit = 1 << chnl;
// none at any higher numbers?
if (LMIC.channelMap < chnlBit)
break;
// not enabled?
if ((LMIC.channelMap & chnlBit) == 0)
continue;
// not feasible?
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
continue;
// This channel is feasible. But might not be available.
feasibleMap |= chnlBit;
// not available yet?
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
if ((s4_t)(LMIC.bands[band].avail - mintime) > 0)
continue;
// ok: this is a candidate.
availMap |= chnlBit;
}
// find the next available chennel.
u2_t saveShuffleMap = LMIC.channelShuffleMap;
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availMap, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
// restore bits in the shuffleMap that were on, but might have reset
// if availMap was used to refresh shuffleMap. These are channels that
// are feasble but not yet candidates due to band saturation
LMIC.channelShuffleMap |= saveShuffleMap & feasibleMap & ~availMap;
if (candidateCh >= 0) {
// update the channel; otherwise we'll just use the
// most recent one.
LMIC.txChnl = candidateCh;
}
return mintime;
}

View File

@ -128,7 +128,9 @@ void LMICeulike_initJoinLoop(uint8_t nDefaultChannels, s1_t adrTxPow) {
#if CFG_TxContinuousMode
LMIC.txChnl = 0
#else
LMIC.txChnl = os_getRndU1() % nDefaultChannels;
uint16_t enableMap = (1 << nDefaultChannels) - 1;
LMIC.channelShuffleMap = enableMap;
LMIC.txChnl = LMIC_findNextChannel(&LMIC.channelShuffleMap, &enableMap, 1, -1);
#endif
LMIC.adrTxPow = adrTxPow;
// TODO(tmm@mcci.com) don't use EU directly, use a table. That
@ -166,12 +168,11 @@ void LMICeulike_updateTx(ostime_t txbeg) {
//
ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
u1_t failed = 0;
u2_t enableMap = (1 << nDefaultChannels) - 1;
// Try each default channel with same DR
// If all fail try next lower datarate
if (++LMIC.txChnl == /* NUM_DEFAULT_CHANNELS */ nDefaultChannels)
LMIC.txChnl = 0;
if ((++LMIC.txCnt % nDefaultChannels) == 0) {
if (LMIC.channelShuffleMap == 0) {
// Lower DR every nth try (having all default channels with same DR)
//
// TODO(tmm@mcci.com) add new DR_REGION_JOIN_MIN instead of LORAWAN_DR0;
@ -179,7 +180,6 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
// the failed flag here. This will cause the outer caller to take the
// appropriate join path. Or add new LMICeulike_GetLowestJoinDR()
//
// TODO(tmm@mcci.com) - see above; please remove regional dependency from this file.
#if CFG_region == LMIC_REGION_as923
// in the join of AS923 v1.1 or older, only DR2 is used.
@ -187,15 +187,22 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
LMIC.datarate = AS923_DR_SF10;
failed = 1;
#else
if (LMIC.datarate == LORAWAN_DR0)
if (LMIC.datarate == LORAWAN_DR0) {
failed = 1; // we have tried all DR - signal EV_JOIN_FAILED
else {
} else {
LMICcore_setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
}
#endif
}
// Clear NEXTCHNL because join state engine controls channel hopping
// find new channel, avoiding repeats.
int newCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &enableMap, 1, LMIC.txChnl);
if (newCh >= 0)
LMIC.txChnl = newCh;
// Clear OP_NEXTCHNL because join state engine controls channel hopping
LMIC.opmode &= ~OP_NEXTCHNL;
// Move txend to randomize synchronized concurrent joins.
// Duty cycle is based on txend.
ostime_t const time = LMICbandplan_nextJoinTime(os_getTime());
@ -220,6 +227,27 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
}
#endif // !DISABLE_JOIN
#if !defined(DISABLE_JOIN)
void LMICeulike_processJoinAcceptCFList(void) {
if ( LMICbandplan_hasJoinCFlist() &&
LMIC.frame[OFF_CFLIST + 15] == LORAWAN_JoinAccept_CFListType_FREQUENCIES) {
u1_t dlen;
u1_t nDefault = LMIC_queryNumDefaultChannels();
dlen = OFF_CFLIST;
for( u1_t chidx = nDefault; chidx < nDefault + 5; chidx++, dlen+=3 ) {
u4_t freq = LMICbandplan_convFreq(&LMIC.frame[dlen]);
if( freq ) {
LMIC_setupChannel(chidx, freq, 0, -1);
#if LMIC_DEBUG_LEVEL > 1
LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel, idx=%d, freq=%"PRIu32"\n", os_getTime(), chidx, freq);
#endif
}
}
}
}
#endif // !DISABLE_JOIN
void LMICeulike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) {
os_copyMem(
pStateBuffer->channelFreq,

View File

@ -66,6 +66,11 @@ enum { BAND_MILLI = 0, BAND_CENTI = 1, BAND_DECI = 2, BAND_AUX = 3 };
// there's a CFList on joins for EU-like plans
#define LMICbandplan_hasJoinCFlist() (1)
/// \brief process CFLists from JoinAccept for EU-like regions
void LMICeulike_processJoinAcceptCFList(void);
/// \brief by default, EU-like plans use LMICeulike_processJoinAcceptCFList
#define LMICbandplan_processJoinAcceptCFList LMICeulike_processJoinAcceptCFList
#define LMICbandplan_advanceBeaconChannel() \
do { /* nothing */ } while (0)

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -49,6 +49,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS
};
bit_t
LMICin866_validDR(dr_t dr) {
// use subtract here to avoid overflow
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
return 0;
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
}
static CONST_TABLE(u1_t, maxFrameLens)[] = {
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 0, 250+5
};
@ -134,17 +142,27 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
return 1;
}
///
/// \brief query number of default channels.
///
u1_t LMIC_queryNumDefaultChannels() {
return NUM_DEFAULT_CHANNELS;
}
///
/// \brief LMIC_setupChannel for IN region
///
/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
/// This routine is used internally for MAC commands, so we enforce
/// this for the extenal API as well.
///
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
// zero the band bits in freq, just in case.
freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel.
if (freq == 0)
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
return 0;
}
bit_t fEnable = (freq != 0);
if (chidx >= MAX_CHANNELS)
@ -173,28 +191,44 @@ u4_t LMICin866_convFreq(xref2cu1_t ptr) {
return freq;
}
// return the next time, but also do channel hopping here
// since there's no duty cycle limitation, and no dwell limitation,
// we simply loop through the channels sequentially.
///
/// \brief change the TX channel given the desired tx time.
///
/// \param [in] now is the time at which we want to transmit. In fact, it's always
/// the current time.
///
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
/// selected channel.
///
/// \details
/// We scan all the bands, creating a mask of all enabled channels that are
/// feasible at the earliest possible time. We then randomly choose one from
/// that, updating the shuffle mask.
///
/// Since there's no duty cycle limitation, and no dwell limitation,
/// we just choose a channel from the shuffle and return the current time.
///
ostime_t LMICin866_nextTx(ostime_t now) {
const u1_t band = BAND_MILLI;
uint16_t availmask;
for (u1_t ci = 0; ci < MAX_CHANNELS; ci++) {
// Find next channel in given band
u1_t chnl = LMIC.bands[band].lastchnl;
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) {
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
chnl -= MAX_CHANNELS;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
return now;
}
}
// scan all the enabled channels and make a mask of candidates
availmask = 0;
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
// not enabled?
if ((LMIC.channelMap & (1 << chnl)) == 0)
continue;
// not feasible?
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
continue;
availmask |= 1 << chnl;
}
// no enabled channel found! just use the last channel.
// now: calculate the mask
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availmask, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
if (candidateCh >= 0) {
// update the channel.
LMIC.txChnl = candidateCh;
}
return now;
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -47,6 +47,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS, // [6]
};
bit_t
LMICkr920_validDR(dr_t dr) {
// use subtract here to avoid overflow
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
return 0;
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
}
static CONST_TABLE(u1_t, maxFrameLens)[] = {
59+5, 59+5, 59+5, 123+5, 250+5, 250+5
};
@ -145,17 +153,28 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
return 1;
}
///
/// \brief query number of default channels.
///
u1_t LMIC_queryNumDefaultChannels() {
return NUM_DEFAULT_CHANNELS;
}
///
/// \brief LMIC_setupChannel for KR920
///
/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
/// This routine is used internally for MAC commands, so we enforce
/// this for the extenal API as well.
///
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
// zero the band bits in freq, just in case.
freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel.
if (freq == 0)
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
return 0;
}
bit_t fEnable = (freq != 0);
if (chidx >= MAX_CHANNELS)
@ -184,28 +203,44 @@ u4_t LMICkr920_convFreq(xref2cu1_t ptr) {
return freq;
}
// return the next time, but also do channel hopping here
// since there's no duty cycle limitation, and no dwell limitation,
// we simply loop through the channels sequentially.
///
/// \brief change the TX channel given the desired tx time.
///
/// \param [in] now is the time at which we want to transmit. In fact, it's always
/// the current time.
///
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
/// selected channel.
///
/// \details
/// We scan all the bands, creating a mask of all enabled channels that are
/// feasible at the earliest possible time. We then randomly choose one from
/// that, updating the shuffle mask.
///
/// Since there's no duty cycle limitation, and no dwell limitation,
/// we just choose a channel from the shuffle and return the current time.
///
ostime_t LMICkr920_nextTx(ostime_t now) {
const u1_t band = BAND_MILLI;
uint16_t availmask;
for (u1_t ci = 0; ci < MAX_CHANNELS; ci++) {
// Find next channel in given band
u1_t chnl = LMIC.bands[band].lastchnl;
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) {
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
chnl -= MAX_CHANNELS;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
return now;
}
}
// scan all the enabled channels and make a mask of candidates
availmask = 0;
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
// not enabled?
if ((LMIC.channelMap & (1 << chnl)) == 0)
continue;
// not feasible?
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
continue;
availmask |= 1 << chnl;
}
// no enabled channel found! just use the last channel.
// now: calculate the mask
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availmask, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
if (candidateCh >= 0) {
// update the channel.
LMIC.txChnl = candidateCh;
}
return now;
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation.
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -55,6 +55,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS // [14]
};
bit_t
LMICus915_validDR(dr_t dr) {
// use subtract here to avoid overflow
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
return 0;
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
}
static CONST_TABLE(u1_t, maxFrameLens)[] = {
19+5, 61+5, 133+5, 250+5, 250+5, 0, 0,0,
61+5, 133+5, 250+5, 250+5, 250+5, 250+5
@ -99,21 +107,34 @@ u4_t LMICus915_convFreq(xref2cu1_t ptr) {
return freq;
}
///
/// \brief query number of default channels.
///
/// For US, we have no programmable channels; all channels
/// are fixed. Return the total channel count.
///
u1_t LMIC_queryNumDefaultChannels() {
return 64 + 8;
}
///
/// \brief LMIC_setupChannel for US915
///
/// \note there are no progammable channels for US915, so this API
/// always returns FALSE.
///
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
LMIC_API_PARAMETER(chidx);
LMIC_API_PARAMETER(freq);
LMIC_API_PARAMETER(drmap);
LMIC_API_PARAMETER(band);
if (chidx < 72 || chidx >= 72 + MAX_XCHANNELS)
return 0; // channels 0..71 are hardwired
LMIC.xchFreq[chidx - 72] = freq;
// TODO(tmm@mcci.com): don't use US SF directly, use something from the LMIC context or a static const
LMIC.xchDrMap[chidx - 72] = drmap == 0 ? DR_RANGE_MAP(US915_DR_SF10, US915_DR_SF8C) : drmap;
LMIC.channelMap[chidx >> 4] |= (1 << (chidx & 0xF));
return 1;
return 0; // channels 0..71 are hardwired
}
bit_t LMIC_disableChannel(u1_t channel) {
bit_t result = 0;
if (channel < 72 + MAX_XCHANNELS) {
if (channel < 72) {
if (ENABLED_CHANNEL(channel)) {
result = 1;
if (IS_CHANNEL_125khz(channel))
@ -128,7 +149,7 @@ bit_t LMIC_disableChannel(u1_t channel) {
bit_t LMIC_enableChannel(u1_t channel) {
bit_t result = 0;
if (channel < 72 + MAX_XCHANNELS) {
if (channel < 72) {
if (!ENABLED_CHANNEL(channel)) {
result = 1;
if (IS_CHANNEL_125khz(channel))
@ -197,13 +218,7 @@ void LMICus915_updateTx(ostime_t txbeg) {
} else {
// at 500kHz bandwidth, we're allowed more power.
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];
}
LMIC.freq = US915_500kHz_UPFBASE + (chnl - 64)*US915_500kHz_UPFSTEP;
}
// Update global duty cycle stats

View File

@ -36,36 +36,34 @@
# error "LMICuslike_getFirst500kHzDR() not defined by bandplan"
#endif
static void setNextChannel(uint start, uint end, uint count) {
///
/// \brief set LMIC.txChan to the next selected channel.
///
/// \param [in] start first channel number
/// \param [in] end one past the last channel number
///
/// \details
/// We set up a call to LMIC_findNextChannel using the channelShuffleMap and
/// the channelEnableMap. We subset these based on start and end. \p start must
/// be a multiple of 16.
///
static void setNextChannel(uint16_t start, uint16_t end, uint16_t count) {
ASSERT(count>0);
ASSERT(start<end);
ASSERT(count <= (end - start));
// We used to pick a random channel once and then just increment. That is not per spec.
// Now we use a new random number each time, because they are not very expensive.
// Regarding the algo below, we cannot pick a number and scan until we hit an enabled channel.
// That would result in the first enabled channel following a set of disabled ones
// being used more frequently than the other enabled channels.
ASSERT((start & 0xF) == 0);
uint16_t const mapStart = start >> 4;
uint16_t const mapEntries = (end - start + 15) >> 4;
// Last used channel is in range. It is not a candidate, per spec.
uint lastTxChan = LMIC.txChnl;
if (start <= lastTxChan && lastTxChan<end &&
// Adjust count only if still enabled. Otherwise, no chance of selection.
ENABLED_CHANNEL(lastTxChan)) {
--count;
if (count == 0) {
return; // Only one active channel, so keep using it.
}
}
int candidate = start + LMIC_findNextChannel(
LMIC.channelShuffleMap + mapStart,
LMIC.channelMap + mapStart,
mapEntries,
LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl
);
uint nth = os_getRndU1() % count;
for (u1_t chnl = start; chnl<end; chnl++) {
// Scan for nth enabled channel that is not the last channel used
if (chnl != lastTxChan && ENABLED_CHANNEL(chnl) && (nth--) == 0) {
LMIC.txChnl = chnl;
return;
}
}
// No feasible channel found! Keep old one.
if (candidate >= 0)
LMIC.txChnl = candidate;
}
@ -87,8 +85,11 @@ void LMICuslike_initDefaultChannels(bit_t fJoin) {
for (u1_t i = 0; i<4; i++)
LMIC.channelMap[i] = 0xFFFF;
LMIC.channelMap[4] = 0x00FF;
os_clearMem(LMIC.channelShuffleMap, sizeof(LMIC.channelShuffleMap));
LMIC.activeChannels125khz = 64;
LMIC.activeChannels500khz = 8;
// choose a random channel.
LMIC.txChnl = 0xFF;
}
// verify that a given setting is permitted
@ -98,6 +99,9 @@ bit_t LMICuslike_canMapChannels(u1_t chpage, u2_t chmap) {
|| channel map appllies to 500kHz (ch 64..71) and in addition
|| all channels 0..63 are turned off or on. MCMC_LADR_CHP_BANK
|| is also special, in that it enables subbands.
||
|| TODO(tmm@mcci.com) revise the 0xFF00 mask for regions with other than
|| eight 500 kHz channels.
*/
if (chpage < MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL) {
// operate on channels 0..15, 16..31, 32..47, 48..63, 64..71
@ -110,17 +114,22 @@ bit_t LMICuslike_canMapChannels(u1_t chpage, u2_t chmap) {
return 1;
}
} else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK) {
if (chmap == 0 || (chmap & 0xFF00) != 0) {
// no bits set, or reserved bitsset , fail.
if ((chmap & 0xFF00) != 0) {
// Reserved bits set, fail.
return 0;
}
} else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON ||
chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF) {
u1_t const en125 = chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON;
// if disabling all 125kHz chans, must have at least one 500kHz chan
// don't allow reserved bits to be set in chmap.
if ((! en125 && chmap == 0) || (chmap & 0xFF00) != 0)
//
// if disabling all 125kHz chans, you might think we must have
// at least one 500kHz chan; but that's a local conclusion.
// Some network servers will disable all (including 500kHz)
// then turn things back on in the next LinkADRReq. So
// we can't fail that here.
//
// But don't allow reserved bits to be set in chmap.
//
if ((chmap & 0xFF00) != 0)
return 0;
} else {
return 0;
@ -230,11 +239,10 @@ bit_t LMICuslike_isDataRateFeasible(dr_t dr) {
#if !defined(DISABLE_JOIN)
void LMICuslike_initJoinLoop(void) {
// set an initial condition so that setNextChannel()'s preconds are met
LMIC.txChnl = 0;
LMIC.txChnl = 0xFF;
// then chose a new channel. This gives us a random first channel for
// the join. Minor nit: if channel 0 is enabled, it will never be used
// as the first join channel. The join logic uses the current txChnl,
// the join. The join logic uses the current txChnl,
// then changes after the rx window expires; so we need to set a valid
// starting point.
setNextChannel(0, 64, LMIC.activeChannels125khz);
@ -277,10 +285,13 @@ ostime_t LMICuslike_nextJoinState(void) {
if (LMIC.datarate != LMICuslike_getFirst500kHzDR()) {
// assume that 500 kHz equiv of last 125 kHz channel
// is also enabled, and use it next.
LMIC.txChnl_125kHz = LMIC.txChnl;
LMIC.txChnl = 64 + (LMIC.txChnl >> 3);
LMICcore_setDrJoin(DRCHG_SET, LMICuslike_getFirst500kHzDR());
}
else {
// restore invariant
LMIC.txChnl = LMIC.txChnl_125kHz;
setNextChannel(0, 64, LMIC.activeChannels125khz);
// TODO(tmm@mcci.com) parameterize
@ -290,6 +301,7 @@ ostime_t LMICuslike_nextJoinState(void) {
}
LMICcore_setDrJoin(DRCHG_SET, dr);
}
// tell the main loop that we've already selected a channel.
LMIC.opmode &= ~OP_NEXTCHNL;
// TODO(tmm@mcci.com): change delay to (0:1) secs + a known t0, but randomized;
@ -311,6 +323,32 @@ ostime_t LMICuslike_nextJoinState(void) {
}
#endif
#if !defined(DISABLE_JOIN)
void LMICuslike_processJoinAcceptCFList(void) {
if ( LMICbandplan_hasJoinCFlist() &&
LMIC.frame[OFF_CFLIST + 15] == LORAWAN_JoinAccept_CFListType_MASK ) {
u1_t dlen;
dlen = OFF_CFLIST;
for( u1_t chidx = 0; chidx < 8 * sizeof(LMIC.channelMap); chidx += 16, dlen += 2 ) {
u2_t mask = os_rlsbf2(&LMIC.frame[dlen]);
#if LMIC_DEBUG_LEVEL > 1
LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel mask, group=%u, mask=%04x\n", os_getTime(), chidx, mask);
#endif
for ( u1_t chnum = chidx; chnum < chidx + 16; ++chnum, mask >>= 1) {
if (chnum >= 72) {
break;
} else if (mask & 1) {
LMIC_enableChannel(chnum);
} else {
LMIC_disableChannel(chnum);
}
}
}
}
}
#endif // !DISABLE_JOIN
void LMICuslike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) {
os_copyMem(
pStateBuffer->channelMap,

View File

@ -63,8 +63,14 @@ LMICuslike_isValidBeacon1(const uint8_t *d) {
// provide a default LMICbandplan_joinAcceptChannelClear()
#define LMICbandplan_joinAcceptChannelClear() do { } while (0)
// no CFList on joins for US-like plans
#define LMICbandplan_hasJoinCFlist() (0)
/// \brief there's a CFList on joins for US-like plans
#define LMICbandplan_hasJoinCFlist() (1)
/// \brief process CFLists from JoinAccept for EU-like regions
void LMICuslike_processJoinAcceptCFList(void);
/// \brief by default, EU-like plans use LMICuslike_processJoinAcceptCFList
#define LMICbandplan_processJoinAcceptCFList LMICuslike_processJoinAcceptCFList
#define LMICbandplan_advanceBeaconChannel() \
do { LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; } while (0)

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyritght (c) 2017 MCCI Corporation.
* Copyright (c) 2017-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -60,7 +60,7 @@ enum { ILLEGAL_RPS = 0xFF };
// Global maximum frame length
enum { STD_PREAMBLE_LEN = 8 };
enum { MAX_LEN_FRAME = LMIC_ENABLE_long_messages ? 255 : 64 };
enum { MAX_LEN_FRAME = LMIC_MAX_FRAME_LENGTH };
enum { LEN_DEVNONCE = 2 };
enum { LEN_ARTNONCE = 3 };
enum { LEN_NETID = 3 };
@ -445,6 +445,13 @@ enum {
LEN_JA = 17,
LEN_JAEXT = 17+16
};
enum {
// JoinAccept CFList types
LORAWAN_JoinAccept_CFListType_FREQUENCIES = 0, ///< the CFList contains 5 frequencies
LORAWAN_JoinAccept_CFListType_MASK = 1, ///< the CFList contains channel-mask data
};
enum {
// Data frame format
OFF_DAT_HDR = 0,
@ -582,20 +589,20 @@ enum {
// Bit fields byte#3 of MCMD_LinkADRReq payload
enum {
MCMD_LinkADRReq_Redundancy_RFU = 0x80,
MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK= 0x70,
MCMD_LinkADRReq_Redundancy_NbTrans_MASK = 0x0F,
MCMD_LinkADRReq_Redundancy_RFU = 0x80, ///< mask for RFU bit
MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK= 0x70, ///< mask for the channel-mask control field.
MCMD_LinkADRReq_Redundancy_NbTrans_MASK = 0x0F, ///< mask for the `NbTrans` (repetition) field.
MCMD_LinkADRReq_ChMaskCntl_EULIKE_DIRECT = 0x00, // direct masking for EU
MCMD_LinkADRReq_ChMaskCntl_EULIKE_ALL_ON = 0x60, // EU: enable everything.
MCMD_LinkADRReq_ChMaskCntl_EULIKE_DIRECT = 0x00, ///< EU-like: direct masking for EU
MCMD_LinkADRReq_ChMaskCntl_EULIKE_ALL_ON = 0x60, ///< EU-like: enable everything.
MCMD_LinkADRReq_ChMaskCntl_USLIKE_500K = 0x40, // mask is for the 8 us-like 500 kHz channels
MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL = 0x50, // first special for us-like
MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK = 0x50, // special: bits are banks.
MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON = 0x60, // special channel page enable, bits applied to 64..71
MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF = 0x70, // special channel page: disble 125K, bits apply to 64..71
MCMD_LinkADRReq_ChMaskCntl_USLIKE_500K = 0x40, ///< US-like: mask is for the 8 us-like 500 kHz channels
MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL = 0x50, ///< US-like: first special for us-like
MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK = 0x50, ///< US-like: special: bits are banks.
MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON = 0x60, ///< US-like: special channel page enable, bits applied to 64..71
MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF = 0x70, ///< US-like: special channel page: disable 125K, bits apply to 64..71
MCMD_LinkADRReq_ChMaskCntl_CN470_ALL_ON = 0x60, // turn all on for China.
MCMD_LinkADRReq_ChMaskCntl_CN470_ALL_ON = 0x60, ///< CN-470: turn all on for China.
};
// Bit fields byte#0 of MCMD_LinkADRReq payload
@ -638,17 +645,8 @@ static inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc) {
#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
static inline int sameSfBw(rps_t r1, rps_t r2) { return ((r1^r2)&0x1F) == 0; }
extern CONST_TABLE(u1_t, _DR2RPS_CRC)[];
static inline rps_t updr2rps (dr_t dr) { return (rps_t)TABLE_GET_U1(_DR2RPS_CRC, dr+1); }
static inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); }
static inline int isFasterDR (dr_t dr1, dr_t dr2) { return dr1 > dr2; }
static inline int isSlowerDR (dr_t dr1, dr_t dr2) { return dr1 < dr2; }
static inline dr_t incDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+2)==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate
static inline dr_t decDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr )==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate
static inline dr_t assertDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)==ILLEGAL_RPS ? (dr_t)DR_DFLTMIN : dr; } // force into a valid DR
static inline bit_t validDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; } // in range
static 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

View File

@ -68,6 +68,7 @@ enum {
AS923_FREQ_MAX = 928000000
};
enum {
AS923_JP_TX_EIRP_MAX_DBM = 13, // 13 dBm = 19.95mW < 20mW
AS923_TX_EIRP_MAX_DBM = 16 // 16 dBm
};
enum { DR_PAGE_AS923 = 0x10 * (LMIC_REGION_as923 - 1) };
@ -75,14 +76,14 @@ enum { DR_PAGE_AS923 = 0x10 * (LMIC_REGION_as923 - 1) };
enum { AS923_LMIC_REGION_EIRP = 1 }; // region uses EIRP
enum { AS923JP_LBT_US = 5000 }; // microseconds of LBT time -- 5000 ==>
// 5 ms. We use us rather than ms for
// future 128us support, and just for
// backward compatibility -- there
// is code that uses the _US constant,
// and it's awkward to break it.
// 5 ms. We use us rather than ms for
// future 128us support, and just for
// backward compatibility -- there
// is code that uses the _US constant,
// and it's awkward to break it.
enum { AS923JP_LBT_DB_MAX = -80 }; // maximum channel strength in dB; if TX
// we measure more than this, we don't tx.
// we measure more than this, we don't tx.
// AS923 v1.1, all channels face a 1% duty cycle. So this will have to change
// in the future via a config. But this code base needs major changes for

View File

@ -238,7 +238,7 @@ void os_wmsbf4 (xref2u1_t buf, u4_t value);
u2_t os_rlsbf2 (xref2cu1_t buf);
#endif
#ifndef os_wlsbf2
//! Write 16-bit quntity into buffer in little endian byte order.
//! Write 16-bit quantity into buffer in little endian byte order.
void os_wlsbf2 (xref2u1_t buf, u2_t value);
#endif

View File

@ -382,10 +382,12 @@ static void writeReg (u1_t addr, u1_t data ) {
hal_spi_write(addr | 0x80, &data, 1);
}
// ttn-esp32: it's safer if buffer is static
static u1_t reg_value_buf[1];
static u1_t readReg (u1_t addr) {
u1_t buf[1];
hal_spi_read(addr & 0x7f, buf, 1);
return buf[0];
hal_spi_read(addr & 0x7f, reg_value_buf, 1);
return reg_value_buf[0];
}
static void writeBuf (u1_t addr, xref2u1_t buf, u1_t len) {
@ -668,9 +670,9 @@ static void configPower () {
if (req_pw >= 20) {
policy = LMICHAL_radio_tx_power_policy_20dBm;
eff_pw = 20;
} else if (eff_pw >= 14) {
} else if (req_pw >= 14) {
policy = LMICHAL_radio_tx_power_policy_paboost;
if (eff_pw > 17) {
if (req_pw > 17) {
eff_pw = 17;
} else {
eff_pw = req_pw;

562
src/ttn.c Normal file
View File

@ -0,0 +1,562 @@
/*******************************************************************************
*
* 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 "ttn.h"
#include "esp_event.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "hal/hal_esp32.h"
#include "lmic/lmic.h"
#include "ttn_logging.h"
#include "ttn_provisioning.h"
#include "ttn_nvs.h"
#include "ttn_rtc.h"
#define TAG "ttn"
#define DEFAULT_MAX_TX_POWER -1000
/**
* @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 bool is_started;
static bool has_joined;
static QueueHandle_t lmic_event_queue;
static ttn_message_cb message_callback;
static ttn_waiting_reason_t waiting_reason;
static ttn_rf_settings_t last_rf_settings[4];
static ttn_rx_tx_window_t current_rx_tx_window;
static int subband = 2;
static ttn_data_rate_t join_data_rate = TTN_DR_JOIN_DEFAULT;
static int max_tx_power = DEFAULT_MAX_TX_POWER;
static void start(void);
static void stop(void);
static bool join_core(void);
static void config_rf_params(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
}
void ttn_set_subband(int band)
{
subband = band;
}
void start(void)
{
if (is_started)
return;
LMIC_registerEventCb(event_callback, NULL);
LMIC_registerRxMessageCb(message_received_callback, NULL);
os_init_ex(NULL);
hal_esp32_enter_critical_section();
LMIC_reset();
LMIC_setClockError(MAX_CLOCK_ERROR * 4 / 100);
waiting_reason = TTN_WAITING_NONE;
hal_esp32_leave_critical_section();
lmic_event_queue = xQueueCreate(4, sizeof(ttn_lmic_event_t));
ASSERT(lmic_event_queue != NULL);
hal_esp32_start_lmic_task();
is_started = true;
}
void stop(void)
{
if (!is_started)
return;
hal_esp32_enter_critical_section();
LMIC_shutdown();
hal_esp32_stop_lmic_task();
waiting_reason = TTN_WAITING_NONE;
hal_esp32_leave_critical_section();
}
void ttn_shutdown(void)
{
stop();
}
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_transiently(const char *dev_eui, const char *app_eui, const char *app_key)
{
return ttn_provisioning_decode_keys(dev_eui, app_eui, app_key);
}
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_with_keys(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(void)
{
if (!ttn_provisioning_have_keys())
{
if (!ttn_provisioning_restore_keys(false))
return false;
}
return join_core();
}
bool ttn_resume_after_deep_sleep(void)
{
if (!ttn_provisioning_have_keys())
{
if (!ttn_provisioning_restore_keys(false))
return false;
}
if (!ttn_provisioning_have_keys())
{
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
return false;
}
start();
if (!ttn_rtc_restore())
return false;
has_joined = true;
return true;
}
bool ttn_resume_after_power_off(int off_duration)
{
if (!ttn_provisioning_have_keys())
{
if (!ttn_provisioning_restore_keys(false))
return false;
}
if (!ttn_provisioning_have_keys())
{
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
return false;
}
start();
if (!ttn_nvs_restore(off_duration))
return false;
has_joined = true;
return true;
}
// Called immediately before sending join request message
void config_rf_params(void)
{
#if defined(CFG_us915) || defined(CFG_au915)
if (subband != 0)
LMIC_selectSubBand(subband - 1);
#endif
if (join_data_rate != TTN_DR_JOIN_DEFAULT || max_tx_power != DEFAULT_MAX_TX_POWER)
{
dr_t dr = join_data_rate == TTN_DR_JOIN_DEFAULT ? LMIC.datarate : (dr_t)join_data_rate;
s1_t txpow = max_tx_power == DEFAULT_MAX_TX_POWER ? LMIC.adrTxPow : max_tx_power;
LMIC_setDrTxpow(dr, txpow);
}
}
bool join_core(void)
{
if (!ttn_provisioning_have_keys())
{
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
return false;
}
start();
has_joined = true;
hal_esp32_enter_critical_section();
xQueueReset(lmic_event_queue);
waiting_reason = TTN_WAITING_FOR_JOIN;
config_rf_params();
LMIC_startJoining();
hal_esp32_wake_up();
hal_esp32_leave_critical_section();
ttn_lmic_event_t event;
xQueueReceive(lmic_event_queue, &event, portMAX_DELAY);
has_joined = event.event == TTN_EVNT_JOIN_COMPLETED;
return has_joined;
}
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_prepare_for_deep_sleep(void)
{
ttn_rtc_save();
stop();
}
void ttn_prepare_for_power_off(void)
{
ttn_nvs_save();
stop();
}
void ttn_wait_for_idle(void)
{
while (true)
{
TickType_t ticks_to_wait = ttn_busy_duration();
if (ticks_to_wait == 0)
return;
vTaskDelay(ticks_to_wait);
}
}
TickType_t ttn_busy_duration(void)
{
TickType_t duration = hal_esp32_get_timer_duration();
if (duration != 0)
return duration; // busy or timer scheduled
if ((LMIC.opmode & (OP_JOINING | OP_TXDATA | OP_POLL | OP_TXRXPEND)) != 0)
return pdMS_TO_TICKS(100); // pending action
return 0; // idle
}
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_enabled(bool enabled)
{
hal_esp32_enter_critical_section();
LMIC_setAdrMode(enabled);
hal_esp32_leave_critical_section();
}
void ttn_set_data_rate(ttn_data_rate_t data_rate)
{
join_data_rate = data_rate;
if (has_joined)
{
hal_esp32_enter_critical_section();
LMIC_setDrTxpow(data_rate, LMIC.adrTxPow);
hal_esp32_leave_critical_section();
}
}
void ttn_set_max_tx_pow(int tx_pow)
{
max_tx_power = tx_pow;
if (has_joined)
{
hal_esp32_enter_critical_section();
LMIC_setDrTxpow(LMIC.datarate, tx_pow);
hal_esp32_leave_critical_section();
}
}
ttn_rf_settings_t ttn_get_rf_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));
}

313
src/ttn_logging.c Normal file
View File

@ -0,0 +1,313 @@
/*******************************************************************************
*
* 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
*
* Circular buffer for detailed logging without affecting LMIC timing.
*******************************************************************************/
#if LMIC_ENABLE_event_logging
#include "ttn_logging.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lmic/lmic.h"
#include <string.h>
#define NUM_RINGBUF_MSG 50
#define TAG "lmic"
/**
* @brief Message structure used in ring buffer
*
* The structure is sent from the LMIC task to the logging task.
*/
typedef struct
{
const char *message;
uint32_t datum;
ev_t event;
ostime_t time;
ostime_t txend;
ostime_t globalDutyAvail;
u4_t freq;
u2_t opmode;
u2_t fcntDn;
u2_t fcntUp;
u2_t rxsyms;
rps_t rps;
u1_t txChnl;
u1_t datarate;
u1_t txrxFlags;
u1_t saveIrqFlags;
} TTNLogMessage;
static void loggingTask(void *param);
static void logFatal(const char *const file, const uint16_t line);
static void printMessage(TTNLogMessage *log);
static void printFatalError(TTNLogMessage *log);
static void printEvent(TTNLogMessage *log);
static void printEvtJoined(TTNLogMessage *log);
static void printEvtJoinFailed(TTNLogMessage *log);
static void printEvtTxComplete(TTNLogMessage *log);
static void printEvtTxStart(TTNLogMessage *log);
static void printEvtRxStart(TTNLogMessage *log);
static void printEvtJoinTxComplete(TTNLogMessage *log);
static void bin2hex(const uint8_t *bin, unsigned len, char *buf, char sep);
// Constants for formatting LORA values
static const char *const SF_NAMES[] = {"FSK", "SF7", "SF8", "SF9", "SF10", "SF11", "SF12", "SFrfu"};
static const char *const BW_NAMES[] = {"BW125", "BW250", "BW500", "BWrfu"};
static const char *const CR_NAMES[] = {"CR 4/5", "CR 4/6", "CR 4/7", "CR 4/8"};
static const char *const CRC_NAMES[] = {"NoCrc", "Crc"};
static RingbufHandle_t ringBuffer;
// Initialize logging
void ttn_log_init(void)
{
ringBuffer = xRingbufferCreate(NUM_RINGBUF_MSG * sizeof(TTNLogMessage), RINGBUF_TYPE_NOSPLIT);
if (ringBuffer == NULL)
{
ESP_LOGE(TAG, "Failed to create ring buffer");
ASSERT(0);
}
xTaskCreate(loggingTask, "ttn_log", 1024 * 4, ringBuffer, 4, NULL);
hal_set_failure_handler(logFatal);
}
// Record a logging event for later output
void ttn_log_event(int event, const char *message, uint32_t datum)
{
if (ringBuffer == NULL)
return;
// capture state
TTNLogMessage log = {
.message = message,
.datum = datum,
.time = os_getTime(),
.txend = LMIC.txend,
.globalDutyAvail = LMIC.globalDutyAvail,
.event = (ev_t)event,
.freq = LMIC.freq,
.opmode = LMIC.opmode,
.fcntDn = (u2_t)LMIC.seqnoDn,
.fcntUp = (u2_t)LMIC.seqnoUp,
.rxsyms = LMIC.rxsyms,
.rps = LMIC.rps,
.txChnl = LMIC.txChnl,
.datarate = LMIC.datarate,
.txrxFlags = LMIC.txrxFlags,
.saveIrqFlags = LMIC.saveIrqFlags,
};
xRingbufferSend(ringBuffer, &log, sizeof(log), 0);
}
// record a fatal event (failed assert) for later output
void logFatal(const char *const file, const uint16_t line)
{
ttn_log_event(-3, file, line);
}
// Record an informational message for later output
// The message must not be freed.
void LMICOS_logEvent(const char *pMessage)
{
ttn_log_event(-1, pMessage, 0);
}
// Record an information message with an integer value for later output
// The message must not be freed.
void LMICOS_logEventUint32(const char *pMessage, uint32_t datum)
{
ttn_log_event(-2, pMessage, datum);
}
// ---------------------------------------------------------------------------
// Log output
// Tasks that receiveds the recorded messages, formats and outputs them.
void loggingTask(void *param)
{
RingbufHandle_t ringBuffer = (RingbufHandle_t)param;
while (true)
{
size_t size;
TTNLogMessage *log = (TTNLogMessage *)xRingbufferReceive(ringBuffer, &size, portMAX_DELAY);
if (log == NULL)
continue;
printMessage(log);
vRingbufferReturnItem(ringBuffer, log);
}
}
// Format and output a log message
void printMessage(TTNLogMessage *log)
{
switch ((int)log->event)
{
case -1:
ESP_LOGI(TAG, "%u (%d ms) - %s: opmode=%x", log->time, osticks2ms(log->time), log->message, log->opmode);
break;
case -2:
ESP_LOGI(TAG, "%u (%d ms) - %s: datum=0x%x, opmode=%x)", log->time, osticks2ms(log->time), log->message,
log->datum, log->opmode);
break;
case -3:
printFatalError(log);
break;
default:
printEvent(log);
break;
}
}
void printFatalError(TTNLogMessage *log)
{
ESP_LOGE(TAG, "%u (%d ms) - %s, %d", log->time, osticks2ms(log->time), log->message, log->datum);
ESP_LOGE(TAG, "- freq=%d.%d, txend=%u, avail=%u, ch=%u", log->freq / 1000000, (log->freq % 1000000) / 100000,
log->txend, log->globalDutyAvail, (unsigned)log->txChnl);
rps_t rps = log->rps;
ESP_LOGE(TAG, "- rps=0x%02x (%s, %s, %s, %s, IH=%d)", rps, SF_NAMES[getSf(rps)], BW_NAMES[getBw(rps)],
CR_NAMES[getCr(rps)], CRC_NAMES[getNocrc(rps)], getIh(rps));
ESP_LOGE(TAG, "- opmode=%x, txrxFlags=0x%02x%s, saveIrqFlags=0x%02x", log->opmode, log->txrxFlags,
(log->txrxFlags & TXRX_ACK) != 0 ? "; received ack" : "", log->saveIrqFlags);
}
void printEvent(TTNLogMessage *log)
{
ESP_LOGI(TAG, "%u (%d ms) - %s", log->time, osticks2ms(log->time), log->message);
switch ((int)log->event)
{
case EV_JOINED:
printEvtJoined(log);
break;
case EV_JOIN_FAILED:
printEvtJoinFailed(log);
break;
case EV_TXCOMPLETE:
printEvtTxComplete(log);
break;
case EV_TXSTART:
printEvtTxStart(log);
break;
case EV_RXSTART:
printEvtRxStart(log);
break;
case EV_JOIN_TXCOMPLETE:
printEvtJoinTxComplete(log);
break;
default:
break;
};
}
// Format and output the detail of a successful network join
void printEvtJoined(TTNLogMessage *log)
{
ESP_LOGI(TAG, "- ch=%d", (unsigned)log->txChnl);
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
ESP_LOGI(TAG, "- netid: %d", netid);
ESP_LOGI(TAG, "- devaddr: %08x", devaddr);
char hexBuf[48];
bin2hex((uint8_t *)&artKey, sizeof(artKey), hexBuf, '-');
ESP_LOGI(TAG, "- artKey: %s", hexBuf);
bin2hex((uint8_t *)&nwkKey, sizeof(nwkKey), hexBuf, '-');
ESP_LOGI(TAG, "- nwkKey: %s", hexBuf);
}
// Format and output the detail of a failed network join
void printEvtJoinFailed(TTNLogMessage *log)
{
rps_t rps = log->rps;
ESP_LOGE(TAG, "- freq=%d.%d, opmode=%x, rps=0x%02x (%s, %s, %s, %s, IH=%d)", log->freq / 1000000,
(log->freq % 1000000) / 100000, log->opmode, rps, SF_NAMES[getSf(rps)], BW_NAMES[getBw(rps)],
CR_NAMES[getCr(rps)], CRC_NAMES[getNocrc(rps)], getIh(rps));
}
void printEvtTxComplete(TTNLogMessage *log)
{
rps_t rps = log->rps;
ESP_LOGI(TAG, "- ch=%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)", (unsigned)log->txChnl, rps, SF_NAMES[getSf(rps)],
BW_NAMES[getBw(rps)], CR_NAMES[getCr(rps)], CRC_NAMES[getNocrc(rps)], getIh(rps));
ESP_LOGI(TAG, "- txrxFlags=0x%02x%s, FcntUp=%04x, FcntDn=%04x, txend=%u", log->txrxFlags,
(log->txrxFlags & TXRX_ACK) != 0 ? "; received ack" : "", log->fcntUp, log->fcntDn, log->txend);
}
void printEvtTxStart(TTNLogMessage *log)
{
rps_t rps = log->rps;
ESP_LOGI(TAG, "- ch=%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)", (unsigned)log->txChnl, rps, SF_NAMES[getSf(rps)],
BW_NAMES[getBw(rps)], CR_NAMES[getCr(rps)], CRC_NAMES[getNocrc(rps)], getIh(rps));
ESP_LOGI(TAG, "- datarate=%u, opmode=%x, txend=%u", log->datarate, log->opmode, log->txend);
}
void printEvtRxStart(TTNLogMessage *log)
{
rps_t rps = log->rps;
ESP_LOGI(TAG, "- freq=%d.%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)", log->freq / 1000000,
(log->freq % 1000000) / 100000, rps, SF_NAMES[getSf(rps)], BW_NAMES[getBw(rps)], CR_NAMES[getCr(rps)],
CRC_NAMES[getNocrc(rps)], getIh(rps));
ESP_LOGI(TAG, "- delta=%dms, rxsysm=%u", osticks2ms(log->time - log->txend), log->rxsyms);
}
void printEvtJoinTxComplete(TTNLogMessage *log)
{
ESP_LOGI(TAG, "- saveIrqFlags=0x%02x", log->saveIrqFlags);
}
static const char *HEX_DIGITS = "0123456789ABCDEF";
/**
* @brief Convert binary data to hexadecimal representation.
*
* @param bin start of binary data
* @param len length of binary data (in bytes)
* @param buf buffer for hexadecimal result
* @param sep separator used between bytes (or 0 for none)
*/
void bin2hex(const uint8_t *bin, unsigned len, char *buf, char sep)
{
int tgt = 0;
for (int i = 0; i < len; i++)
{
if (sep != 0 && i != 0)
buf[tgt++] = sep;
buf[tgt++] = HEX_DIGITS[bin[i] >> 4];
buf[tgt++] = HEX_DIGITS[bin[i] & 0xf];
}
buf[tgt] = 0;
}
#endif

50
src/ttn_logging.h Normal file
View File

@ -0,0 +1,50 @@
/*******************************************************************************
*
* 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
*
* Circular buffer for detailed logging without affecting LMIC timing.
*******************************************************************************/
#ifndef TTN_LOGGING_H
#define TTN_LOGGING_H
#if LMIC_ENABLE_event_logging
#include <freertos/FreeRTOS.h>
#include <freertos/ringbuf.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Logging functions.
*
* Logs internal information from LMIC in an asynchrnous fashion in order
* not to distrub the sensitive LORA timing.
*
* A ring buffer and a separate logging task is ued. The LMIC core records
* relevant values from the current LORA settings and writes them to a ring
* buffer. The logging tasks receives the message and the values, formats
* them and outputs them via the regular ESP-IDF logging mechanism.
*
* In order to activate the detailed logging, set the macro
* `LMIC_ENABLE_event_logging` to 1.
*/
void ttn_log_init(void);
void ttn_log_event(int event, const char *message, uint32_t datum);
#ifdef __cplusplus
}
#endif
#endif
#endif

130
src/ttn_nvs.c Normal file
View File

@ -0,0 +1,130 @@
/*******************************************************************************
*
* 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
*
* Functions for storing and retrieving TTN communication state from NVS.
*******************************************************************************/
#include "esp_log.h"
#include "esp_system.h"
#include "hal/hal_esp32.h"
#include "lmic/lmic.h"
#include "nvs_flash.h"
#include "ttn_rtc.h"
#include <string.h>
#define LMIC_OFFSET(field) __builtin_offsetof(struct lmic_t, field)
#define LMIC_DIST(field1, field2) (LMIC_OFFSET(field2) - LMIC_OFFSET(field1))
#define TAG "ttn_nvs"
#define NVS_FLASH_PARTITION "ttn"
#define NVS_FLASH_KEY_CHUNK_1 "chunk1"
#define NVS_FLASH_KEY_CHUNK_2 "chunk2"
#define NVS_FLASH_KEY_CHUNK_3 "chunk3"
#define NVS_FLASH_KEY_TIME "time"
static struct lmic_t tmp_LMIC;
void ttn_nvs_save()
{
nvs_handle handle = 0;
esp_err_t res = nvs_open(NVS_FLASH_PARTITION, NVS_READWRITE, &handle);
if (res == ESP_ERR_NVS_NOT_INITIALIZED)
ESP_LOGW(TAG, "NVS storage is not initialized. Call 'nvs_flash_init()' first.");
if (res != ESP_OK)
goto done;
// Save LMIC struct except client, osjob, pendTxData and frame
size_t len1 = LMIC_DIST(radio, pendTxData);
res = nvs_set_blob(handle, NVS_FLASH_KEY_CHUNK_1, &LMIC.radio, len1);
if (res != ESP_OK)
goto done;
size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD;
res = nvs_set_blob(handle, NVS_FLASH_KEY_CHUNK_2, (u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, len2);
if (res != ESP_OK)
goto done;
size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME;
res = nvs_set_blob(handle, NVS_FLASH_KEY_CHUNK_3, (u1_t *)&LMIC.frame + MAX_LEN_FRAME, len3);
if (res != ESP_OK)
goto done;
res = nvs_set_u32(handle, NVS_FLASH_KEY_TIME, hal_esp32_get_time());
if (res != ESP_OK)
goto done;
res = nvs_commit(handle);
if (res != ESP_OK)
goto done;
done:
nvs_close(handle);
ESP_ERROR_CHECK(res);
}
bool ttn_nvs_restore(int off_duration)
{
nvs_handle handle = 0;
esp_err_t res = nvs_open(NVS_FLASH_PARTITION, NVS_READWRITE, &handle);
if (res == ESP_ERR_NVS_NOT_INITIALIZED)
ESP_LOGW(TAG, "NVS storage is not initialized. Call 'nvs_flash_init()' first.");
if (res != ESP_OK)
goto done;
uint32_t time_val;
res = nvs_get_u32(handle, NVS_FLASH_KEY_TIME, &time_val);
if (res != ESP_OK)
goto done;
size_t len1 = LMIC_DIST(radio, pendTxData);
res = nvs_get_blob(handle, NVS_FLASH_KEY_CHUNK_1, &tmp_LMIC.radio, &len1);
if (res != ESP_OK)
goto done;
memset(LMIC.pendTxData, 0, MAX_LEN_PAYLOAD);
size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD;
res = nvs_get_blob(handle, NVS_FLASH_KEY_CHUNK_2, (u1_t *)&tmp_LMIC.pendTxData + MAX_LEN_PAYLOAD, &len2);
if (res != ESP_OK)
goto done;
memset(LMIC.frame, 0, MAX_LEN_FRAME);
size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME;
res = nvs_get_blob(handle, NVS_FLASH_KEY_CHUNK_3, (u1_t *)&tmp_LMIC.frame + MAX_LEN_FRAME, &len3);
if (res != ESP_OK)
goto done;
// invalidate data
res = nvs_erase_key(handle, NVS_FLASH_KEY_TIME);
if (res != ESP_OK)
goto done;
res = nvs_commit(handle);
if (res != ESP_OK)
goto done;
if (off_duration != 0)
hal_esp32_set_time(time_val + off_duration * 60);
done:
nvs_close(handle);
if (res == ESP_OK) {
// copy temporary data to actual
memcpy(&LMIC.radio, &tmp_LMIC.radio, LMIC_DIST(radio, pendTxData));
memcpy((u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, (u1_t *)&tmp_LMIC.pendTxData + MAX_LEN_PAYLOAD,
LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD);
memcpy((u1_t *)&LMIC.frame + MAX_LEN_FRAME, (u1_t *)&tmp_LMIC.frame + MAX_LEN_FRAME,
sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME);
}
return res == ESP_OK;
}

30
src/ttn_nvs.h Normal file
View File

@ -0,0 +1,30 @@
/*******************************************************************************
*
* 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
*
* Functions for storing and retrieving TTN communication state from NVS.
*******************************************************************************/
#ifndef TTN_NVS_H
#define TTN_NVS_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C"
{
#endif
void ttn_nvs_save();
bool ttn_nvs_restore(int off_duration);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,59 +1,89 @@
/*******************************************************************************
*
*
* 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
*
* Task listening on a UART port for provisioning commands.
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "ttn_provisioning.h"
#include "driver/uart.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "TTNProvisioning.h"
#include "lmic/lmic.h"
#if ESP_IDF_VERSION_MAJOR >= 5
#include "esp_mac.h"
#endif
#include "freertos/FreeRTOS.h"
#include "hal/hal_esp32.h"
#include "lmic/lmic.h"
#include "nvs_flash.h"
#if defined(TTN_HAS_AT_COMMANDS)
const uart_port_t UART_NUM = (uart_port_t) CONFIG_TTN_PROVISION_UART_NUM;
const int MAX_LINE_LENGTH = 128;
#define UART_NUM CONFIG_TTN_PROVISION_UART_NUM
#define MAX_LINE_LENGTH 128
#endif
static const char* const TAG = "ttn_prov";
static const char* const NVS_FLASH_PARTITION = "ttn";
static const char* const NVS_FLASH_KEY_DEV_EUI = "devEui";
static const char* const NVS_FLASH_KEY_APP_EUI = "appEui";
static const char* const NVS_FLASH_KEY_APP_KEY = "appKey";
#define TAG "ttn_prov"
#define NVS_FLASH_PARTITION "ttn"
#define NVS_FLASH_KEY_DEV_EUI "devEui"
#define NVS_FLASH_KEY_APP_EUI "appEui"
#define NVS_FLASH_KEY_APP_KEY "appKey"
#if defined(TTN_HAS_AT_COMMANDS)
static void provisioning_task(void *pvParameter);
static void add_line_data(int num_bytes);
static void detect_line_end(int start_at);
static void process_line(void);
#endif
#if defined(TTN_CONFIG_UART)
static void config_uart(void);
#endif
static bool decode(bool incl_dev_eui, const char *dev_eui, const char *app_eui, const char *app_key);
static bool read_nvs_value(nvs_handle handle, const char *key, uint8_t *data, size_t expected_length, bool silent);
static bool write_nvs_value(nvs_handle handle, const char *key, const uint8_t *data, size_t len);
static bool hex_str_to_bin(const char *hex, uint8_t *buf, int len);
static int hex_tuple_to_byte(const char *hex);
static int hex_digit_to_val(char ch);
static void bin_to_hex_str(const uint8_t *buf, int len, char *hex);
static char val_to_hex_digit(int val);
static void swap_bytes(uint8_t *buf, int len);
static bool is_all_zeros(const uint8_t *buf, int len);
static uint8_t global_dev_eui[8];
static uint8_t global_app_eui[8];
static uint8_t global_app_key[16];
static bool have_keys = false;
#if defined(TTN_HAS_AT_COMMANDS)
void ttn_provisioning_task_caller(void* pvParameter);
static QueueHandle_t uart_queue;
static char *line_buf;
static int line_length;
static uint8_t last_line_end_char;
static bool quit_task;
#endif
// --- LMIC callbacks
// This EUI must be in little-endian format, so least-significant-byte first.
// When copying an EUI from ttnctl output, this means to reverse the bytes.
// For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 0x70.
// The order is swapped in provisioning_decode_keys().
void os_getArtEui (u1_t* buf)
void os_getArtEui(u1_t *buf)
{
memcpy(buf, global_app_eui, 8);
}
// This should also be in little endian format, see above.
void os_getDevEui (u1_t* buf)
void os_getDevEui(u1_t *buf)
{
memcpy(buf, global_dev_eui, 8);
}
@ -61,48 +91,36 @@ void os_getDevEui (u1_t* buf)
// This key should be in big endian format (or, since it is not really a number
// but a block of memory, endianness does not really apply). In practice, a key
// taken from ttnctl can be copied as-is.
void os_getDevKey (u1_t* buf)
void os_getDevKey(u1_t *buf)
{
memcpy(buf, global_app_key, 16);
}
// --- Constructor
TTNProvisioning::TTNProvisioning()
: have_keys(false)
#if defined(TTN_HAS_AT_COMMANDS)
, uart_queue(nullptr), line_buf(nullptr), line_length(0), last_line_end_char(0), quit_task(false)
#endif
void ttn_provisioning_init(void)
{
}
// --- Provisioning task
#if defined(TTN_HAS_AT_COMMANDS)
void TTNProvisioning::startTask()
void ttn_provisioning_start_task(void)
{
#if defined(TTN_CONFIG_UART)
configUART();
config_uart();
#endif
esp_err_t err = uart_driver_install(UART_NUM, 2048, 2048, 20, &uart_queue, 0);
ESP_ERROR_CHECK(err);
xTaskCreate(ttn_provisioning_task_caller, "ttn_provision", 2048, this, 1, nullptr);
xTaskCreate(provisioning_task, "ttn_provision", 2048, NULL, 1, NULL);
}
void ttn_provisioning_task_caller(void* pvParameter)
void provisioning_task(void *pvParameter)
{
TTNProvisioning* provisioning = (TTNProvisioning*)pvParameter;
provisioning->provisioningTask();
vTaskDelete(nullptr);
}
void TTNProvisioning::provisioningTask()
{
line_buf = (char*)malloc(MAX_LINE_LENGTH + 1);
line_buf = (char *)malloc(MAX_LINE_LENGTH + 1);
line_length = 0;
uart_event_t event;
@ -116,47 +134,48 @@ void TTNProvisioning::provisioningTask()
switch (event.type)
{
case UART_DATA:
addLineData(event.size);
break;
case UART_DATA:
add_line_data(event.size);
break;
case UART_FIFO_OVF:
case UART_BUFFER_FULL:
uart_flush_input(UART_NUM);
xQueueReset(uart_queue);
break;
case UART_FIFO_OVF:
case UART_BUFFER_FULL:
uart_flush_input(UART_NUM);
xQueueReset(uart_queue);
break;
default:
break;
default:
break;
}
}
free(line_buf);
uart_driver_delete(UART_NUM);
vTaskDelete(NULL);
}
void TTNProvisioning::addLineData(int numBytes)
void add_line_data(int num_bytes)
{
int n;
top:
n = numBytes;
n = num_bytes;
if (line_length + n > MAX_LINE_LENGTH)
n = MAX_LINE_LENGTH - line_length;
uart_read_bytes(UART_NUM, (uint8_t*)line_buf + line_length, n, portMAX_DELAY);
uart_read_bytes(UART_NUM, (uint8_t *)line_buf + line_length, n, portMAX_DELAY);
int start_at = line_length;
line_length += n;
detectLineEnd(start_at);
detect_line_end(start_at);
if (n < numBytes)
if (n < num_bytes)
{
numBytes -= n;
num_bytes -= n;
goto top;
}
}
void TTNProvisioning::detectLineEnd(int start_at)
void detect_line_end(int start_at)
{
top:
for (int p = start_at; p < line_length; p++)
@ -173,7 +192,7 @@ top:
last_line_end_char = ch;
if (p > 0)
processLine();
process_line();
memcpy(line_buf, line_buf + p + 1, line_length - p - 1);
line_length -= p + 1;
@ -189,7 +208,7 @@ top:
line_length = 0; // Line too long; flush it
}
void TTNProvisioning::processLine()
void process_line(void)
{
bool is_ok = true;
bool reset_needed = false;
@ -207,26 +226,28 @@ void TTNProvisioning::processLine()
char hexbuf[16];
memcpy(binbuf, global_dev_eui, 8);
swapBytes(binbuf, 8);
binToHexStr(binbuf, 8, hexbuf);
swap_bytes(binbuf, 8);
bin_to_hex_str(binbuf, 8, hexbuf);
uart_write_bytes(UART_NUM, hexbuf, 16);
uart_write_bytes(UART_NUM, "-", 1);
memcpy(binbuf, global_app_eui, 8);
swapBytes(binbuf, 8);
binToHexStr(binbuf, 8, hexbuf);
swap_bytes(binbuf, 8);
bin_to_hex_str(binbuf, 8, hexbuf);
uart_write_bytes(UART_NUM, hexbuf, 16);
uart_write_bytes(UART_NUM, "-00000000000000000000000000000000\r\n", 35);
}
else if (strncmp(line_buf, "AT+PROV=", 8) == 0)
{
is_ok = strlen(line_buf) == 74 && line_buf[24] == '-' && line_buf[41] == '-';
is_ok = strlen(line_buf) == 74 && line_buf[24] == '-' && line_buf[41] == '-';
if (is_ok)
{
line_buf[24] = 0;
line_buf[41] = 0;
is_ok = decodeKeys(line_buf + 8, line_buf + 25, line_buf + 42);
is_ok = ttn_provisioning_decode_keys(line_buf + 8, line_buf + 25, line_buf + 42);
if (is_ok)
is_ok = ttn_provisioning_save_keys();
reset_needed = is_ok;
}
}
@ -236,7 +257,9 @@ void TTNProvisioning::processLine()
if (is_ok)
{
line_buf[25] = 0;
is_ok = fromMAC(line_buf + 9, line_buf + 26);
is_ok = ttn_provisioning_from_mac(line_buf + 9, line_buf + 26);
if (is_ok)
is_ok = ttn_provisioning_save_keys();
reset_needed = is_ok;
}
}
@ -248,8 +271,9 @@ void TTNProvisioning::processLine()
esp_err_t err = esp_efuse_mac_get_default(mac);
ESP_ERROR_CHECK(err);
binToHexStr(mac, 6, hexbuf);
for (int i = 0; i < 12; i += 2) {
bin_to_hex_str(mac, 6, hexbuf);
for (int i = 0; i < 12; i += 2)
{
if (i > 0)
uart_write_bytes(UART_NUM, ":", 1);
uart_write_bytes(UART_NUM, hexbuf + i, 2);
@ -264,11 +288,12 @@ void TTNProvisioning::processLine()
esp_err_t err = esp_efuse_mac_get_default(mac);
ESP_ERROR_CHECK(err);
binToHexStr(mac, 6, hexbuf);
for (int i = 0; i < 12; i += 2) {
bin_to_hex_str(mac, 6, hexbuf);
for (int i = 0; i < 12; i += 2)
{
uart_write_bytes(UART_NUM, hexbuf + i, 2);
if (i == 4)
uart_write_bytes(UART_NUM, "FFFE", 4);
uart_write_bytes(UART_NUM, "FFFE", 4);
}
uart_write_bytes(UART_NUM, "\r\n", 2);
}
@ -283,9 +308,9 @@ void TTNProvisioning::processLine()
if (reset_needed)
{
ttn_hal.enterCriticalSection();
hal_esp32_enter_critical_section();
LMIC_reset();
ttn_hal.leaveCriticalSection();
hal_esp32_leave_critical_section();
LMIC.client.eventCb(LMIC.client.eventUserData, EV_RESET);
}
@ -294,50 +319,45 @@ void TTNProvisioning::processLine()
#endif
#if defined(TTN_CONFIG_UART)
void TTNProvisioning::configUART()
void config_uart(void)
{
esp_err_t err;
uart_config_t uart_config = {
.baud_rate = CONFIG_TTN_PROVISION_UART_BAUDRATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 0,
.use_ref_tick = false
};
uart_config_t uart_config = {.baud_rate = CONFIG_TTN_PROVISION_UART_BAUDRATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE};
err = uart_param_config(UART_NUM, &uart_config);
ESP_ERROR_CHECK(err);
err = uart_set_pin(UART_NUM, CONFIG_TTN_PROVISION_UART_TX_GPIO, CONFIG_TTN_PROVISION_UART_RX_GPIO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
err = uart_set_pin(UART_NUM, CONFIG_TTN_PROVISION_UART_TX_GPIO, CONFIG_TTN_PROVISION_UART_RX_GPIO,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
ESP_ERROR_CHECK(err);
}
#endif
// --- Key handling
bool TTNProvisioning::haveKeys()
bool ttn_provisioning_have_keys(void)
{
return have_keys;
}
bool TTNProvisioning::decodeKeys(const char *dev_eui, const char *app_eui, const char *app_key)
bool ttn_provisioning_decode_keys(const char *dev_eui, const char *app_eui, const char *app_key)
{
return decode(true, dev_eui, app_eui, app_key);
}
bool TTNProvisioning::fromMAC(const char *app_eui, const char *app_key)
bool ttn_provisioning_from_mac(const char *app_eui, const char *app_key)
{
uint8_t mac[6];
esp_err_t err = esp_efuse_mac_get_default(mac);
ESP_ERROR_CHECK(err);
global_dev_eui[7] = mac[0];
global_dev_eui[6] = mac[1];
global_dev_eui[5] = mac[2];
@ -347,33 +367,33 @@ bool TTNProvisioning::fromMAC(const char *app_eui, const char *app_key)
global_dev_eui[1] = mac[4];
global_dev_eui[0] = mac[5];
return decode(false, nullptr, app_eui, app_key);
return decode(false, NULL, app_eui, app_key);
}
bool TTNProvisioning::decode(bool incl_dev_eui, const char *dev_eui, const char *app_eui, const char *app_key)
bool decode(bool incl_dev_eui, const char *dev_eui, const char *app_eui, const char *app_key)
{
uint8_t buf_dev_eui[8];
uint8_t buf_app_eui[8];
uint8_t buf_app_key[16];
if (incl_dev_eui && (strlen(dev_eui) != 16 || !hexStrToBin(dev_eui, buf_dev_eui, 8)))
if (incl_dev_eui && (strlen(dev_eui) != 16 || !hex_str_to_bin(dev_eui, buf_dev_eui, 8)))
{
ESP_LOGW(TAG, "Invalid device EUI: %s", dev_eui);
ESP_LOGW(TAG, "Invalid DevEUI: %s", dev_eui);
return false;
}
if (incl_dev_eui)
swapBytes(buf_dev_eui, 8);
swap_bytes(buf_dev_eui, 8);
if (strlen(app_eui) != 16 || !hexStrToBin(app_eui, buf_app_eui, 8))
if (strlen(app_eui) != 16 || !hex_str_to_bin(app_eui, buf_app_eui, 8))
{
ESP_LOGW(TAG, "Invalid application EUI: %s", app_eui);
ESP_LOGW(TAG, "Invalid AppEUI/JoinEUI: %s", app_eui);
return false;
}
swapBytes(buf_app_eui, 8);
swap_bytes(buf_app_eui, 8);
if (strlen(app_key) != 32 || !hexStrToBin(app_key, buf_app_key, 16))
if (strlen(app_key) != 32 || !hex_str_to_bin(app_key, buf_app_key, 16))
{
ESP_LOGW(TAG, "Invalid application key: %s", app_key);
return false;
@ -384,20 +404,15 @@ bool TTNProvisioning::decode(bool incl_dev_eui, const char *dev_eui, const char
memcpy(global_app_eui, buf_app_eui, sizeof(global_app_eui));
memcpy(global_app_key, buf_app_key, sizeof(global_app_key));
have_keys = !isAllZeros(global_dev_eui, sizeof(global_dev_eui))
&& !isAllZeros(global_app_eui, sizeof(global_app_eui))
&& !isAllZeros(global_app_key, sizeof(global_app_key));
have_keys =
!is_all_zeros(global_dev_eui, sizeof(global_dev_eui)) && !is_all_zeros(global_app_key, sizeof(global_app_key));
if (!saveKeys())
return false;
return true;
}
// --- Non-volatile storage
bool TTNProvisioning::saveKeys()
bool ttn_provisioning_save_keys()
{
bool result = false;
@ -412,32 +427,32 @@ bool TTNProvisioning::saveKeys()
if (res != ESP_OK)
goto done;
if (!writeNvsValue(handle, NVS_FLASH_KEY_DEV_EUI, global_dev_eui, sizeof(global_dev_eui)))
if (!write_nvs_value(handle, NVS_FLASH_KEY_DEV_EUI, global_dev_eui, sizeof(global_dev_eui)))
goto done;
if (!writeNvsValue(handle, NVS_FLASH_KEY_APP_EUI, global_app_eui, sizeof(global_app_eui)))
if (!write_nvs_value(handle, NVS_FLASH_KEY_APP_EUI, global_app_eui, sizeof(global_app_eui)))
goto done;
if (!writeNvsValue(handle, NVS_FLASH_KEY_APP_KEY, global_app_key, sizeof(global_app_key)))
if (!write_nvs_value(handle, NVS_FLASH_KEY_APP_KEY, global_app_key, sizeof(global_app_key)))
goto done;
res = nvs_commit(handle);
ESP_ERROR_CHECK(res);
result = true;
ESP_LOGI(TAG, "Dev and app EUI and app key saved in NVS storage");
ESP_LOGI(TAG, "DevEUI, AppEUI/JoinEUI and AppKey saved in NVS storage");
done:
nvs_close(handle);
return result;
}
bool TTNProvisioning::restoreKeys(bool silent)
bool ttn_provisioning_restore_keys(bool silent)
{
uint8_t buf_dev_eui[8];
uint8_t buf_app_eui[8];
uint8_t buf_app_key[16];
nvs_handle handle = 0;
esp_err_t res = nvs_open(NVS_FLASH_PARTITION, NVS_READONLY, &handle);
if (res == ESP_ERR_NVS_NOT_FOUND)
@ -451,30 +466,29 @@ bool TTNProvisioning::restoreKeys(bool silent)
if (res != ESP_OK)
goto done;
if (!readNvsValue(handle, NVS_FLASH_KEY_DEV_EUI, buf_dev_eui, sizeof(global_dev_eui), silent))
if (!read_nvs_value(handle, NVS_FLASH_KEY_DEV_EUI, buf_dev_eui, sizeof(global_dev_eui), silent))
goto done;
if (!readNvsValue(handle, NVS_FLASH_KEY_APP_EUI, buf_app_eui, sizeof(global_app_eui), silent))
if (!read_nvs_value(handle, NVS_FLASH_KEY_APP_EUI, buf_app_eui, sizeof(global_app_eui), silent))
goto done;
if (!readNvsValue(handle, NVS_FLASH_KEY_APP_KEY, buf_app_key, sizeof(global_app_key), silent))
if (!read_nvs_value(handle, NVS_FLASH_KEY_APP_KEY, buf_app_key, sizeof(global_app_key), silent))
goto done;
memcpy(global_dev_eui, buf_dev_eui, sizeof(global_dev_eui));
memcpy(global_app_eui, buf_app_eui, sizeof(global_app_eui));
memcpy(global_app_key, buf_app_key, sizeof(global_app_key));
have_keys = !isAllZeros(global_dev_eui, sizeof(global_dev_eui))
&& !isAllZeros(global_app_eui, sizeof(global_app_eui))
&& !isAllZeros(global_app_key, sizeof(global_app_key));
have_keys =
!is_all_zeros(global_dev_eui, sizeof(global_dev_eui)) && !is_all_zeros(global_app_key, sizeof(global_app_key));
if (have_keys)
{
ESP_LOGI(TAG, "Dev and app EUI and app key have been restored from NVS storage");
ESP_LOGI(TAG, "DevEUI, AppEUI/JoinEUI and AppKey have been restored from NVS storage");
}
else
{
ESP_LOGW(TAG, "Dev and app EUI and app key are invalid (zeroes only)");
ESP_LOGW(TAG, "DevEUI or DevEUI is invalid (zeroes only)");
}
done:
@ -482,7 +496,7 @@ done:
return true;
}
bool TTNProvisioning::readNvsValue(nvs_handle handle, const char* key, uint8_t* data, size_t expected_length, bool silent)
bool read_nvs_value(nvs_handle handle, const char *key, uint8_t *data, size_t expected_length, bool silent)
{
size_t size = expected_length;
esp_err_t res = nvs_get_blob(handle, key, data, &size);
@ -502,32 +516,31 @@ bool TTNProvisioning::readNvsValue(nvs_handle handle, const char* key, uint8_t*
ESP_LOGW(TAG, "No NVS data found for %s", key);
return false;
}
ESP_ERROR_CHECK(res);
return false;
}
bool TTNProvisioning::writeNvsValue(nvs_handle handle, const char* key, const uint8_t* data, size_t len)
bool write_nvs_value(nvs_handle handle, const char *key, const uint8_t *data, size_t len)
{
uint8_t buf[16];
if (readNvsValue(handle, key, buf, len, true) && memcmp(buf, data, len) == 0)
if (read_nvs_value(handle, key, buf, len, true) && memcmp(buf, data, len) == 0)
return true; // unchanged
esp_err_t res = nvs_set_blob(handle, key, data, len);
ESP_ERROR_CHECK(res);
return res == ESP_OK;
}
// --- Helper functions ---
bool TTNProvisioning::hexStrToBin(const char *hex, uint8_t *buf, int len)
bool hex_str_to_bin(const char *hex, uint8_t *buf, int len)
{
const char* ptr = hex;
const char *ptr = hex;
for (int i = 0; i < len; i++)
{
int val = hexTupleToByte(ptr);
int val = hex_tuple_to_byte(ptr);
if (val < 0)
return false;
buf[i] = val;
@ -536,18 +549,18 @@ bool TTNProvisioning::hexStrToBin(const char *hex, uint8_t *buf, int len)
return true;
}
int TTNProvisioning::hexTupleToByte(const char *hex)
int hex_tuple_to_byte(const char *hex)
{
int nibble1 = hexDigitToVal(hex[0]);
int nibble1 = hex_digit_to_val(hex[0]);
if (nibble1 < 0)
return -1;
int nibble2 = hexDigitToVal(hex[1]);
int nibble2 = hex_digit_to_val(hex[1]);
if (nibble2 < 0)
return -1;
return (nibble1 << 4) | nibble2;
}
int TTNProvisioning::hexDigitToVal(char ch)
int hex_digit_to_val(char ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
@ -558,27 +571,27 @@ int TTNProvisioning::hexDigitToVal(char ch)
return -1;
}
void TTNProvisioning::binToHexStr(const uint8_t* buf, int len, char* hex)
void bin_to_hex_str(const uint8_t *buf, int len, char *hex)
{
for (int i = 0; i < len; i++)
{
uint8_t b = buf[i];
*hex = valToHexDigit((b & 0xf0) >> 4);
*hex = val_to_hex_digit((b & 0xf0) >> 4);
hex++;
*hex = valToHexDigit(b & 0x0f);
*hex = val_to_hex_digit(b & 0x0f);
hex++;
}
}
char TTNProvisioning::valToHexDigit(int val)
char val_to_hex_digit(int val)
{
return "0123456789ABCDEF"[val];
}
void TTNProvisioning::swapBytes(uint8_t* buf, int len)
void swap_bytes(uint8_t *buf, int len)
{
uint8_t* p1 = buf;
uint8_t* p2 = buf + len - 1;
uint8_t *p1 = buf;
uint8_t *p2 = buf + len - 1;
while (p1 < p2)
{
uint8_t t = *p1;
@ -589,10 +602,10 @@ void TTNProvisioning::swapBytes(uint8_t* buf, int len)
}
}
bool TTNProvisioning::isAllZeros(const uint8_t* buf, int len)
bool is_all_zeros(const uint8_t *buf, int len)
{
for (int i = 0; i < len; i++)
if (buf[i] != 0)
return false;
return true;
}
}

39
src/ttn_provisioning.h Normal file
View File

@ -0,0 +1,39 @@
/*******************************************************************************
*
* 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
*
* Task listening on a UART port for provisioning commands.
*******************************************************************************/
#ifndef TTN_PROVISIONING_H
#define TTN_PROVISIONING_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C"
{
#endif
void ttn_provisioning_init(void);
bool ttn_provisioning_have_keys(void);
bool ttn_provisioning_decode_keys(const char *dev_eui, const char *app_eui, const char *app_key);
bool ttn_provisioning_from_mac(const char *app_eui, const char *app_key);
bool ttn_provisioning_save_keys(void);
bool ttn_provisioning_restore_keys(bool silent);
#if defined(TTN_HAS_AT_COMMANDS)
void ttn_provisioning_start_task(void);
#endif
#ifdef __cplusplus
}
#endif
#endif

58
src/ttn_rtc.c Normal file
View File

@ -0,0 +1,58 @@
/*******************************************************************************
*
* 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
*
* Functions for storing and retrieving TTN communication state from RTC memory.
*******************************************************************************/
#include "ttn_rtc.h"
#include "esp_system.h"
#include "lmic/lmic.h"
#include <string.h>
#define LMIC_OFFSET(field) __builtin_offsetof(struct lmic_t, field)
#define LMIC_DIST(field1, field2) (LMIC_OFFSET(field2) - LMIC_OFFSET(field1))
#define TTN_RTC_MEM_SIZE (sizeof(struct lmic_t) - LMIC_OFFSET(radio) - MAX_LEN_PAYLOAD - MAX_LEN_FRAME)
#define TTN_RTC_FLAG_VALUE 0xf30b84ce
RTC_DATA_ATTR uint8_t ttn_rtc_mem_buf[TTN_RTC_MEM_SIZE];
RTC_DATA_ATTR uint32_t ttn_rtc_flag;
void ttn_rtc_save()
{
// Copy LMIC struct except client, osjob, pendTxData and frame
size_t len1 = LMIC_DIST(radio, pendTxData);
memcpy(ttn_rtc_mem_buf, &LMIC.radio, len1);
size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD;
memcpy(ttn_rtc_mem_buf + len1, (u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, len2);
size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME;
memcpy(ttn_rtc_mem_buf + len1 + len2, (u1_t *)&LMIC.frame + MAX_LEN_FRAME, len3);
ttn_rtc_flag = TTN_RTC_FLAG_VALUE;
}
bool ttn_rtc_restore()
{
if (ttn_rtc_flag != TTN_RTC_FLAG_VALUE)
return false;
// Restore data
size_t len1 = LMIC_DIST(radio, pendTxData);
memcpy(&LMIC.radio, ttn_rtc_mem_buf, len1);
memset(LMIC.pendTxData, 0, MAX_LEN_PAYLOAD);
size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD;
memcpy((u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, ttn_rtc_mem_buf + len1, len2);
memset(LMIC.frame, 0, MAX_LEN_FRAME);
size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME;
memcpy((u1_t *)&LMIC.frame + MAX_LEN_FRAME, ttn_rtc_mem_buf + len1 + len2, len3);
ttn_rtc_flag = 0xffffffff; // invalidate RTC data
return true;
}

30
src/ttn_rtc.h Normal file
View File

@ -0,0 +1,30 @@
/*******************************************************************************
*
* 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
*
* Functions for storing and retrieving TTN communication state from RTC memory.
*******************************************************************************/
#ifndef TTN_RTC_H
#define TTN_RTC_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C"
{
#endif
void ttn_rtc_save();
bool ttn_rtc_restore();
#ifdef __cplusplus
}
#endif
#endif