mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2025-06-15 12:24:27 +02:00
Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1057aa09de | ||
|
61771c0bfa | ||
|
89ad39d463 | ||
|
c32c6b4cd6 | ||
|
569afe84e9 | ||
|
4e0587bb8e | ||
|
877784640d | ||
|
79727fdae2 | ||
|
50d45e7fb7 | ||
|
bbdb44cdd6 | ||
|
e7d9caefe1 | ||
|
68d5669833 | ||
|
46ad09736b | ||
|
1b7279518b | ||
|
c1517d3c82 | ||
|
ffcdd12d48 | ||
|
0a7353c146 | ||
|
54a91a27db | ||
|
ee8eead337 | ||
|
f06be4c069 | ||
|
5a7b3b0fcb | ||
|
9fd24c65ec | ||
|
31b00f05a8 | ||
|
ba481ceac5 | ||
|
b626ccb61a | ||
|
f433e826a7 | ||
|
0130928601 | ||
|
e71d584fca | ||
|
de4297a8f4 | ||
|
ee91ccc613 | ||
|
7df10bd6bc | ||
|
bd728887cf | ||
|
1df2c50f6f | ||
|
8fa345a5d4 | ||
|
e34dbcb467 | ||
|
72b878f1d9 | ||
|
17e432d714 | ||
|
5c1db030f2 | ||
|
eb5792fbb9 | ||
|
37d7f8d517 | ||
|
62e829b0d3 | ||
|
6df4b40122 | ||
|
7fa43dbbdb | ||
|
ba908c0b93 | ||
|
281ba52155 | ||
|
36edf92944 | ||
|
f421db44d7 | ||
|
8e2886db27 | ||
|
973a7c41c8 | ||
|
99bab17d4b | ||
|
1913840679 | ||
|
5c695fd223 | ||
|
3086b1bb58 | ||
|
f57c505d7e | ||
|
ba22c5a745 | ||
|
5b22f228da | ||
|
84b2f88f0c | ||
|
fffe7bd8b3 | ||
|
bb41c4960f |
3
.clang-format
Normal file
3
.clang-format
Normal file
@ -0,0 +1,3 @@
|
||||
BasedOnStyle: Microsoft
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 120
|
5
.vscode/c_cpp_properties.json
vendored
5
.vscode/c_cpp_properties.json
vendored
@ -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
|
||||
|
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -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"
|
||||
}
|
||||
}
|
@ -9,6 +9,10 @@ set(COMPONENT_ADD_INCLUDEDIRS
|
||||
)
|
||||
set(COMPONENT_REQUIRES
|
||||
nvs_flash
|
||||
mbedtls
|
||||
driver
|
||||
esp_event
|
||||
esp_timer
|
||||
)
|
||||
|
||||
register_component()
|
||||
|
2
Kconfig
2
Kconfig
@ -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
|
||||
|
||||
|
2
LICENSE
2
LICENSE
@ -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
|
||||
|
27
README.md
27
README.md
@ -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
|
||||
|
||||
|
@ -1,2 +0,0 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
COMPONENT_SRCDIRS := src src/aes src/hal src/lmic
|
5
doc/.gitignore
vendored
Normal file
5
doc/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
xml/
|
||||
html/
|
||||
api.md
|
||||
node_modules/
|
||||
ttn-esp32.zip
|
2619
doc/Doxyfile
Normal file
2619
doc/Doxyfile
Normal file
File diff suppressed because it is too large
Load Diff
21
doc/doc.dox
Normal file
21
doc/doc.dox
Normal 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
|
||||
|
||||
*/
|
@ -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));
|
||||
}
|
||||
}
|
107
doc/doxygen-awesome-css/doxygen-awesome-sidebar-only.css
Normal file
107
doc/doxygen-awesome-css/doxygen-awesome-sidebar-only.css
Normal 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;
|
||||
}
|
||||
}
|
1478
doc/doxygen-awesome-css/doxygen-awesome.css
Normal file
1478
doc/doxygen-awesome-css/doxygen-awesome.css
Normal file
File diff suppressed because it is too large
Load Diff
5
doc/generate.sh
Executable file
5
doc/generate.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
rm -rf html ttn-esp32.zip
|
||||
doxygen Doxyfile
|
||||
cd html
|
||||
zip -r ../ttn-esp32.zip .
|
10
examples/deep_sleep/CMakeLists.txt
Normal file
10
examples/deep_sleep/CMakeLists.txt
Normal 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)
|
131
examples/deep_sleep/main/main.cpp
Normal file
131
examples/deep_sleep/main/main.cpp
Normal 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();
|
||||
}
|
10
examples/deep_sleep_in_c/CMakeLists.txt
Normal file
10
examples/deep_sleep_in_c/CMakeLists.txt
Normal 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)
|
4
examples/deep_sleep_in_c/main/CMakeLists.txt
Normal file
4
examples/deep_sleep_in_c/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ttn-esp32)
|
135
examples/deep_sleep_in_c/main/main.c
Normal file
135
examples/deep_sleep_in_c/main/main.c
Normal 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();
|
||||
}
|
@ -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)
|
||||
|
@ -1,5 +0,0 @@
|
||||
PROJECT_NAME := hello_world
|
||||
|
||||
EXTRA_COMPONENT_DIRS := $(abspath ../..)
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
@ -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())
|
||||
{
|
||||
|
10
examples/hello_world_in_c/CMakeLists.txt
Normal file
10
examples/hello_world_in_c/CMakeLists.txt
Normal 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)
|
4
examples/hello_world_in_c/main/CMakeLists.txt
Normal file
4
examples/hello_world_in_c/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ttn-esp32)
|
117
examples/hello_world_in_c/main/main.c
Normal file
117
examples/hello_world_in_c/main/main.c
Normal 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");
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
PROJECT_NAME := mac_address
|
||||
|
||||
EXTRA_COMPONENT_DIRS := $(abspath ../..)
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
@ -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);
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
PROJECT_NAME := monitoring
|
||||
|
||||
EXTRA_COMPONENT_DIRS := $(abspath ../..)
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
@ -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);
|
||||
|
||||
|
@ -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)
|
4
examples/power_off/main/CMakeLists.txt
Normal file
4
examples/power_off/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "main.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ttn-esp32)
|
@ -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));
|
||||
}
|
10
examples/power_off_in_c/CMakeLists.txt
Normal file
10
examples/power_off_in_c/CMakeLists.txt
Normal 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)
|
4
examples/power_off_in_c/main/CMakeLists.txt
Normal file
4
examples/power_off_in_c/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ttn-esp32)
|
128
examples/power_off_in_c/main/main.c
Normal file
128
examples/power_off_in_c/main/main.c
Normal 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));
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
PROJECT_NAME := provisioning
|
||||
|
||||
EXTRA_COMPONENT_DIRS := $(abspath ../..)
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
@ -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);
|
||||
|
||||
|
@ -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
822
include/ttn.h
Normal 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
|
@ -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": [
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
611
src/hal/hal_esp32.c
Executable 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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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_
|
||||
|
@ -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_
|
||||
|
@ -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_
|
||||
|
@ -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_
|
||||
|
@ -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_
|
||||
|
@ -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_
|
||||
|
@ -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_
|
||||
|
217
src/lmic/lmic_channelshuffle.c
Normal file
217
src/lmic/lmic_channelshuffle.c
Normal 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;
|
||||
}
|
@ -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: {
|
||||
|
@ -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_ */
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
562
src/ttn.c
Normal 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
313
src/ttn_logging.c
Normal 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
50
src/ttn_logging.h
Normal 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
130
src/ttn_nvs.c
Normal 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
30
src/ttn_nvs.h
Normal 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
|
@ -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
39
src/ttn_provisioning.h
Normal 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
58
src/ttn_rtc.c
Normal 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
30
src/ttn_rtc.h
Normal 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
|
Loading…
Reference in New Issue
Block a user