69 Commits

Author SHA1 Message Date
569afe84e9 Weak references for more options to control low speed pins 2023-04-26 10:00:57 +02:00
4e0587bb8e use weak references for HAL functions that control low-speed pins - allows override with own symbols 2023-04-25 09:41:37 +02:00
877784640d Increase version number to 4.2.0-1 2022-09-27 19:20:53 +02:00
79727fdae2 Merge branch 'FrancoLiberali-master' 2022-09-21 21:00:15 +02:00
50d45e7fb7 Upgrade to latest LMIC code (v4.2.0-1) 2022-09-21 20:57:23 +02:00
bbdb44cdd6 Add backward compatibility with v4.x 2022-09-21 20:57:23 +02:00
e7d9caefe1 fix compilation issues 2022-09-21 20:57:23 +02:00
68d5669833 Upgrade to latest LMIC code (v4.2.0-1) 2022-09-21 20:48:49 +02:00
46ad09736b Fix for compilation issue in ESP-IDF v5.x 2022-09-21 20:32:46 +02:00
1b7279518b Add backward compatibility with v4.x 2022-09-21 20:29:52 +02:00
c1517d3c82 fix compilation issues 2022-09-20 18:56:29 +02:00
ffcdd12d48 fix configPower for sx1272 2022-07-29 15:24:20 +02:00
0a7353c146 Assert NSS from SPI callbacks 2022-04-15 12:17:26 +02:00
54a91a27db Improve SPI robustness (fix incompatibility with ESP32-S2) 2022-03-26 20:06:03 +01:00
ee8eead337 Improve compatibility with future ESP-IDF versions 2022-01-30 20:27:18 +01:00
f06be4c069 Update README with link to Wiki 2021-09-29 21:31:48 +02:00
5a7b3b0fcb Fix references in documentation 2021-09-29 19:44:16 +02:00
9fd24c65ec Enhanced deep sleep example 2021-09-29 19:40:57 +02:00
31b00f05a8 Mention deep sleep in README 2021-09-29 18:57:05 +02:00
ba481ceac5 Power off for C++ 2021-09-29 18:02:52 +02:00
b626ccb61a Improve robustness of restore from NVS 2021-09-29 17:46:04 +02:00
f433e826a7 Save and restore from power off 2021-09-29 16:48:59 +02:00
0130928601 Fix ttn_wait_for_idle for MAC cmd processing 2021-09-28 22:38:05 +02:00
e71d584fca Save and restore for deep sleep 2021-09-28 17:35:05 +02:00
de4297a8f4 Delete Makefiles 2021-09-28 10:17:37 +02:00
ee91ccc613 Rename ttn_join() 2021-09-28 10:14:32 +02:00
7df10bd6bc Wait for idle 2021-09-26 20:07:57 +02:00
bd728887cf Prepare for deep sleep functions 2021-09-26 16:35:42 +02:00
1df2c50f6f Fix ttn_get_rf_settings() 2021-08-20 20:41:34 +02:00
8fa345a5d4 Adapted code formatting 2021-07-31 17:03:00 +02:00
e34dbcb467 Link to new API documentation 2021-07-31 12:51:41 +02:00
72b878f1d9 Update README with info for v4.0 2021-07-31 12:36:47 +02:00
17e432d714 Improved API documentation 2021-07-30 23:04:06 +02:00
5c1db030f2 Improve documentation output 2021-07-29 23:06:13 +02:00
eb5792fbb9 Default to subband 2 2021-07-28 23:52:10 +02:00
37d7f8d517 Methods for setting data rate and TX power 2021-07-28 23:48:54 +02:00
62e829b0d3 Example for C 2021-07-27 23:01:04 +02:00
6df4b40122 Allow all zeroes for AppEUI/JoinEUI 2021-07-27 22:37:22 +02:00
7fa43dbbdb Allow disabling subband seletion 2021-07-27 22:08:00 +02:00
ba908c0b93 Configue sub-band 2021-07-26 22:29:20 +02:00
281ba52155 C implementation for TheThingsNetwork 2021-07-25 23:53:54 +02:00
36edf92944 Remove TTNProvisioning.h 2021-07-25 22:15:21 +02:00
f421db44d7 Convert logging to C 2021-07-25 20:49:02 +02:00
8e2886db27 Convert hal_esp32 to C 2021-07-25 20:03:16 +02:00
973a7c41c8 Convert TTNProvisioning to C 2021-07-25 17:00:08 +02:00
99bab17d4b Upgrade to mcci-catena/arduino-lmic 4.0.1-pre 2021-07-25 14:39:11 +02:00
1913840679 Prepare for release 3.3 2021-01-05 21:57:24 +01:00
5c695fd223 Upgrade to version 3.3 of arduino-lmic 2021-01-05 21:57:11 +01:00
3086b1bb58 Static word-aligned buffer for SPI 2021-01-05 21:26:36 +01:00
f57c505d7e Merge branch 'iram_isr_fix' of https://github.com/vladcebo/ttn-esp32 into vladcebo-iram_isr_fix 2021-01-05 21:04:57 +01:00
ba22c5a745 Fix excessiv writing of keys to NVS 2021-01-05 20:55:14 +01:00
5b22f228da Prevent hal_ticks() from going into .text region
This will fix an issue when the flash is disabled (due to OTA update for example) and the cpu crashes with cache disabled access from ISR (when TTN executes)
2021-01-02 17:32:30 +00:00
84b2f88f0c Fix excessive writing of keys to NVS 2020-09-27 16:21:04 +02:00
fffe7bd8b3 Improve documentation 2020-08-02 19:53:02 +02:00
bb41c4960f README updated for 3.2.0 2020-08-02 10:19:58 +02:00
f70ad2a996 Version 2.3 2020-08-01 18:32:10 +02:00
fdacae73f8 Add rssi() function 2020-08-01 18:30:47 +02:00
9489f34542 RF parameter monitoring 2020-08-01 18:21:42 +02:00
2a31ef0096 Update example build configuration 2020-08-01 10:28:25 +02:00
e15d936bb4 Merge send_recv example into hello_world 2020-08-01 10:23:30 +02:00
a8cd58214d Modify example for PlatformIO.ini 2020-08-01 10:13:11 +02:00
b3e0b567b3 Merge branch 'cc32d9-dev' into dev 2020-07-31 21:54:26 +02:00
0853fe05ec Rename reset options 2020-07-31 21:54:09 +02:00
625968cd99 Add shutdown function 2020-07-31 20:14:40 +02:00
8d7d157445 Add shutdown function 2020-07-31 20:14:31 +02:00
b73ed23a97 Enable/disable ADR 2020-07-31 17:48:11 +02:00
513ac44268 Upgrade to LMIC version 3.2.0 2020-07-31 16:32:28 +02:00
45778d2186 New config option: TTN_RADIO_RST_KEEP_ASSERTED 2020-05-25 23:00:00 +02:00
e29477fa7e Update to latest LMIC version (3.0.99.10) 2020-01-01 17:00:47 +01:00
104 changed files with 10600 additions and 2370 deletions

3
.clang-format Normal file
View File

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

View File

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

View File

@ -1,11 +1,11 @@
{ {
"files.associations": { "files.associations": {
"provisioning.h": "c",
"config.h": "c",
"sdkconfig.h": "c", "sdkconfig.h": "c",
"oslmic.h": "c", "oslmic.h": "c",
"hal_esp32.h": "c", "hal_esp32.h": "c",
"esp_log.h": "c", "esp_log.h": "c",
"nvs_flash.h": "c" "lmic.h": "c",
"ttn_provisioning.h": "c",
"lorabase.h": "c"
} }
} }

View File

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

21
Kconfig
View File

@ -16,8 +16,8 @@ config TTN_LORA_FREQ_EU_868
config TTN_LORA_FREQ_US_915 config TTN_LORA_FREQ_US_915
bool "North and South America (915 MHz)" bool "North and South America (915 MHz)"
config TTN_LORA_FREQ_AU_921 config TTN_LORA_FREQ_AU_915
bool "Australia (921 MHz)" bool "Australia (915 MHz)"
config TTN_LORA_FREQ_AS_923 config TTN_LORA_FREQ_AS_923
bool "Asia (923 MHz)" bool "Asia (923 MHz)"
@ -51,10 +51,25 @@ endchoice
config TTN_SPI_FREQ config TTN_SPI_FREQ
int "SPI frequency (in Hz)" int "SPI frequency (in Hz)"
default 10000000 default 2000000
help help
SPI frequency to communicate between ESP32 and SX127x radio chip SPI frequency to communicate between ESP32 and SX127x radio chip
choice TTN_RESET
prompt "Reset states"
default TTN_RESET_STATES_FLOATING
help
Reset pin can be floating for most boards and shields.
A few boards/shields require the pin to be held high for operation.
config TTN_RESET_STATES_FLOATING
bool "Toggle between low and floating"
config TTN_RESET_STATES_ASSERTED
bool "Toggle between low and high"
endchoice
config TTN_BG_TASK_PRIO config TTN_BG_TASK_PRIO
int "Background task priority" int "Background task priority"
default 10 default 10

View File

@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -2,15 +2,30 @@
**The Things Network device library for ESP-IDF (ESP32) supporting devices with Semtech SX127x chips** **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) - OTAA (over-the-air activation)
- uplink and downlink messages - uplink and downlink messages
- saving the EUIs and key in non-volatile memory - 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) - deep sleep and power off without the need for rejoining
- support for regions Eurpe, North America, Australia, Asia and India - [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 ## Get Started
@ -18,11 +33,11 @@ Follow the detailed [Get Started Guide](https://github.com/manuelbl/ttn-esp32/wi
## Supported Boards ## 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 ## 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 ## More information

View File

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

5
doc/.gitignore vendored Normal file
View File

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

2619
doc/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

21
doc/doc.dox Normal file
View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

5
doc/generate.sh Executable file
View File

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

View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# Update the below line to match the path to the ttn-esp32 library,
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(deep_sleep)

View File

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

View File

@ -0,0 +1,133 @@
/*******************************************************************************
*
* 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;
spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO;
spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI;
spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK;
spi_bus_config.quadwp_io_num = -1;
spi_bus_config.quadhd_io_num = -1;
spi_bus_config.max_transfer_sz = 0;
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
ESP_ERROR_CHECK(err);
// Configure the SX127x pins
ttn.configurePins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
// The below line can be commented after the first run as the data is saved in NVS
ttn.provision(devEui, appEui, appKey);
// Register callback for received messages
ttn.onMessage(messageReceived);
// ttn.setAdrEnabled(false);
// ttn.setDataRate(kTTNDataRate_US915_SF7);
// ttn.setMaxTxPower(14);
if (ttn.resumeAfterDeepSleep())
{
printf("Resumed from deep sleep.\n");
}
else
{
printf("Joining...\n");
if (ttn.join())
{
printf("Joined.\n");
}
else
{
printf("Join failed. Goodbye\n");
return;
}
}
printf("Sending message...\n");
counter_in_rtc_mem++;
char message[20];
sprintf(message, "Hello %d", counter_in_rtc_mem);
TTNResponseCode res = ttn.transmitMessage((uint8_t *)message, strlen(message));
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
// Wait until TTN communication is idle and save state
ttn.waitForIdle();
ttn.prepareForDeepSleep();
// Schedule wake up
esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000LL);
printf("Going to deep sleep...\n");
esp_deep_sleep_start();
}

View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# Update the below line to match the path to the ttn-esp32 library,
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(deep_sleep)

View File

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

View File

@ -0,0 +1,135 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Sample program (in C) sending messages and going to deep sleep in-between.
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include <stdio.h>
#include <string.h>
#include "ttn.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
// Go to Components / The Things Network, select the appropriate values and save.
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19
#define TTN_PIN_NSS 18
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
#define TTN_PIN_RST 14
#define TTN_PIN_DIO0 26
#define TTN_PIN_DIO1 35
#define TX_INTERVAL 60 // in sec
// This counter value is retained (in RTC memory) during deep sleep
RTC_DATA_ATTR int counter_in_rtc_mem;
void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
{
printf("Message of %d bytes received on port %d:", length, port);
for (int i = 0; i < length; i++)
printf(" %02x", message[i]);
printf("\n");
}
void app_main(void)
{
esp_err_t err;
// Initialize the GPIO ISR handler service
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
ESP_ERROR_CHECK(err);
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
err = nvs_flash_init();
ESP_ERROR_CHECK(err);
// Initialize SPI bus
spi_bus_config_t spi_bus_config = {
.miso_io_num = TTN_PIN_SPI_MISO,
.mosi_io_num = TTN_PIN_SPI_MOSI,
.sclk_io_num = TTN_PIN_SPI_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1
};
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
ESP_ERROR_CHECK(err);
// Initialize TTN
ttn_init();
// Configure the SX127x pins
ttn_configure_pins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
// The below line can be commented after the first run as the data is saved in NVS
ttn_provision(devEui, appEui, appKey);
// Register callback for received messages
ttn_on_message(messageReceived);
// ttn_set_adr_enabled(false);
// ttn_set_data_rate(TTN_DR_US915_SF7);
// ttn_set_max_tx_pow(14);
if (ttn_resume_after_deep_sleep()) {
printf("Resumed from deep sleep.\n");
} else {
printf("Joining...\n");
if (ttn_join())
{
printf("Joined.\n");
}
else
{
printf("Join failed. Goodbye\n");
return;
}
}
printf("Sending message...\n");
counter_in_rtc_mem++;
char message[20];
sprintf(message, "Hello %d", counter_in_rtc_mem);
ttn_response_code_t res = ttn_transmit_message((uint8_t *)message, strlen(message), 1, false);
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
// Wait until TTN communication is idle and save state
ttn_wait_for_idle();
ttn_prepare_for_deep_sleep();
// Schedule wake up
esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000LL);
printf("Going to deep sleep...\n");
esp_deep_sleep_start();
}

View File

@ -1,9 +1,10 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
get_filename_component(TTN_DIR ../.. ABSOLUTE)
set(EXTRA_COMPONENT_DIRS "${TTN_DIR}")
include($ENV{IDF_PATH}/tools/cmake/project.cmake) 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) project(hello_world)

View File

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

View File

@ -1,4 +1,4 @@
set(COMPONENT_SRCS "main.cpp") idf_component_register(
set(COMPONENT_ADD_INCLUDEDIRS "") SRCS "main.cpp"
INCLUDE_DIRS "."
register_component() REQUIRES ttn-esp32)

View File

@ -7,39 +7,41 @@
* Licensed under MIT License * Licensed under MIT License
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
* *
* Sample program showing how to send a test message every 30 second. * Sample program showing how to send and receive messages.
*******************************************************************************/ *******************************************************************************/
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "esp_event.h" #include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "TheThingsNetwork.h" #include "TheThingsNetwork.h"
// NOTE: // 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. // Go to Components / The Things Network, select the appropriate values and save.
// Copy the below hex string from the "Device EUI" field // Copy the below hex strings from the TTN console (Applications > Your application > End devices
// on your device's overview page in the TTN console. // > Your device > Activation information)
const char *devEui = "????????????????";
// Copy the below two lines from bottom of the same page // AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????"; const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????"; const char *appKey = "????????????????????????????????";
// Pins and other resources // Pins and other resources
#define TTN_SPI_HOST HSPI_HOST #define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN 1 #define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5 #define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27 #define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19 #define TTN_PIN_SPI_MISO 19
#define TTN_PIN_NSS 18 #define TTN_PIN_NSS 18
#define TTN_PIN_RXTX TTN_NOT_CONNECTED #define TTN_PIN_RXTX TTN_NOT_CONNECTED
#define TTN_PIN_RST TTN_NOT_CONNECTED #define TTN_PIN_RST 14
#define TTN_PIN_DIO0 26 #define TTN_PIN_DIO0 26
#define TTN_PIN_DIO1 33 #define TTN_PIN_DIO1 35
static TheThingsNetwork ttn; static TheThingsNetwork ttn;
@ -54,10 +56,18 @@ void sendMessages(void* pvParameter)
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1); TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n"); printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
vTaskDelay(TX_INTERVAL * 1000 / portTICK_PERIOD_MS); 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");
}
extern "C" void app_main(void) extern "C" void app_main(void)
{ {
esp_err_t err; esp_err_t err;
@ -86,6 +96,13 @@ extern "C" void app_main(void)
// The below line can be commented after the first run as the data is saved in NVS // The below line can be commented after the first run as the data is saved in NVS
ttn.provision(devEui, appEui, appKey); ttn.provision(devEui, appEui, appKey);
// Register callback for received messages
ttn.onMessage(messageReceived);
// ttn.setAdrEnabled(false);
// ttn.setDataRate(kTTNDataRate_US915_SF7);
// ttn.setMaxTxPower(14);
printf("Joining...\n"); printf("Joining...\n");
if (ttn.join()) if (ttn.join())
{ {

View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# Update the below line to match the path to the ttn-esp32 library,
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(hello_world)

View File

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

View File

@ -0,0 +1,117 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Sample program for C showing how to send and receive messages.
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "ttn.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
// Go to Components / The Things Network, select the appropriate values and save.
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19
#define TTN_PIN_NSS 18
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
#define TTN_PIN_RST 14
#define TTN_PIN_DIO0 26
#define TTN_PIN_DIO1 35
#define TX_INTERVAL 30
static uint8_t msgData[] = "Hello, world";
void sendMessages(void* pvParameter)
{
while (1) {
printf("Sending message...\n");
ttn_response_code_t res = ttn_transmit_message(msgData, sizeof(msgData) - 1, 1, false);
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
}
}
void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
{
printf("Message of %d bytes received on port %d:", length, port);
for (int i = 0; i < length; i++)
printf(" %02x", message[i]);
printf("\n");
}
void app_main(void)
{
esp_err_t err;
// Initialize the GPIO ISR handler service
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
ESP_ERROR_CHECK(err);
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
err = nvs_flash_init();
ESP_ERROR_CHECK(err);
// Initialize SPI bus
spi_bus_config_t spi_bus_config = {
.miso_io_num = TTN_PIN_SPI_MISO,
.mosi_io_num = TTN_PIN_SPI_MOSI,
.sclk_io_num = TTN_PIN_SPI_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1
};
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
ESP_ERROR_CHECK(err);
// Initialize TTN
ttn_init();
// Configure the SX127x pins
ttn_configure_pins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
// The below line can be commented after the first run as the data is saved in NVS
ttn_provision(devEui, appEui, appKey);
// Register callback for received messages
ttn_on_message(messageReceived);
// ttn_set_adr_enabled(false);
// ttn_set_data_rate(TTN_DR_US915_SF7);
// ttn_set_max_tx_pow(14);
printf("Joining...\n");
if (ttn_join())
{
printf("Joined.\n");
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, NULL);
}
else
{
printf("Join failed. Goodbye\n");
}
}

View File

@ -1,9 +1,8 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
get_filename_component(TTN_DIR ../.. ABSOLUTE)
set(EXTRA_COMPONENT_DIRS "${TTN_DIR}")
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# Update the below line to match the path to the ttn-esp32 library,
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
project(mac_address) project(mac_address)

View File

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

View File

@ -1,4 +1,4 @@
set(COMPONENT_SRCS "main.cpp") idf_component_register(
set(COMPONENT_ADD_INCLUDEDIRS "") SRCS "main.cpp"
INCLUDE_DIRS "."
register_component() REQUIRES ttn-esp32)

View File

@ -17,18 +17,24 @@
#include "TheThingsNetwork.h" #include "TheThingsNetwork.h"
// NOTE: // 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. // Go to Components / The Things Network, select the appropriate values and save.
// Copy the below two lines from the bottom // Copy the below hex strings from the TTN console (Applications > Your application > End devices
// of your device's overview page in the TTN console. // > Your device > Activation information)
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????"; const char *appEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????"; const char *appKey = "????????????????????????????????";
// Pins and other resources // Pins and other resources
#define TTN_SPI_HOST HSPI_HOST #define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN 1 #define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5 #define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27 #define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19 #define TTN_PIN_SPI_MISO 19
@ -51,7 +57,7 @@ void sendMessages(void* pvParameter)
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1); TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n"); printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
vTaskDelay(TX_INTERVAL * 1000 / portTICK_PERIOD_MS); vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
} }
} }

View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# Update the below line to match the path to the ttn-esp32 library,
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
project(monitoring)

View File

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

View File

@ -17,21 +17,25 @@
#include "TheThingsNetwork.h" #include "TheThingsNetwork.h"
// NOTE: // 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. // Go to Components / The Things Network, select the appropriate values and save.
// Copy the below hex string from the "Device EUI" field // Copy the below hex strings from the TTN console (Applications > Your application > End devices
// on your device's overview page in the TTN console. // > Your device > Activation information)
const char *devEui = "????????????????";
// Copy the below two lines from bottom of the same page // AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????"; const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????"; const char *appKey = "????????????????????????????????";
// Pins and other resources // Pins and other resources
#define TTN_SPI_HOST HSPI_HOST #define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN 1 #define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5 #define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27 #define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19 #define TTN_PIN_SPI_MISO 19
@ -39,7 +43,7 @@ const char *appKey = "????????????????????????????????";
#define TTN_PIN_RXTX TTN_NOT_CONNECTED #define TTN_PIN_RXTX TTN_NOT_CONNECTED
#define TTN_PIN_RST 14 #define TTN_PIN_RST 14
#define TTN_PIN_DIO0 26 #define TTN_PIN_DIO0 26
#define TTN_PIN_DIO1 33 #define TTN_PIN_DIO1 35
static TheThingsNetwork ttn; static TheThingsNetwork ttn;
@ -47,23 +51,53 @@ const unsigned TX_INTERVAL = 30;
static uint8_t msgData[] = "Hello, world"; static uint8_t msgData[] = "Hello, world";
void printRFSettings(const char* window, const TTNRFSettings& settings)
{
int bw = (1 << (static_cast<int>(settings.bandwidth) - 1)) * 125;
int sf = static_cast<int>(settings.spreadingFactor) + 5;
if (settings.spreadingFactor == kTTNSFNone)
{
printf("%s: not used\n", window);
}
else if (settings.spreadingFactor == kTTNFSK)
{
printf("%s: FSK, BW %dkHz, %d.%d MHz\n",
window, bw, settings.frequency / 1000000, (settings.frequency % 1000000 + 50000) / 100000);
}
else
{
printf("%s: SF%d, BW %dkHz, %d.%d MHz\n",
window, sf, bw, settings.frequency / 1000000, (settings.frequency % 1000000 + 50000) / 100000);
}
}
void printAllRFSettings()
{
printRFSettings("TX ", ttn.txSettings());
printRFSettings("RX1", ttn.rx1Settings());
printRFSettings("RX2", ttn.rx2Settings());
}
void sendMessages(void* pvParameter) void sendMessages(void* pvParameter)
{ {
while (1) { while (1) {
printf("Sending message...\n"); printf("Sending message...\n");
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1); TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n"); printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
printAllRFSettings();
vTaskDelay(TX_INTERVAL * 1000 / portTICK_PERIOD_MS); vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
} }
} }
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); printf("Message of %d bytes received on port %d:", length, port);
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
printf(" %02x", message[i]); printf(" %02x", message[i]);
printf("\n"); printf("\n");
printf("RSSI: %d dBm\n", ttn.rssi());
} }
extern "C" void app_main(void) extern "C" void app_main(void)
@ -94,12 +128,15 @@ extern "C" void app_main(void)
// The below line can be commented after the first run as the data is saved in NVS // The below line can be commented after the first run as the data is saved in NVS
ttn.provision(devEui, appEui, appKey); ttn.provision(devEui, appEui, appKey);
// Register callback for received messages
ttn.onMessage(messageReceived); ttn.onMessage(messageReceived);
printf("Joining...\n"); printf("Joining...\n");
if (ttn.join()) if (ttn.join())
{ {
printf("Joined.\n"); printf("Joined.\n");
printAllRFSettings();
printf("RSSI: %d dBm\n", ttn.rssi());
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, nullptr); xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, nullptr);
} }
else else

View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# Update the below line to match the path to the ttn-esp32 library,
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(power_off)

View File

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

View File

@ -0,0 +1,126 @@
/*******************************************************************************
*
* 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 "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;
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");
}
extern "C" void app_main(void)
{
esp_err_t err;
// Initialize the GPIO ISR handler service
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
ESP_ERROR_CHECK(err);
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
err = nvs_flash_init();
ESP_ERROR_CHECK(err);
// Initialize SPI bus
spi_bus_config_t spi_bus_config;
spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO;
spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI;
spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK;
spi_bus_config.quadwp_io_num = -1;
spi_bus_config.quadhd_io_num = -1;
spi_bus_config.max_transfer_sz = 0;
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
ESP_ERROR_CHECK(err);
// Configure the SX127x pins
ttn.configurePins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
// The below line can be commented after the first run as the data is saved in NVS
ttn.provision(devEui, appEui, appKey);
// Register callback for received messages
ttn.onMessage(messageReceived);
// ttn.setAdrEnabled(false);
// ttn.setDataRate(kTTNDataRate_US915_SF7);
// ttn.setMaxTxPower(14);
if (ttn.resumeAfterPowerOff(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");
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
// Wait until TTN communication is idle and save state
ttn.waitForIdle();
ttn.prepareForPowerOff();
printf("Power off...\n");
// Do whatever is needed to power off the device.
// For testing, press reset button to simulate power cycle.
while (true)
vTaskDelay(pdMS_TO_TICKS(1000));
}

View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# Update the below line to match the path to the ttn-esp32 library,
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
#add_definitions(-DLMIC_ENABLE_event_logging=1)
project(power_off)

View File

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

View File

@ -0,0 +1,128 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Sample program (in C) sending messages and powering off in-between.
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "ttn.h"
// NOTE:
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
// Go to Components / The Things Network, select the appropriate values and save.
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
// > Your device > Activation information)
// AppEUI (sometimes called JoinEUI)
const char *appEui = "????????????????";
// DevEUI
const char *devEui = "????????????????";
// AppKey
const char *appKey = "????????????????????????????????";
// Pins and other resources
#define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19
#define TTN_PIN_NSS 18
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
#define TTN_PIN_RST 14
#define TTN_PIN_DIO0 26
#define TTN_PIN_DIO1 35
#define TX_INTERVAL 60 // in sec
static uint8_t msgData[] = "Hello, world";
void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
{
printf("Message of %d bytes received on port %d:", length, port);
for (int i = 0; i < length; i++)
printf(" %02x", message[i]);
printf("\n");
}
void app_main(void)
{
esp_err_t err;
// Initialize the GPIO ISR handler service
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
ESP_ERROR_CHECK(err);
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
err = nvs_flash_init();
ESP_ERROR_CHECK(err);
// Initialize SPI bus
spi_bus_config_t spi_bus_config = {
.miso_io_num = TTN_PIN_SPI_MISO,
.mosi_io_num = TTN_PIN_SPI_MOSI,
.sclk_io_num = TTN_PIN_SPI_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1
};
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
ESP_ERROR_CHECK(err);
// Initialize TTN
ttn_init();
// Configure the SX127x pins
ttn_configure_pins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
// The below line can be commented after the first run as the data is saved in NVS
ttn_provision(devEui, appEui, appKey);
// Register callback for received messages
ttn_on_message(messageReceived);
// ttn_set_adr_enabled(false);
// ttn_set_data_rate(TTN_DR_US915_SF7);
// ttn_set_max_tx_pow(14);
if (ttn_resume_after_power_off(60)) {
printf("Resumed from power off.\n");
} else {
printf("Joining...\n");
if (ttn_join())
{
printf("Joined.\n");
}
else
{
printf("Join failed. Goodbye\n");
return;
}
}
printf("Sending message...\n");
ttn_response_code_t res = ttn_transmit_message(msgData, sizeof(msgData) - 1, 1, false);
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
// Wait until TTN communication is idle and save state
ttn_wait_for_idle();
ttn_prepare_for_power_off();
printf("Power off...\n");
// Do whatever is needed to power off the device.
// For testing, press reset button to simulate power cycle.
while (true)
vTaskDelay(pdMS_TO_TICKS(1000));
}

View File

@ -1,9 +1,8 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
get_filename_component(TTN_DIR ../.. ABSOLUTE)
set(EXTRA_COMPONENT_DIRS "${TTN_DIR}")
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# Update the below line to match the path to the ttn-esp32 library,
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
list(APPEND EXTRA_COMPONENT_DIRS "../..")
project(provisioning) project(provisioning)

View File

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

View File

@ -1,4 +1,4 @@
set(COMPONENT_SRCS "main.cpp") idf_component_register(
set(COMPONENT_ADD_INCLUDEDIRS "") SRCS "main.cpp"
INCLUDE_DIRS "."
register_component() REQUIRES ttn-esp32)

View File

@ -17,13 +17,15 @@
#include "TheThingsNetwork.h" #include "TheThingsNetwork.h"
// NOTE: // 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 'make menuconfig'.
// Go to Components / The Things Network, select the appropriate values and save. // Go to Components / The Things Network, select the appropriate values and save.
// Pins and other resources // Pins and other resources
#define TTN_SPI_HOST HSPI_HOST #define TTN_SPI_HOST SPI2_HOST
#define TTN_SPI_DMA_CHAN 1 #define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
#define TTN_PIN_SPI_SCLK 5 #define TTN_PIN_SPI_SCLK 5
#define TTN_PIN_SPI_MOSI 27 #define TTN_PIN_SPI_MOSI 27
#define TTN_PIN_SPI_MISO 19 #define TTN_PIN_SPI_MISO 19
@ -46,7 +48,7 @@ void sendMessages(void* pvParameter)
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1); TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n"); printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
vTaskDelay(TX_INTERVAL * 1000 / portTICK_PERIOD_MS); vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
} }
} }

View File

@ -1,9 +0,0 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
get_filename_component(TTN_DIR ../.. ABSOLUTE)
set(EXTRA_COMPONENT_DIRS "${TTN_DIR}")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(send_recv)

View File

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

View File

@ -1,4 +0,0 @@
set(COMPONENT_SRCS "main.cpp")
set(COMPONENT_ADD_INCLUDEDIRS "")
register_component()

File diff suppressed because it is too large Load Diff

822
include/ttn.h Normal file
View File

@ -0,0 +1,822 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* High-level C API for ttn-esp32.
*******************************************************************************/
/**
* @defgroup c_api C API
*/
#ifndef TTN_C_H
#define TTN_C_H
#include "driver/spi_master.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @addtogroup c_api
*
* @{
*/
/**
* @brief Constant for indicating that a pin is not connected
*/
#define TTN_NOT_CONNECTED 0xff
/**
* @brief Integer data type for specifiying the port of an uplink or downlink message.
*/
typedef uint8_t ttn_port_t;
/**
* @brief Response codes
*/
typedef enum
{
/** @brief Transmission failed error */
TTN_ERROR_TRANSMISSION_FAILED = -1,
/** @brief Unexpected or internal error */
TTN_ERROR_UNEXPECTED = -10,
/** @brief Successful transmission of an uplink message */
TTN_SUCCESSFUL_TRANSMISSION = 1,
/** @brief Successful receipt of a downlink message */
TTN_SUCCESSFUL_RECEIVE = 2
} ttn_response_code_t;
/**
* @brief RX/TX window
*/
typedef enum
{
/**
* @brief Outside RX/TX window
*/
TTN_WINDOW_IDLE = 0,
/**
* @brief Transmission window (up to RX1 window)
*/
TTN_WINDOW_TX = 1,
/**
* @brief Reception window 1 (up to RX2 window)
*/
TTN_WINDOW_RX1 = 2,
/**
* @brief Reception window 2
*/
TTN_WINDOW_RX2 = 3
} ttn_rx_tx_window_t;
/**
* @brief Spreading Factor
*/
typedef enum
{
/**
* @brief Unused / undefined spreading factor
*/
TTN_SF_NONE = 0,
/**
* @brief Frequency Shift Keying (FSK)
*/
TTN_FSK = 1,
/**
* @brief Spreading Factor 7 (SF7)
*/
TTN_SF7 = 2,
/**
* @brief Spreading Factor 8 (SF8)
*/
TTN_SF8 = 3,
/**
* @brief Spreading Factor 9 (SF9)
*/
TTN_SF9 = 4,
/**
* @brief Spreading Factor 10 (SF10)
*/
TTN_SF10 = 5,
/**
* @brief Spreading Factor 11 (SF11)
*/
TTN_SF11 = 6,
/**
* @brief Spreading Factor 12 (SF12)
*/
TTN_SF12 = 7
} ttn_spreading_factor_t;
/**
* @brief Bandwidth
*/
typedef enum
{
/**
* @brief Undefined/unused bandwidth
*/
TTN_BW_NONE = 0,
/**
* @brief Bandwidth of 125 kHz
*/
TTN_BW_125 = 1,
/**
* @brief Bandwidth of 250 kHz
*/
TTN_BW_250 = 2,
/**
* @brief Bandwidth of 500 kHz
*/
TTN_BW_500 = 3
} ttn_bandwidth_t;
/**
* @brief Data Rate
*
* Note that the spreading factor, bandwidth, bit rate and maximum message
* size associated with each data rate depends on the region.
*/
typedef enum
{
/**
* @brief Data rate for region AS923 using SF12 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF12 = 0,
/**
* @brief Data rate for region AS923 using SF11 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF11 = 1,
/**
* @brief Data rate for region AS923 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF10 = 2,
/**
* @brief Data rate for region AS923 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF9 = 3,
/**
* @brief Data rate for region AS923 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF8 = 4,
/**
* @brief Data rate for region AS923 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_AS923_SF7_BW125 = 5,
/**
* @brief Data rate for region AS923 using SF7 and 250 kHz bandwidth.
*/
TTN_DR_AS923_SF7_BW250 = 6,
/**
* @brief Data rate for region AS923 using FSK and 50 kpbs.
*/
TTN_DR_AS923_FSK = 7,
/**
* @brief Data rate for region AU915 using SF12 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF12 = 0,
/**
* @brief Data rate for region AU915 using SF11 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF11 = 1,
/**
* @brief Data rate for region AU915 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF10 = 2,
/**
* @brief Data rate for region AU915 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF9 = 3,
/**
* @brief Data rate for region AU915 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF8 = 4,
/**
* @brief Data rate for region AU915 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_AU915_SF7 = 5,
/**
* @brief Data rate for region AU915 using SF8 and 500 kHz bandwidth.
*/
TTN_DR_AU915_SF8_BW500 = 6,
/**
* @brief Data rate for region AU915 using SF12 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF12_BW500 = 8,
/**
* @brief Data rate for region AU915 using SF11 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF11_BW500 = 9,
/**
* @brief Data rate for region AU915 using SF10 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF10_BW500 = 10,
/**
* @brief Data rate for region AU915 using SF9 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF9_BW500 = 11,
/**
* @brief Data rate for region AU915 using SF8 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF8_BW500_DR12 = 12,
/**
* @brief Data rate for region AU915 using SF7 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_AU915_SF7_BW500 = 13,
/**
* @brief Data rate for region EU868 using SF12 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF12 = 0,
/**
* @brief Data rate for region EU868 using SF11 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF11 = 1,
/**
* @brief Data rate for region EU868 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF10 = 2,
/**
* @brief Data rate for region EU868 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF9 = 3,
/**
* @brief Data rate for region EU868 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF8 = 4,
/**
* @brief Data rate for region EU868 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_EU868_SF7_BW125 = 5,
/**
* @brief Data rate for region EU868 using SF7 and 250 kHz bandwidth.
*/
TTN_DR_EU868_SF7_BW250 = 6,
/**
* @brief Data rate for region EU868 using FSK and 50 kpbs.
*/
TTN_DR_EU868_FSK = 7,
/**
* @brief Data rate for region IN866 using SF12 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF12 = 0,
/**
* @brief Data rate for region IN866 using SF11 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF11 = 1,
/**
* @brief Data rate for region IN866 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF10 = 2,
/**
* @brief Data rate for region IN866 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF9 = 3,
/**
* @brief Data rate for region IN866 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF8 = 4,
/**
* @brief Data rate for region IN866 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_IN866_SF7 = 5,
/**
* @brief Data rate for region IN866 using FSK and 50 kpbs.
*/
TTN_DR_IN866_FSK = 7,
/**
* @brief Data rate for region KR920 using SF12 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF12 = 0,
/**
* @brief Data rate for region KR920 using SF11 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF11 = 1,
/**
* @brief Data rate for region KR920 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF10 = 2,
/**
* @brief Data rate for region KR920 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF9 = 3,
/**
* @brief Data rate for region KR920 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF8 = 4,
/**
* @brief Data rate for region KR920 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_KR920_SF7 = 5,
/**
* @brief Data rate for region US915 using SF10 and 125 kHz bandwidth.
*/
TTN_DR_US915_SF10 = 0,
/**
* @brief Data rate for region US915 using SF9 and 125 kHz bandwidth.
*/
TTN_DR_US915_SF9 = 1,
/**
* @brief Data rate for region US915 using SF8 and 125 kHz bandwidth.
*/
TTN_DR_US915_SF8 = 2,
/**
* @brief Data rate for region US915 using SF7 and 125 kHz bandwidth.
*/
TTN_DR_US915_SF7 = 3,
/**
* @brief Data rate for region US915 using SF8 and 500 kHz bandwidth.
*/
TTN_DR_US915_SF8_BW500 = 4,
/**
* @brief Data rate for region US915 using SF12 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF12_BW500 = 8,
/**
* @brief Data rate for region US915 using SF11 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF11_BW500 = 9,
/**
* @brief Data rate for region US915 using SF10 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF10_BW500 = 10,
/**
* @brief Data rate for region US915 using SF9 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF9_BW500 = 11,
/**
* @brief Data rate for region US915 using SF8 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF8_BW500_DR12 = 12,
/**
* @brief Data rate for region US915 using SF7 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
TTN_DR_US915_SF7_BW500 = 13,
/**
* @brief Default data rate for joining.
*/
TTN_DR_JOIN_DEFAULT = 255
} ttn_data_rate_t;
/**
* @brief RF settings for TX or RX
*/
typedef struct
{
/**
* @brief Spreading Factor (SF)
*/
ttn_spreading_factor_t spreading_factor;
/**
* @brief Bandwidth (BW)
*/
ttn_bandwidth_t bandwidth;
/**
* @brief Frequency, in Hz
*/
uint32_t frequency;
} ttn_rf_settings_t;
/**
* @brief Callback for recieved messages
*
* @param payload pointer to the received bytes
* @param length number of received bytes
* @param port port the message was received on
*/
typedef void (*ttn_message_cb)(const uint8_t *payload, size_t length, ttn_port_t port);
/**
* @brief Initializes The Things Network device instance.
*
* Call this function once at the start of the program and before all other TTN functions.
*/
void ttn_init(void);
/**
* @brief Configures the pins used to communicate with the LoRaWAN radio chip.
*
* Before calling this member function, the SPI bus needs to be configured using `spi_bus_initialize()`.
* Additionally, `gpio_install_isr_service()` must have been called to initialize the GPIO ISR handler service.
*
* Call this function after @ref ttn_init() and before all other TTN functions.
*
* @param spi_host The SPI bus/peripherial to use (`SPI1_HOST`, `SPI2_HOST`, `SPI3_HOST`, `FSPI_HOST`, `HSPI_HOST`, or `VSPI_HOST`).
* @param nss The GPIO pin number connected to the radio chip's NSS pin (serving as the SPI chip select)
* @param rxtx The GPIO pin number connected to the radio chip's RXTX pin (@ref TTN_NOT_CONNECTED if not
* connected)
* @param rst The GPIO pin number connected to the radio chip's RST pin (@ref TTN_NOT_CONNECTED if not
* connected)
* @param dio0 The GPIO pin number connected to the radio chip's DIO0 pin
* @param dio1 The GPIO pin number connected to the radio chip's DIO1 pin
*/
void ttn_configure_pins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0,
uint8_t dio1);
/**
* @brief Sets the frequency sub-band to be used.
*
* For regions with sub-bands (USA, Australia), sets the sub-band to be used for uplink communication.
* For other regions, this function has no effect.
*
* The sub-band must be set before joining or sending the first message.
*
* If not set, it defaults to sub-band 2 as defined by TTN.
*
* @param band band (0 for all bands, or value between 1 and 8)
*/
void ttn_set_subband(int band);
/**
* @brief Sets the keys needed to activate the device via OTAA, without activating it.
*
* The provided DevEUI, AppEUI/JoinEUI and AppKey are saved in non-volatile memory. Before
* this function is called, `nvs_flash_init()` must have been called once.
*
* In order to reduce flash wear, this function detects if the keys have not changed
* and will not write them again.
*
* Call @ref ttn_join() to activate the device.
*
* @param dev_eui DevEUI (16 character string with hexadecimal data)
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param app_key AppKey of the device (32 character string with hexadecimal data)
* @return `true` if the provisioning was successful, `false` if the provisioning failed
*/
bool ttn_provision(const char *dev_eui, const char *app_eui, const char *app_key);
/**
* @brief Sets the keys needed to activate the device via OTAA, without activating it.
*
* The provided DevEUI, AppEUI/JoinEUI and AppKey are only stored in RAM and will be lost
* when the device is powered off or put in deep sleep.
*
* Call @ref ttn_join() to activate the device.
*
* @param dev_eui DevEUI (16 character string with hexadecimal data)
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param app_key AppKey of the device (32 character string with hexadecimal data)
* @return `true` if the provisioning was successful, `false` if the provisioning failed
*/
bool ttn_provision_transiently(const char *dev_eui, const char *app_eui, const char *app_key);
/**
* @brief Sets the information needed to activate the device via OTAA, using the MAC to generate the DevEUI
* and without activating it.
*
* The generated DevEUI and the provided AppEUI/JoinEUI and AppKey are saved in non-volatile memory. Before
* this function is called, `nvs_flash_init` must have been called once.
*
* In order to reduce flash wear, this function detects if the keys have not changed
* and will not write them again.
*
* The DevEUI is generated by retrieving the ESP32's WiFi MAC address and expanding it into a DevEUI
* by adding FFFE in the middle. So the MAC address A0:B1:C2:01:02:03 becomes the EUI A0B1C2FFFE010203.
* This hexadecimal data can be entered into the DevEUI field in the TTN console.
*
* Generating the DevEUI from the MAC address allows to flash the same AppEUI/JoinEUI and AppKey to a batch of
* devices. However, using the same AppKey for multiple devices is insecure. Only use this approach if
* it is okay for that the LoRa communication of your application can easily be intercepted and that
* forged data can be injected.
*
* Call @ref ttn_join() to activate.
*
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param app_key AppKey of the device (32 character string with hexadecimal data)
* @return `true` if the provisioning was successful, `false` if the provisioning failed
*/
bool ttn_provision_with_mac(const char *app_eui, const char *app_key);
/**
* @brief Starts task listening on configured UART for AT commands.
*
* Run `make menuconfig` to configure it.
*/
void ttn_start_provisioning_task(void);
/**
* @brief Waits until the DevEUI, AppEUI/JoinEUI and AppKey have been provisioned
* by the provisioning task.
*
* If the device has already been provisioned (stored data in NVS, call of provision()
* or call of @ref ttn_join_with_keys(), this function immediately returns.
*/
void ttn_wait_for_provisioning(void);
/**
* @brief Activates the device via OTAA using previously provisioned keys.
*
* The DevEUI, AppEUI/JoinEUI and AppKey must have already been provisioned by a call
* to @ref ttn_provision() or @ref ttn_provision_with_mac().
* Before this function is called, `nvs_flash_init()` must have been called once.
*
* The RF module is initialized and the TTN background task is started.
*
* The function blocks until the activation has completed or failed.
*
* @return `true` if the activation was succesful, `false` if the activation failed
*/
bool ttn_join(void);
/**
* @brief Activates the device via OTAA using the provided keys.
*
* For the activation, the provided DevEUI, AppEUI/JoinEUI and AppKey are used.
* They are NOT saved in non-volatile memory.
*
* The RF module is initialized and the TTN background task is started.
*
* The function blocks until the activation has completed or failed.
*
* @param dev_eui DevEUI (16 character string with hexadecimal data)
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param app_key AppKey of the device (32 character string with hexadecimal data)
* @return `true` if the activation was succesful, `false` if the activation failed
*/
bool ttn_join_with_keys(const char *dev_eui, const char *app_eui, const char *app_key);
/**
* @brief Resumes TTN communication after deep sleep.
*
* The communcation state is restored from data previously saved in RTC memory.
* The RF module and the TTN background task are started.
*
* This function is called instead of @ref ttn_join_with_keys() or @ref ttn_join()
* to continue with the established communication and to avoid a further join procedure.
*
* @return `true` if the device was able to resume, `false` otherwise.
*/
bool ttn_resume_after_deep_sleep(void);
/**
* @brief Resumes TTN communication after power off.
*
* The communcation state is restored from data previously saved in NVS (non-volatile storage).
* The RF module and the TTN background task are started.
*
* This function is called instead of @ref ttn_join_with_keys() or @ref ttn_join()
* to continue with the established communication and to avoid a further join procedure.
*
* In order to advance the clock, the estimated duration the device was powered off has to
* be specified. As the exact duration is probably not known, an estimation of the shortest
* duration between power-off and next power-on can be used instead.
*
* If the device has access to the real time, set the system time (using `settimeofday()`)
* before calling this function (and before @ref ttn_join()) and pass 0 for `off_duration`.
*
* Before this function is called, `nvs_flash_init()` must have been called once.
*
* @param off_duration duration the device was powered off (in minutes)
* @return `true` if the device was able to resume, `false` otherwise.
*/
bool ttn_resume_after_power_off(int off_duration);
/**
* @brief Stops all activies and prepares for deep sleep.
*
* This function is called before entering deep sleep. It saves the current
* communication state in RTC memory and shuts down the RF module and the
* TTN background task.
*
* It neither clears the provisioned keys nor the configured pins
* but they will be lost if the device goes into deep sleep.
*
* Before calling this function, use @ref ttn_busy_duration() to check
* that the TTN device is idle and ready to go to deep sleep.
*
* To restart communication, @ref ttn_resume_after_deep_sleep() must be called.
*/
void ttn_prepare_for_deep_sleep(void);
/**
* @brief Stops all activies and prepares for power off.
*
* This function is called before powering off the device. It saves the current
* communication state in NVS (non-volatile storage) and shuts down the RF module
* and the TTN background task.
*
* It neither clears the provisioned keys nor the configured pins
* but they will be lost if the device is powered off.
*
* Before calling this function, use @ref ttn_busy_duration() to check
* that the TTN device is idle and ready to be powered off.
*
* To restart communication, @ref ttn_resume_after_power_off(int) must be called.
*
* Before this function is called, `nvs_flash_init()` must have been called once.
*/
void ttn_prepare_for_power_off(void);
/**
* @brief Waits until the TTN device is idle.
*
* If the TTN device is idle, the ESP32 can go into deep sleep mode
* or be powered off without disrupting an on-going communication.
*/
void ttn_wait_for_idle(void);
/**
* @brief Returns the minimum duration the TTN device will be busy.
*
* This function can be called to check whether the TTN device is
* still involved in communication or ready to go to deep sleep or
* to be powered off.
*
* If it returns 0, the TTN communication is idle and the device can go
* to deep sleep or can be powered off.
*
* If it returns a value different from 0, the value indicates the duration
* the device will be certainly busy. After that time, this function must be
* called again. It might still return a value different from 0.
*
* @return busy duration (in FreeRTOS ticks)
*/
TickType_t ttn_busy_duration(void);
/**
* @brief Stops all activies.
*
* This function shuts down the RF module and the TTN background task. It neither clears the
* provisioned keys nor the configured pins. The currentat device state (and activation)
* are lost.
*
* To restart communication, @ref ttn_join_with_keys() and @ref ttn_join() must be called.
*/
void ttn_shutdown(void);
/**
* @brief Transmits a message
*
* The function blocks until the message could be transmitted and a message has been received
* in the subsequent receive window (or the window expires). Additionally, the function will
* first wait until the duty cycle allows a transmission (enforcing the duty cycle limits).
*
* @param payload bytes to be transmitted
* @param length number of bytes to be transmitted
* @param port port (use 1 as default)
* @param confirm flag indicating if a confirmation should be requested (use `false` as default)
* @return @ref TTN_SUCCESSFUL_TRANSMISSION for successful transmission, @ref TTN_ERROR_TRANSMISSION_FAILED for
* failed transmission, @ref TTN_ERROR_UNEXPECTED for unexpected error
*/
ttn_response_code_t ttn_transmit_message(const uint8_t *payload, size_t length, ttn_port_t port, bool confirm);
/**
* @brief Sets the function to be called when a message is received
*
* When a message is received, the specified function is called. The
* message, its length and the port number are provided as
* parameters. The values are only valid during the duration of the
* callback. So they must be immediately processed or copied.
*
* Messages are received as a result of @ref ttn_transmit_message(). The callback is called
* in the task that called this function and it occurs before this function
* returns control to the caller.
*
* @param callback the callback function
*/
void ttn_on_message(ttn_message_cb callback);
/**
* @brief Checks if DevEUI, AppEUI/JoinEUI and AppKey have been stored in non-volatile storage
* or have been provided by a call to @ref ttn_join_with_keys() or to @ref ttn_provision_transiently().
*
* @return `true` if they are stored, complete and of the correct size, `false` otherwise
*/
bool ttn_is_provisioned(void);
/**
* @brief Sets the RSSI calibration value for LBT (Listen Before Talk).
*
* This value is added to RSSI measured prior to decision. It must include the guardband.
* Ignored in US, EU, IN and other countries where LBT is not required.
* Defaults to 10 dB.
*
* @param rssi_cal RSSI calibration value, in dB
*/
void ttn_set_rssi_cal(int8_t rssi_cal);
/**
* Returns whether Adaptive Data Rate (ADR) is enabled.
*
* @return `true` if enabled, `false` if disabled
*/
bool ttn_adr_enabled(void);
/**
* @brief Enables or disabled Adaptive Data Rate (ADR).
*
* ADR is enabled by default. It optimizes data rate, airtime and energy consumption
* for devices with stable RF conditions. It should be turned off for mobile devices.
*
* @param enabled `true` to enable, `false` to disable
*/
void ttn_set_adr_enabled(bool enabled);
/**
* @brief Sets the transmission data rate (i.e. the data rate for uplink messages).
*
* If ADR is enabled, it's is used as the initial data rate and later adjusted depending
* on the RF conditions. If ADR is disabled, it is used for all uplink messages.
*
* @param data_rate data rate (use constants of enum @ref ttn_data_rate_t)
*/
void ttn_set_data_rate(ttn_data_rate_t data_rate);
/**
* @brief Sets the maximum power for transmission
*
* The power is specified in dBm and sets the power emitted by the radio.
* If the antenna has a gain, it must be substracted from the specified value to
* achieve the correct transmission power.
*
* @param tx_pow power, in dBm
*/
void ttn_set_max_tx_pow(int tx_pow);
/**
* @brief Gets current RX/TX window
* @return window
*/
ttn_rx_tx_window_t ttn_rx_tx_window(void);
/**
* @brief Gets the RF settings for the specified window
* @param window RX/TX window (valid values are @ref TTN_WINDOW_TX, @ref TTN_WINDOW_RX1 and @ref TTN_WINDOW_RX2
*/
ttn_rf_settings_t ttn_get_rf_settings(ttn_rx_tx_window_t window);
/**
* @brief Gets the RF settings of the last (or ongoing) transmission.
* @return RF settings
*/
ttn_rf_settings_t ttn_tx_settings(void);
/**
* @brief Gets the RF settings of the last (or ongoing) reception of RX window 1.
* @return RF settings
*/
ttn_rf_settings_t ttn_rx1_settings(void);
/**
* @brief Gets the RF settings of the last (or ongoing) reception of RX window 2.
* @return RF settings
*/
ttn_rf_settings_t ttn_rx2_settings(void);
/**
* @brief Gets the received signal strength indicator (RSSI).
*
* RSSI is the measured signal strength of the last recevied message (incl. join responses).
*
* @return RSSI, in dBm
*/
int ttn_rssi();
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif

View File

@ -11,15 +11,14 @@
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/manuelbl/ttn-esp32.git", "url": "https://github.com/manuelbl/ttn-esp32.git",
"branch": "dev" "branch": "master"
}, },
"version": "3.0.99", "version": "4.2.0-1",
"license": "MIT License", "license": "MIT License",
"export": { "export": {
"include": [ "include": [
"include", "include",
"src", "src"
"esp_idf_lmic_config.h"
] ]
}, },
"frameworks": "espidf", "frameworks": "espidf",

View File

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

View File

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

View File

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

View File

@ -2,322 +2,22 @@
* *
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x * 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 * Licensed under MIT License
* https://opensource.org/licenses/MIT * 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 "TheThingsNetwork.h"
#include "TTNProvisioning.h"
#include "TTNLogging.h"
TTNRFSettings TheThingsNetwork::getRFSettings(TTNRxTxWindow window)
/**
* @brief Reason the user code is waiting
*/
enum TTNWaitingReason
{ {
eWaitingNone, ttn_rf_settings_t settings = ttn_get_rf_settings(static_cast<ttn_rx_tx_window_t>(window));
eWaitingForJoin, TTNRFSettings result;
eWaitingForTransmission result.spreadingFactor = static_cast<TTNSpreadingFactor>(settings.spreading_factor);
}; result.bandwidth = static_cast<TTNBandwidth>(settings.bandwidth);
result.frequency = settings.frequency;
/** return result;
* @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 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);
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();
waitingReason = eWaitingNone;
if (lmicEventQueue != nullptr)
{
xQueueReset(lmicEventQueue);
}
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(1000 / portTICK_PERIOD_MS);
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();
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;
}
// --- 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)
{
#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));
} }

View File

@ -34,9 +34,9 @@
// This should be defined elsewhere // This should be defined elsewhere
void lmic_aes_encrypt(u1_t *data, u1_t *key); 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 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 // Shift the given buffer left one bit
static void shift_left(xref2u1_t buf, u1_t len) { static void shift_left(xref2u1_t buf, u1_t len) {

View File

@ -17,7 +17,11 @@
#elif defined(CONFIG_TTN_LORA_FREQ_US_915) #elif defined(CONFIG_TTN_LORA_FREQ_US_915)
#define CFG_us915 1 #define CFG_us915 1
#elif defined(CONFIG_TTN_LORA_FREQ_AU_921) #elif defined(CONFIG_TTN_LORA_FREQ_AU_921)
#define CFG_au921 1 #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
#elif defined(CONFIG_TTN_LORA_FREQ_AS_923) #elif defined(CONFIG_TTN_LORA_FREQ_AS_923)
#define CFG_as923 1 #define CFG_as923 1
#elif defined(CONFIG_TTN_LORA_FREQ_AS_923_JP) #elif defined(CONFIG_TTN_LORA_FREQ_AS_923_JP)
@ -47,7 +51,6 @@
#endif #endif
#endif #endif
// 16 μs per tick // 16 μs per tick
// LMIC requires ticks to be 15.5μs - 100 μs long // LMIC requires ticks to be 15.5μs - 100 μs long
#define US_PER_OSTICK 16 #define US_PER_OSTICK 16

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

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

View File

@ -1,490 +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
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
{ // keep pin floating
gpio_set_level(ttn_hal.pinRst, val);
gpio_set_direction(ttn_hal.pinRst, GPIO_MODE_INPUT);
}
}
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_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.
void hal_waitUntil(u4_t time)
{
ttn_hal.waitUntil(time);
}
void 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);
}
// 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
}
// -----------------------------------------------------------------------------
// 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) {
os_runloop();
}
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() {
xTaskCreate(lmicBackgroundTask, "ttn_lmic", 1024 * 4, nullptr, CONFIG_TTN_BG_TASK_PRIO, &lmicTask);
// enable interrupts
gpio_isr_handler_add(pinDIO0, dioIrqHandler, (void *)0);
gpio_isr_handler_add(pinDIO1, dioIrqHandler, (void *)1);
}
// -----------------------------------------------------------------------------
// Fatal failure
static hal_failure_handler_t* custom_hal_failure_handler = nullptr;
void hal_set_failure_handler(const hal_failure_handler_t* const handler)
{
custom_hal_failure_handler = handler;
}
void hal_failed(const char *file, u2_t line)
{
if (custom_hal_failure_handler != nullptr)
(*custom_hal_failure_handler)(file, line);
ESP_LOGE(TAG, "LMIC failed and stopped: %s:%d", file, line);
// go to sleep forever
while (true)
{
vTaskDelay(portMAX_DELAY);
}
}

View File

@ -2,7 +2,7 @@
* *
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x * 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 * Licensed under MIT License
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
@ -10,83 +10,55 @@
* Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF. * Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF.
*******************************************************************************/ *******************************************************************************/
#ifndef _hal_esp32_h_ #ifndef HAL_ESP32_H
#define _hal_esp32_h_ #define HAL_ESP32_H
#include <stdint.h> #include "freertos/FreeRTOS.h"
#include <freertos/FreeRTOS.h> #include "driver/spi_master.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>
enum WaitKind { #ifdef __cplusplus
CHECK_IO, extern "C" {
WAIT_FOR_ANY_EVENT, #endif
WAIT_FOR_TIMER
};
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 void hal_esp32_wake_up(void);
{ void hal_esp32_init_critical_section(void);
public: void hal_esp32_enter_critical_section(void);
HAL_ESP32(); 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 hal_esp32_set_rssi_cal(int8_t rssi_cal);
void init();
void startLMICTask();
void wakeUp(); TickType_t hal_esp32_get_timer_duration(void);
void initCriticalSection();
void enterCriticalSection();
void leaveCriticalSection();
void spiWrite(uint8_t cmd, const uint8_t *buf, size_t len); /**
void spiRead(uint8_t cmd, uint8_t *buf, size_t len); * Gets the time.
uint8_t checkTimer(uint32_t osTime); *
void sleep(); * 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);
void waitUntil(uint32_t osTime); /**
* Sets the time.
spi_host_device_t spiHost; *
gpio_num_t pinNSS; * The time is relative to boot time of the
gpio_num_t pinRxTx; * run when the device joined the TTN network.
gpio_num_t pinRst; *
gpio_num_t pinDIO0; * @param time_val time (in seconds)
gpio_num_t pinDIO1; */
int8_t rssiCal; void hal_esp32_set_time(uint32_t time_val);
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;
};
extern HAL_ESP32 ttn_hal;
#endif // _hal_esp32_h_ #ifdef __cplusplus
}
#endif
#endif // HAL_ESP32_H

View File

@ -169,7 +169,7 @@
// enable support for MCMD_DeviceTimeReq and MCMD_DeviceTimeAns // enable support for MCMD_DeviceTimeReq and MCMD_DeviceTimeAns
// this is always defined, and non-zero to enable it. // this is always defined, and non-zero to enable it.
#if !defined(LMIC_ENABLE_DeviceTimeReq) #if !defined(LMIC_ENABLE_DeviceTimeReq)
# define LMIC_ENABLE_DeviceTimeReq 0 # define LMIC_ENABLE_DeviceTimeReq 1
#endif #endif
// LMIC_ENABLE_user_events // LMIC_ENABLE_user_events
@ -187,9 +187,17 @@
#endif #endif
// LMIC_ENABLE_long_messages // LMIC_ENABLE_long_messages
// LMIC certification requires that this be enabled. // LMIC certification requires full-length 255 frames, but to save RAM,
#if !defined(LMIC_ENABLE_long_messages) // a shorter maximum can be set. This controls both RX and TX buffers,
# define LMIC_ENABLE_long_messages 1 /* PARAM */ // 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 #endif
// LMIC_ENABLE_event_logging // LMIC_ENABLE_event_logging
@ -204,4 +212,12 @@
# define LMIC_LORAWAN_SPEC_VERSION LMIC_LORAWAN_SPEC_VERSION_1_0_3 # define LMIC_LORAWAN_SPEC_VERSION LMIC_LORAWAN_SPEC_VERSION_1_0_3
#endif #endif
// LMIC_ENABLE_arbitrary_clock_error
// We normally don't want to allow users to set wide clock errors, because
// we assume reasonably-disciplined os_getTime() values. But... there might
// be platforms that require wider errors.
#if !defined(LMIC_ENABLE_arbitrary_clock_error)
# define LMIC_ENABLE_arbitrary_clock_error 0 /* PARAM */
#endif
#endif // _lmic_config_h_ #endif // _lmic_config_h_

View File

@ -110,9 +110,10 @@ void hal_sleep (void);
u4_t hal_ticks (void); u4_t hal_ticks (void);
/* /*
* busy-wait until specified timestamp (in ticks) is reached. * busy-wait until specified timestamp (in ticks) is reached. If on-time, return 0,
* otherwise return the number of ticks we were late.
*/ */
void hal_waitUntil (u4_t time); u4_t hal_waitUntil (u4_t time);
/* /*
* check and rewind timer for target time. * check and rewind timer for target time.
@ -132,7 +133,8 @@ void hal_failed (const char *file, u2_t line);
* set a custom hal failure handler routine. The default behaviour, defined in * set a custom hal failure handler routine. The default behaviour, defined in
* hal_failed(), is to halt by looping infintely. * 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 * get the calibration value for radio_rssi
@ -168,6 +170,17 @@ uint8_t hal_getTxPowerPolicy(
u4_t freq u4_t freq
); );
void hal_pollPendingIRQs_helper();
void hal_processPendingIRQs(void);
/// \brief check for any pending interrupts: stub if interrupts are enabled.
static inline void hal_pollPendingIRQs(void)
{
#if !defined(LMIC_USE_INTERRUPTS)
hal_pollPendingIRQs_helper();
#endif /* !defined(LMIC_USE_INTERRUPTS) */
}
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View File

@ -119,7 +119,7 @@ void os_wmsbf4 (xref2u1_t buf, u4_t v) {
#if !defined(os_getBattLevel) #if !defined(os_getBattLevel)
u1_t os_getBattLevel (void) { u1_t os_getBattLevel (void) {
return MCMD_DEVS_BATT_NOINFO; return LMIC.client.devStatusAns_battery;
} }
#endif #endif
@ -294,6 +294,16 @@ ostime_t calcAirTime (rps_t rps, u1_t plen) {
// 500kHz | 0 1 2 3 4 5 // 500kHz | 0 1 2 3 4 5
// //
static void setRxsyms (ostime_t rxsyms) {
if (rxsyms >= (((ostime_t)1) << 10u)) {
LMIC.rxsyms = (1u << 10u) - 1;
} else if (rxsyms < 0) {
LMIC.rxsyms = 0;
} else {
LMIC.rxsyms = rxsyms;
}
}
#if !defined(DISABLE_BEACONS) #if !defined(DISABLE_BEACONS)
static ostime_t calcRxWindow (u1_t secs, dr_t dr) { static ostime_t calcRxWindow (u1_t secs, dr_t dr) {
ostime_t rxoff, err; ostime_t rxoff, err;
@ -306,9 +316,9 @@ static ostime_t calcRxWindow (u1_t secs, dr_t dr) {
rxoff = (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp; rxoff = (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp;
err = (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp; err = (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp;
} }
u1_t rxsyms = LMICbandplan_MINRX_SYMS_LoRa_ClassB; rxsyms_t rxsyms = LMICbandplan_MINRX_SYMS_LoRa_ClassB;
err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns; err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns;
LMIC.rxsyms = LMICbandplan_MINRX_SYMS_LoRa_ClassB + (err / dr2hsym(dr)); setRxsyms(LMICbandplan_MINRX_SYMS_LoRa_ClassB + (err / dr2hsym(dr)));
return (rxsyms-LMICbandplan_PAMBL_SYMS) * dr2hsym(dr) + rxoff; return (rxsyms-LMICbandplan_PAMBL_SYMS) * dr2hsym(dr) + rxoff;
} }
@ -616,7 +626,9 @@ static void stateJustJoined (void) {
#if !defined(DISABLE_BEACONS) #if !defined(DISABLE_BEACONS)
// Decode beacon - do not overwrite bcninfo unless we have a match! // Decode beacon - do not overwrite bcninfo unless we have a match!
static lmic_beacon_error_t decodeBeacon (void) { static lmic_beacon_error_t decodeBeacon (void) {
ASSERT(LMIC.dataLen == LEN_BCN); // implicit header RX guarantees this if (LMIC.dataLen != LEN_BCN) { // implicit header RX guarantees this
return LMIC_BEACON_ERROR_INVALID;
}
xref2u1_t d = LMIC.frame; xref2u1_t d = LMIC.frame;
if(! LMICbandplan_isValidBeacon1(d)) if(! LMICbandplan_isValidBeacon1(d))
return LMIC_BEACON_ERROR_INVALID; // first (common) part fails CRC check return LMIC_BEACON_ERROR_INVALID; // first (common) part fails CRC check
@ -709,11 +721,17 @@ static CONST_TABLE(u1_t, macCmdSize)[] = {
}; };
static u1_t getMacCmdSize(u1_t macCmd) { static u1_t getMacCmdSize(u1_t macCmd) {
if (macCmd < 2) if (macCmd >= 2) {
return 0; const unsigned macCmdMinus2 = macCmd - 2u;
if (macCmd >= LENOF_TABLE(macCmdSize) - 2) if (macCmdMinus2 < LENOF_TABLE(macCmdSize)) {
return 0; // macCmd in table, fetch it's size.
return TABLE_GET_U1(macCmdSize, macCmd - 2); return TABLE_GET_U1(macCmdSize, macCmdMinus2);
}
}
// macCmd too small or too large: return zero. Zero is
// never a legal command size, so it signals an error
// to the caller.
return 0;
} }
static bit_t static bit_t
@ -746,8 +764,11 @@ applyAdrRequests(
p4 = opts[oidx+4]; // ChMaskCtl, NbTrans p4 = opts[oidx+4]; // ChMaskCtl, NbTrans
u1_t chpage = p4 & MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK; // channel page 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); map_ok = LMICbandplan_mapChannels(chpage, chmap);
LMICOS_logEventUint32("applyAdrRequests: mapChannels", (chpage << 16)|(chmap << 0)); LMICOS_logEventUint32("applyAdrRequests: mapChannels", ((u4_t)chpage << 16)|(chmap << 0));
} }
} }
@ -789,7 +810,7 @@ applyAdrRequests(
changes = 1; changes = 1;
} }
LMICOS_logEventUint32("applyAdrRequests: setDrTxPow", (adrAns << 16)|(dr << 8)|(p1 << 0)); LMICOS_logEventUint32("applyAdrRequests: setDrTxPow", ((u4_t)adrAns << 16)|(dr << 8)|(p1 << 0));
// handle power changes here, too. // handle power changes here, too.
changes |= setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1)); changes |= setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1));
@ -836,7 +857,7 @@ scan_mac_cmds_link_adr(
if( !LMICbandplan_canMapChannels(chpage, chmap) ) { if( !LMICbandplan_canMapChannels(chpage, chmap) ) {
adrAns &= ~MCMD_LinkADRAns_ChannelACK; adrAns &= ~MCMD_LinkADRAns_ChannelACK;
LMICOS_logEventUint32("scan_mac_cmds_link_adr: failed canMapChannels", (chpage << UINT32_C(16))|(chmap << UINT32_C(0))); LMICOS_logEventUint32("scan_mac_cmds_link_adr: failed canMapChannels", ((u4_t)chpage << 16)|((u4_t)chmap << 0));
} }
if( !validDR(dr) ) { if( !validDR(dr) ) {
@ -871,10 +892,14 @@ scan_mac_cmds(
uint8_t cmd; uint8_t cmd;
LMIC.pendMacLen = 0; LMIC.pendMacLen = 0;
if (port == 0) if (port == 0) {
// port zero: mac data is in the normal payload, and there can't be
// piggyback mac data.
LMIC.pendMacPiggyback = 0; LMIC.pendMacPiggyback = 0;
else } else {
// port is either -1 (no port) or non-zero (piggyback): treat as piggyback.
LMIC.pendMacPiggyback = 1; LMIC.pendMacPiggyback = 1;
}
while( oidx < olen ) { while( oidx < olen ) {
bit_t response_fit; bit_t response_fit;
@ -883,8 +908,9 @@ scan_mac_cmds(
cmd = opts[oidx]; cmd = opts[oidx];
/* compute length, and exit for illegal commands */ /* compute length, and exit for illegal commands */
// cmdlen == 0 for error, or > 0 length of command.
int const cmdlen = getMacCmdSize(cmd); int const cmdlen = getMacCmdSize(cmd);
if (cmdlen > olen - oidx) { if (cmdlen <= 0 || cmdlen > olen - oidx) {
// "the first unknown command terminates processing" // "the first unknown command terminates processing"
olen = oidx; olen = oidx;
break; break;
@ -993,7 +1019,7 @@ scan_mac_cmds(
if( ans == (MCMD_NewChannelAns_DataRateACK|MCMD_NewChannelAns_ChannelACK)) { if( ans == (MCMD_NewChannelAns_DataRateACK|MCMD_NewChannelAns_ChannelACK)) {
if ( ! LMIC_setupChannel(chidx, freq, DR_RANGE_MAP(MinDR, MaxDR), -1) ) { if ( ! LMIC_setupChannel(chidx, freq, DR_RANGE_MAP(MinDR, MaxDR), -1) ) {
LMICOS_logEventUint32("NewChannelReq: setupChannel failed", (MaxDR << 24u) | (MinDR << 16u) | (raw_f_not_zero << 8) | (chidx << 0)); LMICOS_logEventUint32("NewChannelReq: setupChannel failed", ((u4_t)MaxDR << 24u) | ((u4_t)MinDR << 16u) | (raw_f_not_zero << 8) | (chidx << 0));
ans &= ~MCMD_NewChannelAns_ChannelACK; ans &= ~MCMD_NewChannelAns_ChannelACK;
} }
} }
@ -1059,13 +1085,12 @@ scan_mac_cmds(
#if defined(ENABLE_MCMD_BeaconTimingAns) && !defined(DISABLE_BEACONS) #if defined(ENABLE_MCMD_BeaconTimingAns) && !defined(DISABLE_BEACONS)
case MCMD_BeaconTimingAns: { case MCMD_BeaconTimingAns: {
// Ignore if tracking already enabled // Ignore if tracking already enabled or bcninfoTries == 0
if( (LMIC.opmode & OP_TRACK) == 0 ) { if( (LMIC.opmode & OP_TRACK) == 0 && LMIC.bcninfoTries != 0) {
LMIC.bcnChnl = opts[oidx+3]; LMIC.bcnChnl = opts[oidx+3];
// Enable tracking - bcninfoTries // Enable tracking - bcninfoTries
LMIC.opmode |= OP_TRACK; LMIC.opmode |= OP_TRACK;
// Cleared later in txComplete handling - triggers EV_BEACON_FOUND // LMIC.bcninfoTries is cleared later in txComplete handling - triggers EV_BEACON_FOUND
ASSERT(LMIC.bcninfoTries!=0);
// Setup RX parameters // Setup RX parameters
LMIC.bcninfo.txtime = (LMIC.rxtime LMIC.bcninfo.txtime = (LMIC.rxtime
+ ms2osticks(os_rlsbf2(&opts[oidx+1]) * MCMD_BeaconTimingAns_TUNIT) + ms2osticks(os_rlsbf2(&opts[oidx+1]) * MCMD_BeaconTimingAns_TUNIT)
@ -1204,7 +1229,7 @@ static bit_t decodeFrame (void) {
goto norx; goto norx;
} }
if( poff > pend ) { if( poff > pend ) {
LMICOS_logEventUint32("decodeFrame: corrupted frame", (dlen << 16) | (fct << 8) | (poff - pend)); LMICOS_logEventUint32("decodeFrame: corrupted frame", ((u4_t)dlen << 16) | (fct << 8) | (poff - pend));
EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME, EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME,
e_.eui = MAIN::CDEV->getEui(), e_.eui = MAIN::CDEV->getEui(),
e_.info = 0x1000000 + (poff-pend) + (fct<<8) + (dlen<<16))); e_.info = 0x1000000 + (poff-pend) + (fct<<8) + (dlen<<16)));
@ -1245,7 +1270,7 @@ static bit_t decodeFrame (void) {
e_.eui = MAIN::CDEV->getEui(), e_.eui = MAIN::CDEV->getEui(),
e_.info = LMIC.seqnoDn, e_.info = LMIC.seqnoDn,
e_.info2 = seqno)); e_.info2 = seqno));
LMICOS_logEventUint32("decodeFrame: rollover discarded", (seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); LMICOS_logEventUint32("decodeFrame: rollover discarded", ((u4_t)seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0));
goto norx; goto norx;
} }
if( seqno != LMIC.seqnoDn-1 || !LMIC.lastDnConf || ftype != HDR_FTYPE_DCDN ) { if( seqno != LMIC.seqnoDn-1 || !LMIC.lastDnConf || ftype != HDR_FTYPE_DCDN ) {
@ -1253,19 +1278,19 @@ static bit_t decodeFrame (void) {
e_.eui = MAIN::CDEV->getEui(), e_.eui = MAIN::CDEV->getEui(),
e_.info = LMIC.seqnoDn, e_.info = LMIC.seqnoDn,
e_.info2 = seqno)); e_.info2 = seqno));
LMICOS_logEventUint32("decodeFrame: Retransmit confimed discarded", (seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); LMICOS_logEventUint32("decodeFrame: Retransmit confimed discarded", ((u4_t)seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0));
goto norx; goto norx;
} }
// Replay of previous sequence number allowed only if // Replay of previous sequence number allowed only if
// previous frame and repeated both requested confirmation // previous frame and repeated both requested confirmation
// but set a flag, so we don't actually process the message. // but set a flag, so we don't actually process the message.
LMICOS_logEventUint32("decodeFrame: Retransmit confimed accepted", (seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); LMICOS_logEventUint32("decodeFrame: Retransmit confimed accepted", ((u4_t)seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0));
replayConf = 1; replayConf = 1;
LMIC.dnConf = FCT_ACK; LMIC.dnConf = FCT_ACK;
} }
else { else {
if( seqnoDiff > LMICbandplan_MAX_FCNT_GAP) { if( seqnoDiff > LMICbandplan_MAX_FCNT_GAP) {
LMICOS_logEventUint32("decodeFrame: gap too big", (seqnoDiff << 16) | (seqno & 0xFFFFu)); LMICOS_logEventUint32("decodeFrame: gap too big", ((u4_t)seqnoDiff << 16) | (seqno & 0xFFFFu));
goto norx; goto norx;
} }
if( seqno > LMIC.seqnoDn ) { if( seqno > LMIC.seqnoDn ) {
@ -1279,7 +1304,7 @@ static bit_t decodeFrame (void) {
// DN frame requested confirmation - provide ACK once with next UP frame // DN frame requested confirmation - provide ACK once with next UP frame
LMIC.dnConf = LMIC.lastDnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0); LMIC.dnConf = LMIC.lastDnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0);
if (LMIC.dnConf) if (LMIC.dnConf)
LMICOS_logEventUint32("decodeFrame: Confirmed downlink", (seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); LMICOS_logEventUint32("decodeFrame: Confirmed downlink", ((u4_t)seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0));
} }
if (port == 0 && olen != 0 && pend > poff) { if (port == 0 && olen != 0 && pend > poff) {
@ -1364,7 +1389,7 @@ static bit_t decodeFrame (void) {
e_.info = Base::lsbf4(&d[pend]), e_.info = Base::lsbf4(&d[pend]),
e_.info2 = seqno)); e_.info2 = seqno));
// discard the data // discard the data
LMICOS_logEventUint32("decodeFrame: discarding replay", (seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0)); LMICOS_logEventUint32("decodeFrame: discarding replay", ((u4_t)seqno << 16) | (LMIC.lastDnConf << 8) | (ftype << 0));
goto norx; goto norx;
} }
@ -1420,59 +1445,89 @@ static void setupRx2 (void) {
radioRx(); radioRx();
} }
ostime_t LMICcore_adjustForDrift (ostime_t delay, ostime_t hsym) { //! \brief Adjust the delay (in ticks) of the target window-open time from nominal.
if (LMIC.client.clockError != 0) { //! \param hsym the duration of one-half symbol in osticks.
// Calculate how much the clock will drift maximally after delay has //! \param rxsyms_in the nominal window length -- minimum length of time to delay.
// passed. This indicates the amount of time we can be early //! \return Effective delay to use (positive for later, negative for earlier).
// _or_ late. //! \post LMIC.rxsyms is set to the number of rxsymbols to be used for preamble timeout.
ostime_t drift = (int64_t)delay * LMIC.client.clockError / MAX_CLOCK_ERROR; //! \bug For FSK, the radio driver ignores LMIC.rxsysms, and uses a fixed value of 4080 bits
//! (81 ms)
// Increase the receive window by twice the maximum drift (to //!
// compensate for a slow or a fast clock). //! \details The calculation of the RX Window opening time has to balance several things.
delay -= drift; //! The system clock might be inaccurate. Generally, the LMIC assumes that the timebase
//! is accurage to 100 ppm, or 0.01%. 0.01% of a 6 second window is 600 microseconds.
// adjust rxsyms (the size of the window in syms) according to our //! For LoRa, the fastest data rates of interest is SF7 (1024 us/symbol); with an 8-byte
// uncertainty. do this in a strange order to avoid a divide if we can. //! preamble, the shortest preamble is 8.092ms long. If using FSK, the symbol rate is
// rely on hsym = Tsym / 2 //! 20 microseconds, but the preamble is 8*5 bits long, so the preamble is 800 microseconds.
if ((255 - LMIC.rxsyms) * hsym < drift) { //! Unless LMIC_ENABLE_arbitrary_clock_error is true, we fold clock errors of > 0.4% back
LMIC.rxsyms = 255; //! to 0.4%.
} else { ostime_t LMICcore_adjustForDrift (ostime_t delay, ostime_t hsym, rxsyms_t rxsyms_in) {
LMIC.rxsyms = (u1_t) (LMIC.rxsyms + drift / hsym);
}
}
return delay;
}
ostime_t LMICcore_RxWindowOffset (ostime_t hsym, u1_t rxsyms_in) {
ostime_t const Tsym = 2 * hsym;
ostime_t rxsyms;
ostime_t rxoffset; ostime_t rxoffset;
rxsyms = ((2 * (int)rxsyms_in - 8) * Tsym + LMICbandplan_RX_ERROR_ABS_osticks * 2 + Tsym - 1) / Tsym; // decide if we want to move left or right of the reference time.
if (rxsyms < rxsyms_in) { rxoffset = -LMICbandplan_RX_EXTRA_MARGIN_osticks;
rxsyms = rxsyms_in;
}
LMIC.rxsyms = (u1_t) rxsyms;
rxoffset = (8 - rxsyms) * hsym - LMICbandplan_RX_EXTRA_MARGIN_osticks; u2_t clockerr = LMIC.client.clockError;
return rxoffset; // Most crystal oscillators are 100 ppm. If things are that tight, there's
// no point in specifying a drift, as 6 seconds at 100ppm is +/- 600 microseconds.
// We position the windows at the front, and there's some extra margin, so...
// don't bother setting values <= 100 ppm.
if (clockerr != 0)
{
// client has set clock error. Limit this to 0.1% unless there's
// 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.
// 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;
}
}
// If the clock is slow, the window needs to open earlier in our time
// in order to open at or before the specified time (in real world),.
// Don't bother to round, as this is very fine-grained.
ostime_t drift = (ostime_t)(((int64_t)delay * clockerr) / MAX_CLOCK_ERROR);
// calculate the additional rxsyms needed to hit the window nominally.
ostime_t const tsym = 2 * hsym;
ostime_t driftwin;
driftwin = 2 * drift;
if (rxoffset < 0)
driftwin += -rxoffset;
// else we'll hit the window nominally.
rxsyms_in += (driftwin + tsym - 1) / tsym;
// reduce the rxoffset by the drift; this compensates for a slow clock;
// but it makes the rxtime too early by approximately `drift` if clock
// is fast.
rxoffset -= drift;
setRxsyms(rxsyms_in);
return delay + rxoffset;
} }
static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) {
ostime_t hsym = dr2hsym(dr); ostime_t hsym = dr2hsym(dr);
// Center the receive window on the center of the expected preamble and timeout. // Schedule the start of the receive window. os_getRadioRxRampup() is used to make sure we
// (again note that hsym is half a sumbol time, so no /2 needed) // exit "sleep" well enough in advance of the receive window to be able to
// we leave RX_RAMPUP unadjusted for the clock drift. The IBM LMIC generates delays // time things accurately.
// that are too long for SF12, and too short for other SFs, so we follow the
// Semtech reference code.
// //
// This also sets LMIC.rxsyms. // This also sets LMIC.rxsyms. This is NOT normally used for FSK; see LMICbandplan_txDoneFSK()
LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay + LMICcore_RxWindowOffset(hsym, LMICbandplan_MINRX_SYMS_LoRa_ClassA), hsym); LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay, hsym, LMICbandplan_MINRX_SYMS_LoRa_ClassA);
LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": sched Rx12 %"LMIC_PRId_ostime_t"\n", os_getTime(), LMIC.rxtime - RX_RAMPUP); LMIC_X_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": sched Rx12 %"LMIC_PRId_ostime_t"\n", os_getTime(), LMIC.rxtime - os_getRadioRxRampup());
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - os_getRadioRxRampup(), func);
} }
static void setupRx1 (osjobcb_t func) { static void setupRx1 (osjobcb_t func) {
@ -1498,9 +1553,8 @@ static void txDone (ostime_t delay, osjobcb_t func) {
// change params and rps (US only) before we increment txChnl // change params and rps (US only) before we increment txChnl
LMICbandplan_setRx1Params(); LMICbandplan_setRx1Params();
// LMIC.rxsyms carries the TX datarate (can be != LMIC.datarate [confirm retries etc.]) // LMIC.dndr carries the TX datarate (can be != LMIC.datarate [confirm retries etc.])
// Setup receive - LMIC.rxtime is preloaded with 1.5 symbols offset to tune // Setup receive -- either schedule FSK or schedule rx1 or rx2 window.
// into the middle of the 8 symbols preamble.
if( LMICbandplan_isFSK() ) { if( LMICbandplan_isFSK() ) {
LMICbandplan_txDoneFSK(delay, func); LMICbandplan_txDoneFSK(delay, func);
} }
@ -1527,12 +1581,22 @@ static bit_t processJoinAccept (void) {
if ((LMIC.txrxFlags & TXRX_DNW1) != 0 && LMIC.dataLen == 0) if ((LMIC.txrxFlags & TXRX_DNW1) != 0 && LMIC.dataLen == 0)
return 0; return 0;
ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); // formerly we asserted.
if ((LMIC.opmode & OP_TXRXPEND) == 0)
// nothing we can do.
return 1;
// formerly we asserted.
if ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) == 0) {
// we shouldn't be here. just drop the frame, but clean up txrxpend.
return processJoinAccept_badframe();
}
if( LMIC.dataLen == 0 ) { if( LMIC.dataLen == 0 ) {
// we didn't get any data and we're in slot 2. So... there's no join frame. // we didn't get any data and we're in slot 2. So... there's no join frame.
return processJoinAccept_nojoinframe(); return processJoinAccept_nojoinframe();
} }
u1_t hdr = LMIC.frame[0]; u1_t hdr = LMIC.frame[0];
u1_t dlen = LMIC.dataLen; u1_t dlen = LMIC.dataLen;
u4_t mic = os_rlsbf4(&LMIC.frame[dlen-4]); // safe before modified by encrypt! u4_t mic = os_rlsbf4(&LMIC.frame[dlen-4]); // safe before modified by encrypt!
@ -1560,21 +1624,9 @@ static bit_t processJoinAccept (void) {
// initDefaultChannels(0) for EU-like, nothing otherwise // initDefaultChannels(0) for EU-like, nothing otherwise
LMICbandplan_joinAcceptChannelClear(); LMICbandplan_joinAcceptChannelClear();
if (!LMICbandplan_hasJoinCFlist() && dlen > LEN_JA) { // process the CFList if present
// if no JoinCFList, we're supposed to continue if (dlen == LEN_JAEXT) {
// the join per 2.2.5 of LoRaWAN regional 2.2.4 LMICbandplan_processJoinAcceptCFList();
// 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
}
}
} }
// already incremented when JOIN REQ got sent off // already incremented when JOIN REQ got sent off
@ -1594,7 +1646,6 @@ static bit_t processJoinAccept (void) {
? EV::joininfo_t::REJOIN_ACCEPT ? EV::joininfo_t::REJOIN_ACCEPT
: EV::joininfo_t::ACCEPT))); : EV::joininfo_t::ACCEPT)));
ASSERT((LMIC.opmode & (OP_JOINING|OP_REJOIN))!=0);
// //
// XXX(tmm@mcci.com) OP_REJOIN confuses me, and I'm not sure why we're // XXX(tmm@mcci.com) OP_REJOIN confuses me, and I'm not sure why we're
// adjusting DRs here. We've just received a join accept, and the // adjusting DRs here. We've just received a join accept, and the
@ -1646,7 +1697,12 @@ static bit_t processJoinAccept_nojoinframe(void) {
// the rejoin-sent count. Internal callers will turn on rejoin // the rejoin-sent count. Internal callers will turn on rejoin
// occasionally. // occasionally.
if( (LMIC.opmode & OP_JOINING) == 0) { if( (LMIC.opmode & OP_JOINING) == 0) {
ASSERT((LMIC.opmode & OP_REJOIN) != 0); // formerly, we asserted ((LMIC.opmode & OP_REJOIN) != 0);
// but now we just return 1 if it's not asserted.
if ( (LMIC.opmode & OP_REJOIN) == 0) {
LMIC.opmode &= ~OP_TXRXPEND;
return 1;
}
LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND); LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND);
if( LMIC.rejoinCnt < 10 ) if( LMIC.rejoinCnt < 10 )
LMIC.rejoinCnt++; LMIC.rejoinCnt++;
@ -1805,7 +1861,8 @@ static bit_t buildDataFrame (void) {
// highest importance are the ones in the pendMac buffer. // highest importance are the ones in the pendMac buffer.
int end = OFF_DAT_OPTS; int end = OFF_DAT_OPTS;
if (LMIC.pendTxPort != 0 && LMIC.pendMacPiggyback && LMIC.pendMacLen != 0) { // Send piggyback data if: !txdata or txport != 0
if ((! txdata || LMIC.pendTxPort != 0) && LMIC.pendMacPiggyback && LMIC.pendMacLen != 0) {
os_copyMem(LMIC.frame + end, LMIC.pendMacData, LMIC.pendMacLen); os_copyMem(LMIC.frame + end, LMIC.pendMacData, LMIC.pendMacLen);
end += LMIC.pendMacLen; end += LMIC.pendMacLen;
} }
@ -1882,7 +1939,7 @@ static bit_t buildDataFrame (void) {
u1_t maxFlen = LMICbandplan_maxFrameLen(LMIC.datarate); u1_t maxFlen = LMICbandplan_maxFrameLen(LMIC.datarate);
if (flen > maxFlen) { if (flen > maxFlen) {
LMICOS_logEventUint32("frame too long for this bandplan", (dlen << 16) | (flen << 8) | maxFlen); LMICOS_logEventUint32("frame too long for this bandplan", ((u4_t)dlen << 16) | (flen << 8) | maxFlen);
return 0; return 0;
} }
@ -1896,7 +1953,7 @@ static bit_t buildDataFrame (void) {
LMIC.seqnoUp += 1; LMIC.seqnoUp += 1;
DO_DEVDB(LMIC.seqnoUp,seqnoUp); DO_DEVDB(LMIC.seqnoUp,seqnoUp);
} else { } else {
LMICOS_logEventUint32("retransmit", (LMIC.frame[OFF_DAT_FCT] << 24u) | (LMIC.txCnt << 16u) | (LMIC.upRepeatCount << 8u) | (LMIC.upRepeat<<0u)); LMICOS_logEventUint32("retransmit", ((u4_t)LMIC.frame[OFF_DAT_FCT] << 24u) | ((u4_t)LMIC.txCnt << 16u) | (LMIC.upRepeatCount << 8u) | (LMIC.upRepeat<<0u));
EV(devCond, INFO, (e_.reason = EV::devCond_t::RE_TX, EV(devCond, INFO, (e_.reason = EV::devCond_t::RE_TX,
e_.eui = MAIN::CDEV->getEui(), e_.eui = MAIN::CDEV->getEui(),
e_.info = LMIC.seqnoUp-1, e_.info = LMIC.seqnoUp-1,
@ -1981,7 +2038,9 @@ static void onBcnRx (xref2osjob_t osjob) {
// Implicitely cancels any pending TX/RX transaction. // Implicitely cancels any pending TX/RX transaction.
// Also cancels an onpoing joining procedure. // Also cancels an onpoing joining procedure.
static void startScan (void) { static void startScan (void) {
ASSERT(LMIC.devaddr!=0 && (LMIC.opmode & OP_JOINING)==0); // formerly, we asserted.
if (LMIC.devaddr == 0 || (LMIC.opmode & OP_JOINING) != 0)
return;
if( (LMIC.opmode & OP_SHUTDOWN) != 0 ) if( (LMIC.opmode & OP_SHUTDOWN) != 0 )
return; return;
// Cancel onging TX/RX transaction // Cancel onging TX/RX transaction
@ -2170,7 +2229,10 @@ static bit_t processDnData_norx(void);
static bit_t processDnData_txcomplete(void); static bit_t processDnData_txcomplete(void);
static bit_t processDnData (void) { static bit_t processDnData (void) {
ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); // if no TXRXPEND, we shouldn't be here and can do nothign.
// formerly we asserted.
if ((LMIC.opmode & OP_TXRXPEND) == 0)
return 1;
if( LMIC.dataLen == 0 ) { if( LMIC.dataLen == 0 ) {
// if this is an RX1 window, shouldn't we return 0 to schedule // if this is an RX1 window, shouldn't we return 0 to schedule
@ -2416,7 +2478,7 @@ static void processBeacon (xref2osjob_t osjob) {
e_.eui = MAIN::CDEV->getEui(), e_.eui = MAIN::CDEV->getEui(),
e_.info = drift, e_.info = drift,
e_.info2 = /*occasion BEACON*/0)); e_.info2 = /*occasion BEACON*/0));
ASSERT((LMIC.bcninfo.flags & (BCN_PARTIAL|BCN_FULL)) != 0); // formerly we'd assert on BCN_PARTIAL|BCN_FULL, but we can't get here if so
} else { } else {
ev = EV_BEACON_MISSED; ev = EV_BEACON_MISSED;
LMIC.bcninfo.txtime += BCN_INTV_osticks - LMIC.drift; LMIC.bcninfo.txtime += BCN_INTV_osticks - LMIC.drift;
@ -2424,9 +2486,9 @@ static void processBeacon (xref2osjob_t osjob) {
LMIC.missedBcns++; LMIC.missedBcns++;
// Delay any possible TX after surmised beacon - it's there although we missed it // Delay any possible TX after surmised beacon - it's there although we missed it
txDelay(LMIC.bcninfo.txtime + BCN_RESERVE_osticks, 4); txDelay(LMIC.bcninfo.txtime + BCN_RESERVE_osticks, 4);
if( LMIC.missedBcns > MAX_MISSED_BCNS ) // if too many missed beacons or we lose sync, drop back to Class A.
LMIC.opmode |= OP_REJOIN; // try if we can roam to another network if( LMIC.missedBcns > MAX_MISSED_BCNS ||
if( LMIC.bcnRxsyms > MAX_RXSYMS ) { LMIC.bcnRxsyms > MAX_RXSYMS ) {
LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN); LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN);
reportEventAndUpdate(EV_LOST_TSYNC); reportEventAndUpdate(EV_LOST_TSYNC);
return; return;
@ -2495,8 +2557,14 @@ static void engineUpdate_inner (void) {
if( (LMIC.opmode & OP_TRACK) != 0 ) { if( (LMIC.opmode & OP_TRACK) != 0 ) {
// We are tracking a beacon // We are tracking a beacon
ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 ); // formerly asserted ( now - (LMIC.bcnRxtime - os_getRadioRxRampup()) <= 0 );
rxtime = LMIC.bcnRxtime - RX_RAMPUP; rxtime = LMIC.bcnRxtime - os_getRadioRxRampup();
if (now - rxtime < 0) {
// too late: drop out of Class B.
LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN);
reportEventNoUpdate(EV_LOST_TSYNC);
return;
}
} }
#endif // !DISABLE_BEACONS #endif // !DISABLE_BEACONS
@ -2609,7 +2677,7 @@ static void engineUpdate_inner (void) {
#if !defined(DISABLE_PING) #if !defined(DISABLE_PING)
if( (LMIC.opmode & OP_PINGINI) != 0 ) { if( (LMIC.opmode & OP_PINGINI) != 0 ) {
// One more RX slot in this beacon period? // One more RX slot in this beacon period?
if( rxschedNext(&LMIC.ping, now+RX_RAMPUP) ) { if( rxschedNext(&LMIC.ping, now+os_getRadioRxRampup()) ) {
if( txbeg != 0 && (txbeg - LMIC.ping.rxtime) < 0 ) if( txbeg != 0 && (txbeg - LMIC.ping.rxtime) < 0 )
goto txdelay; goto txdelay;
LMIC.rxsyms = LMIC.ping.rxsyms; LMIC.rxsyms = LMIC.ping.rxsyms;
@ -2617,8 +2685,14 @@ static void engineUpdate_inner (void) {
LMIC.freq = LMIC.ping.freq; LMIC.freq = LMIC.ping.freq;
LMIC.rps = dndr2rps(LMIC.ping.dr); LMIC.rps = dndr2rps(LMIC.ping.dr);
LMIC.dataLen = 0; LMIC.dataLen = 0;
ASSERT(LMIC.rxtime - now+RX_RAMPUP >= 0 ); ostime_t rxtime_ping = LMIC.rxtime - os_getRadioRxRampup();
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, FUNC_ADDR(startRxPing)); // did we miss the time?
if (now - rxtime_ping > 0) {
LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN);
reportEventNoUpdate(EV_LOST_TSYNC);
} else {
os_setTimedCallback(&LMIC.osjob, rxtime_ping, FUNC_ADDR(startRxPing));
}
return; return;
} }
// no - just wait for the beacon // no - just wait for the beacon
@ -2713,6 +2787,11 @@ void LMIC_reset (void) {
LMIC.adrEnabled = FCT_ADREN; LMIC.adrEnabled = FCT_ADREN;
resetJoinParams(); resetJoinParams();
LMIC.rxDelay = DELAY_DNW1; LMIC.rxDelay = DELAY_DNW1;
// LMIC.pendMacLen = 0;
// LMIC.pendMacPiggyback = 0;
// LMIC.dn2Ans = 0;
// LMIC.macDlChannelAns = 0;
// LMIC.macRxTimingSetupAns = 0;
#if !defined(DISABLE_PING) #if !defined(DISABLE_PING)
LMIC.ping.freq = FREQ_PING; // defaults for ping LMIC.ping.freq = FREQ_PING; // defaults for ping
LMIC.ping.dr = DR_PING; // ditto LMIC.ping.dr = DR_PING; // ditto
@ -2739,6 +2818,7 @@ void LMIC_reset (void) {
void LMIC_init (void) { void LMIC_init (void) {
LMIC.opmode = OP_SHUTDOWN; LMIC.opmode = OP_SHUTDOWN;
LMIC.client.devStatusAns_battery = MCMD_DEVS_BATT_NOINFO;
LMICbandplan_init(); LMICbandplan_init();
} }
@ -2799,7 +2879,11 @@ dr_t LMIC_feasibleDataRateForFrame(dr_t dr, u1_t payloadSize) {
} }
static bit_t isTxPathBusy(void) { 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) { static bit_t adjustDrForFrameIfNotBusy(u1_t len) {
@ -2819,7 +2903,11 @@ void LMIC_setTxData (void) {
} }
void LMIC_setTxData_strict (void) { void LMIC_setTxData_strict (void) {
LMICOS_logEventUint32(__func__, (LMIC.pendTxPort << 24u) | (LMIC.pendTxConf << 16u) | (LMIC.pendTxLen << 0u)); if (isTxPathBusy()) {
return;
}
LMICOS_logEventUint32(__func__, ((u4_t)LMIC.pendTxPort << 24u) | ((u4_t)LMIC.pendTxConf << 16u) | (LMIC.pendTxLen << 0u));
LMIC.opmode |= OP_TXDATA; LMIC.opmode |= OP_TXDATA;
if( (LMIC.opmode & OP_JOINING) == 0 ) { if( (LMIC.opmode & OP_JOINING) == 0 ) {
LMIC.txCnt = 0; // reset the confirmed uplink FSM LMIC.txCnt = 0; // reset the confirmed uplink FSM
@ -2837,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 // 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) { 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 // already have a message queued
return LMIC_ERROR_TX_BUSY; return LMIC_ERROR_TX_BUSY;
} }
@ -2857,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 LMIC_ERROR_TX_FAILED;
} }
} }
return 0; return LMIC_ERROR_SUCCESS;
} }
// send a message with callback; try to adjust data rate // send a message with callback; try to adjust data rate
@ -3013,6 +3101,52 @@ int LMIC_getNetworkTimeReference(lmic_time_reference_t *pReference) {
pReference->tNetwork = LMIC.netDeviceTime; pReference->tNetwork = LMIC.netDeviceTime;
return 1; return 1;
} }
#else
LMIC_API_PARAMETER(pReference);
#endif // LMIC_ENABLE_DeviceTimeReq #endif // LMIC_ENABLE_DeviceTimeReq
return 0; return 0;
} }
///
/// \brief set battery level to be returned by `DevStatusAns`.
///
/// \param uBattLevel is the 8-bit value to be returned. Per LoRaWAN 1.0.3 line 769,
/// this is \c MCMD_DEVS_EXT_POWER (0) if on external power,
/// \c MCMD_DEVS_NOINFO (255) if not able to measure battery level,
/// or a value in [ \c MCMD_DEVS_BATT_MIN, \c MCMD_DEVS_BATT_MAX ], numerically
/// [1, 254], to represent the charge state of the battery. Note that
/// this is not millivolts.
///
/// \returns
/// This function returns the previous value of the battery level.
///
/// \details
/// The LMIC maintains an idea of the current battery state, initially set to
/// \c MCMD_DEVS_NOINFO after the call to LMIC_init(). The appplication then calls
/// this function from time to time to update the battery level.
///
/// It is possible (in non-Arduino environments) to supply a local implementation
/// of os_getBatteryLevel(). In that case, it's up to the implementation to decide
/// whether to use the value supplied by this API.
///
/// This implementation was chosen to minimize the risk of a battery measurement
/// introducting breaking delays into the LMIC.
///
u1_t LMIC_setBatteryLevel(u1_t uBattLevel) {
const u1_t result = LMIC.client.devStatusAns_battery;
LMIC.client.devStatusAns_battery = uBattLevel;
return result;
}
///
/// \brief get battery level that is to be returned by `DevStatusAns`.
///
/// \returns
/// This function returns the saved value of the battery level.
///
/// \see LMIC_setBatteryLevel()
///
u1_t LMIC_getBatteryLevel(void) {
return LMIC.client.devStatusAns_battery;
}

View File

@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2016 Matthijs Kooijman. * Copyright (c) 2016 Matthijs Kooijman.
* Copyright (c) 2016-2019 MCCI Corporation. * Copyright (c) 2016-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -96,39 +96,72 @@
extern "C"{ extern "C"{
#endif #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_MAJOR 1
#define LMIC_VERSION_MINOR 6 #define LMIC_VERSION_MINOR 6
#define LMIC_VERSION_BUILD 1468577746 #define LMIC_VERSION_BUILD 1468577746
// Arduino LMIC version // Arduino LMIC version
#define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \ #define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \
(((major) << 24ul) | ((minor) << 16ul) | ((patch) << 8ul) | ((local) << 0ul)) ((((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, 0, 99, 5) /* v3.0.99.5 */ #define ARDUINO_LMIC_VERSION \
ARDUINO_LMIC_VERSION_CALC(4, 2, 0, 1) /* 4.2.0-1 */
#define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \ #define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \
(((v) >> 24u) & 0xFFu) ((((v)*UINT32_C(1)) >> 24u) & 0xFFu)
#define ARDUINO_LMIC_VERSION_GET_MINOR(v) \ #define ARDUINO_LMIC_VERSION_GET_MINOR(v) \
(((v) >> 16u) & 0xFFu) ((((v)*UINT32_C(1)) >> 16u) & 0xFFu)
#define ARDUINO_LMIC_VERSION_GET_PATCH(v) \ #define ARDUINO_LMIC_VERSION_GET_PATCH(v) \
(((v) >> 8u) & 0xFFu) ((((v)*UINT32_C(1)) >> 8u) & 0xFFu)
#define ARDUINO_LMIC_VERSION_GET_LOCAL(v) \ #define ARDUINO_LMIC_VERSION_GET_LOCAL(v) \
((v) & 0xFFu) ((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 ! //! Only For Antenna Tuning Tests !
//#define CFG_TxContinuousMode 1 //#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. // MAX_LEN_FRAME is what the code uses.
enum { MAX_FRAME_LEN = MAX_LEN_FRAME }; //!< Library cap on max frame length enum { MAX_FRAME_LEN = MAX_LEN_FRAME }; //!< Library cap on max frame length
enum { TXCONF_ATTEMPTS = 8 }; //!< Transmit attempts for confirmed frames enum { TXCONF_ATTEMPTS = 8 }; //!< Transmit attempts for confirmed frames
enum { MAX_MISSED_BCNS = 20 }; // threshold for triggering rejoin requests enum { MAX_MISSED_BCNS = (2 * 60 * 60 + 127) / 128 }; //!< threshold for dropping out of class B, triggering rejoin requests
enum { MAX_RXSYMS = 100 }; // stop tracking beacon beyond this // 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 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 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 enum { LINK_CHECK_CONT = 0 , // continue with this after reported dead link
LINK_CHECK_DEAD = 32 , // after this UP frames and no response to ack from NWK assume link is dead (ADR_ACK_DELAY) LINK_CHECK_DEAD = 32 , // after this UP frames and no response to ack from NWK assume link is dead (ADR_ACK_DELAY)
@ -154,7 +187,7 @@ struct band_t {
u2_t txcap; // duty cycle limitation: 1/txcap u2_t txcap; // duty cycle limitation: 1/txcap
s1_t txpow; // maximum TX power s1_t txpow; // maximum TX power
u1_t lastchnl; // last used channel 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 TYPEDEF_xref2band_t; //!< \internal
@ -165,10 +198,8 @@ struct lmic_saved_adr_state_s {
#elif CFG_LMIC_US_like // US915 spectrum ================================================= #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 { 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 activeChannels125khz;
u2_t activeChannels500khz; u2_t activeChannels500khz;
}; };
@ -185,10 +216,10 @@ enum { KEEP_TXPOW = -128 };
#if !defined(DISABLE_PING) #if !defined(DISABLE_PING)
//! \internal //! \internal
struct rxsched_t { struct rxsched_t {
u1_t dr; dr_t dr;
u1_t intvExp; // 0..7 u1_t intvExp; // 0..7
u1_t slot; // runs from 0 to 128 u1_t slot; // runs from 0 to 128
u1_t rxsyms; rxsyms_t rxsyms;
ostime_t rxbase; ostime_t rxbase;
ostime_t rxtime; // start of next spot ostime_t rxtime; // start of next spot
u4_t freq; u4_t freq;
@ -219,7 +250,7 @@ struct bcninfo_t {
#endif // !DISABLE_BEACONS #endif // !DISABLE_BEACONS
// purpose of receive window - lmic_t.rxState // purpose of receive window - lmic_t.rxState
enum { RADIO_RST=0, RADIO_TX=1, RADIO_RX=2, RADIO_RXON=3 }; enum { RADIO_RST=0, RADIO_TX=1, RADIO_RX=2, RADIO_RXON=3, RADIO_TX_AT=4, };
// Netid values / lmic_t.netid // Netid values / lmic_t.netid
enum { NETID_NONE=(int)~0U, NETID_MASK=(int)0xFFFFFF }; enum { NETID_NONE=(int)~0U, NETID_MASK=(int)0xFFFFFF };
// MAC operation modes (lmic_t.opmode). // MAC operation modes (lmic_t.opmode).
@ -320,9 +351,25 @@ static inline bit_t LMIC_BEACON_SUCCESSFUL(lmic_beacon_error_t e) {
return e < 0; return e < 0;
} }
// LMIC_CFG_max_clock_error_ppm
#if !defined(LMIC_CFG_max_clock_error_ppm)
# define LMIC_CFG_max_clock_error_ppm 2000 /* max clock error: 0.2% (2000 ppm) */
#endif
enum { enum {
// This value represents 100% error in LMIC.clockError // This value represents 100% error in LMIC.clockError
MAX_CLOCK_ERROR = 65536, MAX_CLOCK_ERROR = 65536,
//! \brief maximum clock error that users can specify: 2000 ppm (0.2%).
//! \details This is the limit for clock error, unless LMIC_ENABLE_arbitrary_clock_error is set.
//! The default is 4,000 ppm, which is .004, or 0.4%; this is what you get on an
//! STM32L0 running with the HSI oscillator after cal. If your clock error is bigger,
//! usually you want to calibrate it so that millis() and micros() are reasonably
//! accurate. Important: do not use clock error to compensate for late serving
//! of the LMIC. If you see that LMIC.radio.rxlate_count is increasing, you need
//! to adjust your application logic so the LMIC gets serviced promptly when a
//! Class A downlink (or beacon) is pending.
LMIC_kMaxClockError_ppm = 4000,
}; };
// callbacks for client alerts. // callbacks for client alerts.
@ -412,7 +459,33 @@ struct lmic_client_data_s {
u2_t clockError; //! Inaccuracy in the clock. CLOCK_ERROR_MAX represents +/-100% error u2_t clockError; //! Inaccuracy in the clock. CLOCK_ERROR_MAX represents +/-100% error
/* finally, things that are (u)int8_t */ /* finally, things that are (u)int8_t */
/* none at the moment */ u1_t devStatusAns_battery; //!< value to report in MCMD_DevStatusAns message.
};
/*
Structure: lmic_radio_data_t
Function:
Holds LMIC radio driver.
Description:
Eventually this will be used for all portable things for the radio driver,
but for now it's where we can start to add things.
*/
typedef struct lmic_radio_data_s lmic_radio_data_t;
struct lmic_radio_data_s {
// total os ticks of accumulated delay error. Can overflow!
ostime_t rxlate_ticks;
// number of rx late launches.
unsigned rxlate_count;
// total os ticks of accumulated tx delay error. Can overflow!
ostime_t txlate_ticks;
// number of tx late launches.
unsigned txlate_count;
}; };
/* /*
@ -439,6 +512,9 @@ struct lmic_t {
rxsched_t ping; // pingable setup rxsched_t ping; // pingable setup
#endif #endif
// the radio driver portable context
lmic_radio_data_t radio;
/* (u)int32_t things */ /* (u)int32_t things */
// Radio settings TX/RX (also accessed by HAL) // Radio settings TX/RX (also accessed by HAL)
@ -479,10 +555,10 @@ struct lmic_t {
// bit map of enabled datarates for each channel // bit map of enabled datarates for each channel
u2_t channelDrMap[MAX_CHANNELS]; u2_t channelDrMap[MAX_CHANNELS];
u2_t channelMap; u2_t channelMap;
u2_t channelShuffleMap;
#elif CFG_LMIC_US_like #elif CFG_LMIC_US_like
u4_t xchFreq[MAX_XCHANNELS]; // extra channel frequencies (if device is behind a repeater) u2_t channelMap[(72+15)/16]; // enabled bits
u2_t xchDrMap[MAX_XCHANNELS]; // extra channel datarate ranges ---XXX: ditto u2_t channelShuffleMap[(72+15)/16]; // enabled bits
u2_t channelMap[(72+MAX_XCHANNELS+15)/16]; // enabled bits
u2_t activeChannels125khz; u2_t activeChannels125khz;
u2_t activeChannels500khz; u2_t activeChannels500khz;
#endif #endif
@ -498,13 +574,14 @@ struct lmic_t {
s2_t drift; // last measured drift s2_t drift; // last measured drift
s2_t lastDriftDiff; s2_t lastDriftDiff;
s2_t maxDriftDiff; s2_t maxDriftDiff;
rxsyms_t bcnRxsyms; //
#endif #endif
/* (u)int8_t things */ /* (u)int8_t things */
lmic_engine_update_state_t engineUpdateState; // state of the engineUpdate() evaluator. lmic_engine_update_state_t engineUpdateState; // state of the engineUpdate() evaluator.
s1_t rssi; s1_t rssi;
s1_t snr; // LMIC.snr is SNR times 4 s1_t snr; // LMIC.snr is SNR times 4
u1_t rxsyms; rxsyms_t rxsyms; // symbols for receive timeout.
u1_t dndr; u1_t dndr;
s1_t txpow; // transmit dBm (administrative) s1_t txpow; // transmit dBm (administrative)
s1_t radio_txpow; // the radio driver's copy of txpow, in dB limited by adrTxPow, and s1_t radio_txpow; // the radio driver's copy of txpow, in dB limited by adrTxPow, and
@ -516,7 +593,10 @@ struct lmic_t {
u1_t txChnl; // channel for next TX u1_t txChnl; // channel for next TX
u1_t globalDutyRate; // max rate: 1/2^k 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 u1_t upRepeat; // configured up repeat
s1_t adrTxPow; // ADR adjusted TX power s1_t adrTxPow; // ADR adjusted TX power
u1_t datarate; // current data rate u1_t datarate; // current data rate
@ -586,7 +666,6 @@ struct lmic_t {
#if !defined(DISABLE_BEACONS) #if !defined(DISABLE_BEACONS)
u1_t bcnChnl; u1_t bcnChnl;
u1_t bcnRxsyms; //
#endif #endif
u1_t noRXIQinversion; u1_t noRXIQinversion;
@ -607,6 +686,12 @@ bit_t LMIC_enableChannel(u1_t channel);
bit_t LMIC_disableSubBand(u1_t band); bit_t LMIC_disableSubBand(u1_t band);
bit_t LMIC_selectSubBand(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_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) void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off)
@ -653,6 +738,11 @@ int LMIC_getNetworkTimeReference(lmic_time_reference_t *pReference);
int LMIC_registerRxMessageCb(lmic_rxmessage_cb_t *pRxMessageCb, void *pUserData); int LMIC_registerRxMessageCb(lmic_rxmessage_cb_t *pRxMessageCb, void *pUserData);
int LMIC_registerEventCb(lmic_event_cb_t *pEventCb, 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. // APIs for client half of compliance.
typedef u1_t lmic_compliance_rx_action_t; typedef u1_t lmic_compliance_rx_action_t;
@ -678,4 +768,7 @@ DECL_ON_LMIC_EVENT;
} // extern "C" } // extern "C"
#endif #endif
// names for backward compatibility
#include "lmic_compat.h"
#endif // _lmic_h_ #endif // _lmic_h_

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -35,6 +35,11 @@
// //
// BEG: AS923 related stuff // 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 // see table in section 2.7.3
CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
@ -50,6 +55,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS 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. // see table in 2.7.6 -- this assumes UplinkDwellTime = 0.
static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = { static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
59+5, // [0] 59+5, // [0]
@ -122,7 +135,7 @@ static CONST_TABLE(s1_t, TXMAXEIRP)[16] = {
static int8_t LMICas923_getMaxEIRP(uint8_t mcmd_txparam) { static int8_t LMICas923_getMaxEIRP(uint8_t mcmd_txparam) {
// if uninitialized, return default. // if uninitialized, return default.
if (mcmd_txparam == 0xFF) if (mcmd_txparam == 0xFF)
return AS923_TX_EIRP_MAX_DBM; return AS923_REGION_TX_EIRP_MAX_DBM;
else else
return TABLE_GET_S1( return TABLE_GET_S1(
TXMAXEIRP, TXMAXEIRP,
@ -191,7 +204,7 @@ void LMICas923_initDefaultChannels(bit_t join) {
} }
LMIC.bands[BAND_CENTI].txcap = AS923_TX_CAP; 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].lastchnl = os_getRndU1() % MAX_CHANNELS;
LMIC.bands[BAND_CENTI].avail = os_getTime(); 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; 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) { 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. // zero the band bits in freq, just in case.
freq &= ~3; freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) { if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel. // can't do anything to a default channel.
if (freq == 0) return 0;
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
} }
bit_t fEnable = (freq != 0); bit_t fEnable = (freq != 0);
if (chidx >= MAX_CHANNELS) if (chidx >= MAX_CHANNELS)
@ -315,36 +340,109 @@ void LMICas923_setRx1Params(void) {
LMIC.rps = dndr2rps(LMIC.dndr); LMIC.rps = dndr2rps(LMIC.dndr);
} }
///
// return the next time, but also do channel hopping here /// \brief change the TX channel given the desired tx time.
// identical to the EU868 version; but note that we only have BAND_CENTI ///
// at work. /// \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) { ostime_t LMICas923_nextTx(ostime_t now) {
u1_t bmap = 0xF; ostime_t mintime = now + /*8h*/sec2osticks(28800);
do { u2_t availMap;
ostime_t mintime = now + /*8h*/sec2osticks(28800); u2_t feasibleMap;
u1_t band = 0; u1_t bandMap;
for (u1_t bi = 0; bi<4; bi++) {
if ((bmap & (1 << bi)) && mintime - LMIC.bands[bi].avail > 0) // set mintime to the earliest time of all enabled channels
mintime = LMIC.bands[band = bi].avail; // (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
// Find next channel in given band // avail times.
u1_t chnl = LMIC.bands[band].lastchnl; bandMap = 0;
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) { for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
if ((chnl = (chnl + 1)) >= MAX_CHANNELS) u2_t chnlBit = 1 << chnl;
chnl -= MAX_CHANNELS;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled // none at any higher numbers?
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 && if (LMIC.channelMap < chnlBit)
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band break;
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
return mintime; // not enabled?
} if ((LMIC.channelMap & chnlBit) == 0)
} continue;
if ((bmap &= ~(1 << band)) == 0) {
// No feasible channel found! // not feasible?
return mintime; if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
} continue;
} while (1);
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) #if !defined(DISABLE_BEACONS)
@ -364,7 +462,7 @@ ostime_t LMICas923_nextJoinState(void) {
void void
LMICas923_initJoinLoop(void) { LMICas923_initJoinLoop(void) {
// LMIC.txParam is set to 0xFF by the central code at init time. // 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 void

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -30,10 +30,10 @@
#include "lmic_bandplan.h" #include "lmic_bandplan.h"
#if defined(CFG_au921) #if defined(CFG_au915)
// ================================================================================ // ================================================================================
// //
// BEG: AU921 related stuff // BEG: AU915 related stuff
// //
CONST_TABLE(u1_t, _DR2RPS_CRC)[] = { CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
@ -55,6 +55,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS 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)[] = { static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 0, 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 }; 61+5, 137+5, 250+5, 250+5, 250+5, 250+5 };
@ -64,16 +72,16 @@ static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = {
61+5, 137+5, 250+5, 250+5, 250+5, 250+5 }; 61+5, 137+5, 250+5, 250+5, 250+5, 250+5 };
static bit_t static bit_t
LMICau921_getUplinkDwellBit() { LMICau915_getUplinkDwellBit() {
// if uninitialized, return default. // if uninitialized, return default.
if (LMIC.txParam == 0xFF) { if (LMIC.txParam == 0xFF) {
return AU921_INITIAL_TxParam_UplinkDwellTime; return AU915_INITIAL_TxParam_UplinkDwellTime;
} }
return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0; return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0;
} }
uint8_t LMICau921_maxFrameLen(uint8_t dr) { uint8_t LMICau915_maxFrameLen(uint8_t dr) {
if (LMICau921_getUplinkDwellBit()) { if (LMICau915_getUplinkDwellBit()) {
if (dr < LENOF_TABLE(maxFrameLens_dwell0)) if (dr < LENOF_TABLE(maxFrameLens_dwell0))
return TABLE_GET_U1(maxFrameLens_dwell0, dr); return TABLE_GET_U1(maxFrameLens_dwell0, dr);
else else
@ -91,10 +99,10 @@ static CONST_TABLE(s1_t, TXMAXEIRP)[16] = {
8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36 8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36
}; };
static int8_t LMICau921_getMaxEIRP(uint8_t mcmd_txparam) { static int8_t LMICau915_getMaxEIRP(uint8_t mcmd_txparam) {
// if uninitialized, return default. // if uninitialized, return default.
if (mcmd_txparam == 0xFF) if (mcmd_txparam == 0xFF)
return AU921_TX_EIRP_MAX_DBM; return AU915_TX_EIRP_MAX_DBM;
else else
return TABLE_GET_S1( return TABLE_GET_S1(
TXMAXEIRP, TXMAXEIRP,
@ -103,11 +111,11 @@ static int8_t LMICau921_getMaxEIRP(uint8_t mcmd_txparam) {
); );
} }
int8_t LMICau921_pow2dbm(uint8_t mcmd_ladr_p1) { int8_t LMICau915_pow2dbm(uint8_t mcmd_ladr_p1) {
if ((mcmd_ladr_p1 & MCMD_LinkADRReq_POW_MASK) == MCMD_LinkADRReq_POW_MASK) if ((mcmd_ladr_p1 & MCMD_LinkADRReq_POW_MASK) == MCMD_LinkADRReq_POW_MASK)
return -128; return -128;
else { else {
return ((s1_t)(LMICau921_getMaxEIRP(LMIC.txParam) - (((mcmd_ladr_p1)&MCMD_LinkADRReq_POW_MASK)<<1))); return ((s1_t)(LMICau915_getMaxEIRP(LMIC.txParam) - (((mcmd_ladr_p1)&MCMD_LinkADRReq_POW_MASK)<<1)));
} }
} }
@ -131,20 +139,37 @@ static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = {
// get ostime for symbols based on datarate. This is not like us915, // get ostime for symbols based on datarate. This is not like us915,
// becuase the times don't match between the upper half and lower half // becuase the times don't match between the upper half and lower half
// of the table. // of the table.
ostime_t LMICau921_dr2hsym(uint8_t dr) { ostime_t LMICau915_dr2hsym(uint8_t dr) {
return TABLE_GET_OSTIME(DR2HSYM_osticks, dr); return TABLE_GET_OSTIME(DR2HSYM_osticks, dr);
} }
u4_t LMICau921_convFreq(xref2cu1_t ptr) { u4_t LMICau915_convFreq(xref2cu1_t ptr) {
u4_t freq = (os_rlsbf4(ptr - 1) >> 8) * 100; u4_t freq = (os_rlsbf4(ptr - 1) >> 8) * 100;
if (freq < AU921_FREQ_MIN || freq > AU921_FREQ_MAX) if (freq < AU915_FREQ_MIN || freq > AU915_FREQ_MAX)
freq = 0; freq = 0;
return freq; return freq;
} }
// au921: 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) { bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
LMIC_API_PARAMETER(chidx); LMIC_API_PARAMETER(chidx);
LMIC_API_PARAMETER(freq); LMIC_API_PARAMETER(freq);
@ -229,14 +254,14 @@ bit_t LMIC_selectSubBand(u1_t band) {
return result; return result;
} }
void LMICau921_updateTx(ostime_t txbeg) { void LMICau915_updateTx(ostime_t txbeg) {
u1_t chnl = LMIC.txChnl; u1_t chnl = LMIC.txChnl;
LMIC.txpow = LMICau921_getMaxEIRP(LMIC.txParam); LMIC.txpow = LMICau915_getMaxEIRP(LMIC.txParam);
if (chnl < 64) { if (chnl < 64) {
LMIC.freq = AU921_125kHz_UPFBASE + chnl*AU921_125kHz_UPFSTEP; LMIC.freq = AU915_125kHz_UPFBASE + chnl*AU915_125kHz_UPFSTEP;
} else { } else {
ASSERT(chnl < 64 + 8); ASSERT(chnl < 64 + 8);
LMIC.freq = AU921_500kHz_UPFBASE + (chnl - 64)*AU921_500kHz_UPFSTEP; LMIC.freq = AU915_500kHz_UPFBASE + (chnl - 64)*AU915_500kHz_UPFSTEP;
} }
// Update global duty cycle stat and deal with dwell time. // Update global duty cycle stat and deal with dwell time.
@ -248,8 +273,8 @@ void LMICau921_updateTx(ostime_t txbeg) {
ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen); ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
globalDutyDelay = txbeg + (airtime << LMIC.globalDutyRate); globalDutyDelay = txbeg + (airtime << LMIC.globalDutyRate);
} }
if (LMICau921_getUplinkDwellBit(LMIC.txParam)) { if (LMICau915_getUplinkDwellBit(LMIC.txParam)) {
dwellDelay = AU921_UPLINK_DWELL_TIME_osticks; dwellDelay = AU915_UPLINK_DWELL_TIME_osticks;
} }
if (dwellDelay > globalDutyDelay) { if (dwellDelay > globalDutyDelay) {
globalDutyDelay = dwellDelay; globalDutyDelay = dwellDelay;
@ -260,22 +285,22 @@ void LMICau921_updateTx(ostime_t txbeg) {
} }
#if !defined(DISABLE_BEACONS) #if !defined(DISABLE_BEACONS)
void LMICau921_setBcnRxParams(void) { void LMICau915_setBcnRxParams(void) {
LMIC.dataLen = 0; LMIC.dataLen = 0;
LMIC.freq = AU921_500kHz_DNFBASE + LMIC.bcnChnl * AU921_500kHz_DNFSTEP; LMIC.freq = AU915_500kHz_DNFBASE + LMIC.bcnChnl * AU915_500kHz_DNFSTEP;
LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN), 1), LEN_BCN); LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN), 1), LEN_BCN);
} }
#endif // !DISABLE_BEACONS #endif // !DISABLE_BEACONS
// set the Rx1 dndr, rps. // set the Rx1 dndr, rps.
void LMICau921_setRx1Params(void) { void LMICau915_setRx1Params(void) {
u1_t const txdr = LMIC.dndr; u1_t const txdr = LMIC.dndr;
u1_t candidateDr; u1_t candidateDr;
LMIC.freq = AU921_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * AU921_500kHz_DNFSTEP; LMIC.freq = AU915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * AU915_500kHz_DNFSTEP;
if ( /* TX datarate */txdr < AU921_DR_SF8C) if ( /* TX datarate */txdr < AU915_DR_SF8C)
candidateDr = txdr + 8 - LMIC.rx1DrOffset; candidateDr = txdr + 8 - LMIC.rx1DrOffset;
else else
candidateDr = AU921_DR_SF7CR; candidateDr = AU915_DR_SF7CR;
if (candidateDr < LORAWAN_DR8) if (candidateDr < LORAWAN_DR8)
candidateDr = LORAWAN_DR8; candidateDr = LORAWAN_DR8;
@ -286,17 +311,17 @@ void LMICau921_setRx1Params(void) {
LMIC.rps = dndr2rps(LMIC.dndr); LMIC.rps = dndr2rps(LMIC.dndr);
} }
void LMICau921_initJoinLoop(void) { void LMICau915_initJoinLoop(void) {
// LMIC.txParam is set to 0xFF by the central code at init time. // LMIC.txParam is set to 0xFF by the central code at init time.
LMICuslike_initJoinLoop(); LMICuslike_initJoinLoop();
// initialize the adrTxPower. // initialize the adrTxPower.
LMIC.adrTxPow = LMICau921_getMaxEIRP(LMIC.txParam); // dBm LMIC.adrTxPow = LMICau915_getMaxEIRP(LMIC.txParam); // dBm
} }
// //
// END: AU921 related stuff // END: AU915 related stuff
// //
// ================================================================================ // ================================================================================
#endif #endif

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -37,8 +37,8 @@
# include "lmic_bandplan_eu868.h" # include "lmic_bandplan_eu868.h"
#elif defined(CFG_us915) #elif defined(CFG_us915)
# include "lmic_bandplan_us915.h" # include "lmic_bandplan_us915.h"
#elif defined(CFG_au921) #elif defined(CFG_au915)
# include "lmic_bandplan_au921.h" # include "lmic_bandplan_au915.h"
#elif defined(CFG_as923) #elif defined(CFG_as923)
# include "lmic_bandplan_as923.h" # include "lmic_bandplan_as923.h"
#elif defined(CFG_kr920) #elif defined(CFG_kr920)
@ -166,6 +166,14 @@
# error "LMICbandplan_isDataRateFeasible() not defined by bandplan" # error "LMICbandplan_isDataRateFeasible() not defined by bandplan"
#endif #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 // Things common to lmic.c code
// //
@ -226,7 +234,24 @@
// internal APIs // internal APIs
ostime_t LMICcore_rndDelay(u1_t secSpan); ostime_t LMICcore_rndDelay(u1_t secSpan);
void LMICcore_setDrJoin(u1_t reason, u1_t dr); void LMICcore_setDrJoin(u1_t reason, u1_t dr);
ostime_t LMICcore_adjustForDrift(ostime_t delay, ostime_t hsym); ostime_t LMICcore_adjustForDrift(ostime_t delay, ostime_t hsym, rxsyms_t rxsyms_in);
ostime_t LMICcore_RxWindowOffset(ostime_t hsym, u1_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_ #endif // _lmic_bandplan_h_

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * 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); ostime_t LMICas923_nextJoinTime(ostime_t now);
#define LMICbandplan_nextJoinTime(now) LMICas923_nextJoinTime(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_ #endif // _lmic_bandplan_as923_h_

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -26,8 +26,8 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#ifndef _lmic_bandplan_au921_h_ #ifndef _lmic_bandplan_au915_h_
# define _lmic_bandplan_au921_h_ # define _lmic_bandplan_au915_h_
// preconditions for lmic_us_like.h // preconditions for lmic_us_like.h
#define LMICuslike_getFirst500kHzDR() (LORAWAN_DR6) #define LMICuslike_getFirst500kHzDR() (LORAWAN_DR6)
@ -37,33 +37,37 @@
# include "lmic_us_like.h" # include "lmic_us_like.h"
#endif #endif
// return maximum frame length (including PHY header) for this data rate (au921); 0 --> not valid dr. // return maximum frame length (including PHY header) for this data rate (au915); 0 --> not valid dr.
uint8_t LMICau921_maxFrameLen(uint8_t dr); uint8_t LMICau915_maxFrameLen(uint8_t dr);
// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr. // return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr.
#define LMICbandplan_maxFrameLen(dr) LMICau921_maxFrameLen(dr) #define LMICbandplan_maxFrameLen(dr) LMICau915_maxFrameLen(dr)
int8_t LMICau921_pow2dbm(uint8_t mcmd_ladr_p1); int8_t LMICau915_pow2dbm(uint8_t mcmd_ladr_p1);
#define pow2dBm(mcmd_ladr_p1) LMICau921_pow2dbm(mcmd_ladr_p1) #define pow2dBm(mcmd_ladr_p1) LMICau915_pow2dbm(mcmd_ladr_p1)
ostime_t LMICau921_dr2hsym(uint8_t dr); ostime_t LMICau915_dr2hsym(uint8_t dr);
#define dr2hsym(dr) LMICau921_dr2hsym(dr) #define dr2hsym(dr) LMICau915_dr2hsym(dr)
#define LMICbandplan_getInitialDrJoin() (LORAWAN_DR2) #define LMICbandplan_getInitialDrJoin() (LORAWAN_DR2)
void LMICau921_initJoinLoop(void); void LMICau915_initJoinLoop(void);
#define LMICbandplan_initJoinLoop() LMICau921_initJoinLoop() #define LMICbandplan_initJoinLoop() LMICau915_initJoinLoop()
void LMICau921_setBcnRxParams(void); void LMICau915_setBcnRxParams(void);
#define LMICbandplan_setBcnRxParams() LMICau921_setBcnRxParams() #define LMICbandplan_setBcnRxParams() LMICau915_setBcnRxParams()
u4_t LMICau921_convFreq(xref2cu1_t ptr); u4_t LMICau915_convFreq(xref2cu1_t ptr);
#define LMICbandplan_convFreq(ptr) LMICau921_convFreq(ptr) #define LMICbandplan_convFreq(ptr) LMICau915_convFreq(ptr)
void LMICau921_setRx1Params(void); void LMICau915_setRx1Params(void);
#define LMICbandplan_setRx1Params() LMICau921_setRx1Params() #define LMICbandplan_setRx1Params() LMICau915_setRx1Params()
void LMICau921_updateTx(ostime_t txbeg); void LMICau915_updateTx(ostime_t txbeg);
#define LMICbandplan_updateTx(txbeg) LMICau921_updateTx(txbeg) #define LMICbandplan_updateTx(txbeg) LMICau915_updateTx(txbeg)
#endif // _lmic_bandplan_au921_h_ #undef LMICbandplan_validDR
bit_t LMICau915_validDR(dr_t dr);
#define LMICbandplan_validDR(dr) LMICau915_validDR(dr)
#endif // _lmic_bandplan_au915_h_

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * 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); void LMICeu868_setRx1Params(void);
#define LMICbandplan_setRx1Params() LMICeu868_setRx1Params() #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_ #endif // _lmic_eu868_h_

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * 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); void LMICin866_setRx1Params(void);
#define LMICbandplan_setRx1Params() LMICin866_setRx1Params() #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_ #endif // _lmic_bandplan_in866_h_

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * 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); void LMICkr920_updateTx(ostime_t txbeg);
#define LMICbandplan_updateTx(t) LMICkr920_updateTx(t) #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_ #endif // _lmic_kr920_h_

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * 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); void LMICus915_updateTx(ostime_t txbeg);
#define LMICbandplan_updateTx(txbeg) LMICus915_updateTx(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_ #endif // _lmic_bandplan_us915_h_

View File

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

72
src/lmic/lmic_compat.h Normal file
View File

@ -0,0 +1,72 @@
/*
Module: lmic_compat.h
Function:
Symbols that are defined for backward compatibility
Copyright notice and license info:
See LICENSE file accompanying this project.
Author:
Terry Moore, MCCI Corporation January 2020
Description:
This include file centralizes backwards compatibility
definitions. The idea is to centralize the decision,
so it's clear as to what's deprecated.
*/
#ifndef _lmic_compat_h_ /* prevent multiple includes */
#define _lmic_compat_h_
#ifdef __cplusplus
extern "C"{
#endif
#ifndef ARDUINO_LMIC_VERSION
# error "This file is normally included from lmic.h, not stand alone"
#endif
#define LMIC_DEPRECATE(m) _Pragma(#m)
#if ! defined(LMIC_REGION_au921) && ARDUINO_LMIC_VERSION < ARDUINO_LMIC_VERSION_CALC(5,0,0,0)
# define LMIC_REGION_au921 LMIC_DEPRECATE(GCC warning "LMIC_REGION_au921 is deprecated, EOL at V5, use LMIC_REGION_au915") \
LMIC_REGION_au915
// Frequency plan symbols
# define AU921_DR_SF12 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF12
# define AU921_DR_SF11 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF11
# define AU921_DR_SF10 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF10
# define AU921_DR_SF9 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF9
# define AU921_DR_SF8 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF8
# define AU921_DR_SF7 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF7
# define AU921_DR_SF8C LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF8C
# define AU921_DR_NONE LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_NONE
# define AU921_DR_SF12CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF12CR
# define AU921_DR_SF11CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF11CR
# define AU921_DR_SF10CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF10CR
# define AU921_DR_SF9CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF9CR
# define AU921_DR_SF8CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF8CR
# define AU921_DR_SF7CR LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_DR_SF7CR
# define AU921_125kHz_UPFBASE LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_125kHz_UPFBASE
# define AU921_125kHz_UPFSTEP LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_125kHz_UPFSTEP
# define AU921_500kHz_UPFBASE LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_UPFBASE
# define AU921_500kHz_UPFSTEP LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_UPFSTEP
# define AU921_500kHz_DNFBASE LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_DNFBASE
# define AU921_500kHz_DNFSTEP LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_500kHz_DNFSTEP
# define AU921_FREQ_MIN LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_FREQ_MIN
# define AU921_FREQ_MAX LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_FREQ_MAX
# define AU921_TX_EIRP_MAX_DBM LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_TX_EIRP_MAX_DBM
# define AU921_INITIAL_TxParam_UplinkDwellTime LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_INITIAL_TxParam_UplinkDwellTime
# define AU921_UPLINK_DWELL_TIME_osticks LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_UPLINK_DWELL_TIME_osticks
# define DR_PAGE_AU921 LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") DR_PAGE_AU915
# define AU921_LMIC_REGION_EIRP LMIC_DEPRECATE(GCC warning "A921 symbols are deprecated EOL V5, use AU915") AU915_LMIC_REGION_EIRP
#endif
#ifdef __cplusplus
}
#endif
#endif /* _lmic_compat_h_ */

View File

@ -284,7 +284,7 @@ static void evMessage(
break; break;
} }
case LORAWAN_COMPLIANCE_CMD_LINK: { case LORAWAN_COMPLIANCE_CMD_LINK: {
// not clear what this request does. // we are required to initiate a Link
break; break;
} }
case LORAWAN_COMPLIANCE_CMD_JOIN: { case LORAWAN_COMPLIANCE_CMD_JOIN: {
@ -659,6 +659,7 @@ void acSetTimer(ostime_t delay) {
} }
static void timerExpiredCb(osjob_t *j) { static void timerExpiredCb(osjob_t *j) {
LMIC_API_PARAMETER(j);
LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED; LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED;
fsmEval(); fsmEval();
} }
@ -726,7 +727,7 @@ static void acSendUplink(void) {
__func__, __func__,
eSend, eSend,
(unsigned) downlink & 0xFFFF, (unsigned) downlink & 0xFFFF,
LMIC.client.txMessageCb (unsigned) sizeof(payload)
); );
LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE; LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE;
fsmEval(); fsmEval();
@ -734,6 +735,8 @@ static void acSendUplink(void) {
} }
static void sendUplinkCompleteCb(void *pUserData, int fSuccess) { static void sendUplinkCompleteCb(void *pUserData, int fSuccess) {
LMIC_API_PARAMETER(pUserData);
LMIC_API_PARAMETER(fSuccess);
LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE; LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE;
LMIC_COMPLIANCE_PRINTF("%s(%s)\n", __func__, LMICcompliance_txSuccessToString(fSuccess)); LMIC_COMPLIANCE_PRINTF("%s(%s)\n", __func__, LMICcompliance_txSuccessToString(fSuccess));
fsmEvalDeferred(); fsmEvalDeferred();

View File

@ -67,7 +67,7 @@ Revision history:
#define LMIC_REGION_us915 2 #define LMIC_REGION_us915 2
#define LMIC_REGION_cn783 3 #define LMIC_REGION_cn783 3
#define LMIC_REGION_eu433 4 #define LMIC_REGION_eu433 4
#define LMIC_REGION_au921 5 #define LMIC_REGION_au915 5
#define LMIC_REGION_cn490 6 #define LMIC_REGION_cn490 6
#define LMIC_REGION_as923 7 #define LMIC_REGION_as923 7
#define LMIC_REGION_kr920 8 #define LMIC_REGION_kr920 8
@ -93,6 +93,16 @@ Revision history:
# include CFG_TEXT_1(ARDUINO_LMIC_PROJECT_CONFIG_H) # include CFG_TEXT_1(ARDUINO_LMIC_PROJECT_CONFIG_H)
#endif /* ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS */ #endif /* ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS */
#if defined(CFG_au921) && !defined(CFG_au915)
# warning "CFG_au921 was deprecated in favour of CFG_au915. Support for CFG_au921 might be removed in the future."
# define CFG_au915
#endif
// for backwards compatibility to legacy code, define CFG_au921 if we see CFG_au915.
#if defined(CFG_au915) && !defined(CFG_au921)
# define CFG_au921
#endif
// a mask of the supported regions // a mask of the supported regions
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not // TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable. // user-editable.
@ -101,37 +111,13 @@ Revision history:
(1 << LMIC_REGION_us915) | \ (1 << LMIC_REGION_us915) | \
/* (1 << LMIC_REGION_cn783) | */ \ /* (1 << LMIC_REGION_cn783) | */ \
/* (1 << LMIC_REGION_eu433) | */ \ /* (1 << LMIC_REGION_eu433) | */ \
(1 << LMIC_REGION_au921) | \ (1 << LMIC_REGION_au915) | \
/* (1 << LMIC_REGION_cn490) | */ \ /* (1 << LMIC_REGION_cn490) | */ \
(1 << LMIC_REGION_as923) | \ (1 << LMIC_REGION_as923) | \
(1 << LMIC_REGION_kr920) | \ (1 << LMIC_REGION_kr920) | \
(1 << LMIC_REGION_in866) | \ (1 << LMIC_REGION_in866) | \
0) 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_au921) << LMIC_REGION_au921) | \
(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. // the selected region.
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not // TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable. // user-editable.
@ -143,8 +129,8 @@ Revision history:
# define CFG_region LMIC_REGION_cn783 # define CFG_region LMIC_REGION_cn783
#elif defined(CFG_eu433) #elif defined(CFG_eu433)
# define CFG_region LMIC_REGION_eu433 # define CFG_region LMIC_REGION_eu433
#elif defined(CFG_au921) #elif defined(CFG_au915)
# define CFG_region LMIC_REGION_au921 # define CFG_region LMIC_REGION_au915
#elif defined(CFG_cn490) #elif defined(CFG_cn490)
# define CFG_region LMIC_REGION_cn490 # define CFG_region LMIC_REGION_cn490
#elif defined(CFG_as923jp) #elif defined(CFG_as923jp)
@ -161,35 +147,120 @@ Revision history:
# define CFG_region 0 # define CFG_region 0
#endif #endif
// a bitmask of EU-like regions -- these are regions which have up to 16 // LMIC_CFG_*_ENA -- either 1 or 0 based on whether that region
// channels indidually programmable via downloink. // is enabled. Note: these must be after the code that special-cases
// // CFG_as923jp.
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not #if defined(CFG_eu868)
// user-editable. # 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 ( \ #define CFG_LMIC_EU_like_MASK ( \
(1 << LMIC_REGION_eu868) | \ (1 << LMIC_REGION_eu868) | \
/* (1 << LMIC_REGION_us915) | */ \ /* (1 << LMIC_REGION_us915) | */ \
(1 << LMIC_REGION_cn783) | \ (1 << LMIC_REGION_cn783) | \
(1 << LMIC_REGION_eu433) | \ (1 << LMIC_REGION_eu433) | \
/* (1 << LMIC_REGION_au921) | */ \ /* (1 << LMIC_REGION_au915) | */ \
/* (1 << LMIC_REGION_cn490) | */ \ /* (1 << LMIC_REGION_cn490) | */ \
(1 << LMIC_REGION_as923) | \ (1 << LMIC_REGION_as923) | \
(1 << LMIC_REGION_kr920) | \ (1 << LMIC_REGION_kr920) | \
(1 << LMIC_REGION_in866) | \ (1 << LMIC_REGION_in866) | \
0) 0)
// a bitmask of` US-like regions -- these are regions with 64 fixed 125 kHz channels /// \brief bitmask of` US-like regions
// overlaid by 8 500 kHz channels. The channel frequencies can't be changed, but ///
// subsets of channels can be selected via masks. /// US-like regions have 64 fixed 125-kHz channels overlaid by 8 500-kHz
// /// channels. The channel frequencies can't be changed, but
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not /// subsets of channels can be selected via masks.
// user-editable. ///
/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
/// user-editable.
#define CFG_LMIC_US_like_MASK ( \ #define CFG_LMIC_US_like_MASK ( \
/* (1 << LMIC_REGION_eu868) | */ \ /* (1 << LMIC_REGION_eu868) | */ \
(1 << LMIC_REGION_us915) | \ (1 << LMIC_REGION_us915) | \
/* (1 << LMIC_REGION_cn783) | */ \ /* (1 << LMIC_REGION_cn783) | */ \
/* (1 << LMIC_REGION_eu433) | */ \ /* (1 << LMIC_REGION_eu433) | */ \
(1 << LMIC_REGION_au921) | \ (1 << LMIC_REGION_au915) | \
/* (1 << LMIC_REGION_cn490) | */ \ /* (1 << LMIC_REGION_cn490) | */ \
/* (1 << LMIC_REGION_as923) | */ \ /* (1 << LMIC_REGION_as923) | */ \
/* (1 << LMIC_REGION_kr920) | */ \ /* (1 << LMIC_REGION_kr920) | */ \
@ -201,16 +272,19 @@ Revision history:
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not // TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable. // 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)) #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)) #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 // so that we can (for example) compare
// //
// LMIC_LORAWAN_SPEC_VERSION < LMIC_LORAWAN_SPEC_VERSION_1_0_3. // 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_2 0x01000200u /// Refers to LoRaWAN spec 1.0.2
#define LMIC_LORAWAN_SPEC_VERSION_1_0_3 0x01000300u #define LMIC_LORAWAN_SPEC_VERSION_1_0_3 0x01000300u /// Refers to LoRaWAN spec 1.0.3
#endif /* _LMIC_CONFIG_PRECONDITIONS_H_ */ #endif /* _LMIC_CONFIG_PRECONDITIONS_H_ */

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -49,6 +49,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS 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)[] = { static CONST_TABLE(u1_t, maxFrameLens)[] = {
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 250+5 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, 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) { 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. // zero the band bits in freq, just in case.
freq &= ~3; freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) { if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel. // can't do anything to a default channel.
if (freq == 0) return 0;
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
} }
bit_t fEnable = (freq != 0); bit_t fEnable = (freq != 0);
if (chidx >= MAX_CHANNELS) if (chidx >= MAX_CHANNELS)
@ -204,32 +223,111 @@ ostime_t LMICeu868_nextJoinTime(ostime_t time) {
return 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) { ostime_t LMICeu868_nextTx(ostime_t now) {
u1_t bmap = 0xF; ostime_t mintime = now + /*8h*/sec2osticks(28800);
do { u2_t availMap;
ostime_t mintime = now + /*8h*/sec2osticks(28800); u2_t feasibleMap;
u1_t band = 0; u1_t bandMap;
for (u1_t bi = 0; bi<4; bi++) {
if ((bmap & (1 << bi)) && mintime - LMIC.bands[bi].avail > 0) // set mintime to the earliest time of all enabled channels
mintime = LMIC.bands[band = bi].avail; // (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
// Find next channel in given band // avail times.
u1_t chnl = LMIC.bands[band].lastchnl; bandMap = 0;
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) { for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
if ((chnl = (chnl + 1)) >= MAX_CHANNELS) u2_t chnlBit = 1 << chnl;
chnl -= MAX_CHANNELS;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled // none at any higher numbers?
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 && if (LMIC.channelMap < chnlBit)
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band break;
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
return mintime; // not enabled?
} if ((LMIC.channelMap & chnlBit) == 0)
} continue;
if ((bmap &= ~(1 << band)) == 0) {
// No feasible channel found! // not feasible?
return mintime; if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
} continue;
} while (1);
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
u1_t const thisBandBit = 1 << band;
// already considered?
if ((bandMap & thisBandBit) != 0)
continue;
// consider this band.
bandMap |= thisBandBit;
// enabled, not considered, feasible: adjust the min time.
if ((s4_t)(mintime - LMIC.bands[band].avail) > 0)
mintime = LMIC.bands[band].avail;
}
// make a mask of candidates available for use
availMap = 0;
feasibleMap = 0;
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
u2_t chnlBit = 1 << chnl;
// none at any higher numbers?
if (LMIC.channelMap < chnlBit)
break;
// not enabled?
if ((LMIC.channelMap & chnlBit) == 0)
continue;
// not feasible?
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
continue;
// This channel is feasible. But might not be available.
feasibleMap |= chnlBit;
// not available yet?
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
if ((s4_t)(LMIC.bands[band].avail - mintime) > 0)
continue;
// ok: this is a candidate.
availMap |= chnlBit;
}
// find the next available chennel.
u2_t saveShuffleMap = LMIC.channelShuffleMap;
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availMap, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
// restore bits in the shuffleMap that were on, but might have reset
// if availMap was used to refresh shuffleMap. These are channels that
// are feasble but not yet candidates due to band saturation
LMIC.channelShuffleMap |= saveShuffleMap & feasibleMap & ~availMap;
if (candidateCh >= 0) {
// update the channel; otherwise we'll just use the
// most recent one.
LMIC.txChnl = candidateCh;
}
return mintime;
} }

View File

@ -128,7 +128,9 @@ void LMICeulike_initJoinLoop(uint8_t nDefaultChannels, s1_t adrTxPow) {
#if CFG_TxContinuousMode #if CFG_TxContinuousMode
LMIC.txChnl = 0 LMIC.txChnl = 0
#else #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 #endif
LMIC.adrTxPow = adrTxPow; LMIC.adrTxPow = adrTxPow;
// TODO(tmm@mcci.com) don't use EU directly, use a table. That // 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) { ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
u1_t failed = 0; u1_t failed = 0;
u2_t enableMap = (1 << nDefaultChannels) - 1;
// Try each default channel with same DR // Try each default channel with same DR
// If all fail try next lower datarate // If all fail try next lower datarate
if (++LMIC.txChnl == /* NUM_DEFAULT_CHANNELS */ nDefaultChannels) if (LMIC.channelShuffleMap == 0) {
LMIC.txChnl = 0;
if ((++LMIC.txCnt % nDefaultChannels) == 0) {
// Lower DR every nth try (having all default channels with same DR) // 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; // 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 // the failed flag here. This will cause the outer caller to take the
// appropriate join path. Or add new LMICeulike_GetLowestJoinDR() // appropriate join path. Or add new LMICeulike_GetLowestJoinDR()
// //
// TODO(tmm@mcci.com) - see above; please remove regional dependency from this file. // TODO(tmm@mcci.com) - see above; please remove regional dependency from this file.
#if CFG_region == LMIC_REGION_as923 #if CFG_region == LMIC_REGION_as923
// in the join of AS923 v1.1 or older, only DR2 is used. // 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; LMIC.datarate = AS923_DR_SF10;
failed = 1; failed = 1;
#else #else
if (LMIC.datarate == LORAWAN_DR0) if (LMIC.datarate == LORAWAN_DR0) {
failed = 1; // we have tried all DR - signal EV_JOIN_FAILED failed = 1; // we have tried all DR - signal EV_JOIN_FAILED
else { } else {
LMICcore_setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate)); LMICcore_setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
} }
#endif #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; LMIC.opmode &= ~OP_NEXTCHNL;
// Move txend to randomize synchronized concurrent joins. // Move txend to randomize synchronized concurrent joins.
// Duty cycle is based on txend. // Duty cycle is based on txend.
ostime_t const time = LMICbandplan_nextJoinTime(os_getTime()); ostime_t const time = LMICbandplan_nextJoinTime(os_getTime());
@ -220,6 +227,27 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
} }
#endif // !DISABLE_JOIN #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) { void LMICeulike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) {
os_copyMem( os_copyMem(
pStateBuffer->channelFreq, pStateBuffer->channelFreq,
@ -255,14 +283,15 @@ void LMICeulike_setRx1Freq(void) {
// Class A txDone handling for FSK. // Class A txDone handling for FSK.
void void
LMICeulike_txDoneFSK(ostime_t delay, osjobcb_t func) { LMICeulike_txDoneFSK(ostime_t delay, osjobcb_t func) {
ostime_t const hsym = us2osticksRound(80); // one symbol == one bit at 50kHz == 20us.
ostime_t const hsym = us2osticksRound(10);
// start a little earlier. // start a little earlier. PRERX_FSK is in bytes; one byte at 50 kHz == 160us
delay -= LMICbandplan_PRERX_FSK * us2osticksRound(160); delay -= LMICbandplan_PRERX_FSK * us2osticksRound(160);
// set LMIC.rxtime and LMIC.rxsyms: // set LMIC.rxtime and LMIC.rxsyms:
LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay + LMICcore_RxWindowOffset(hsym, LMICbandplan_RXLEN_FSK), hsym); LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay, hsym, 8 * LMICbandplan_RXLEN_FSK);
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - os_getRadioRxRampup(), func);
} }
#endif // CFG_LMIC_EU_like #endif // CFG_LMIC_EU_like

View File

@ -66,6 +66,11 @@ enum { BAND_MILLI = 0, BAND_CENTI = 1, BAND_DECI = 2, BAND_AUX = 3 };
// there's a CFList on joins for EU-like plans // there's a CFList on joins for EU-like plans
#define LMICbandplan_hasJoinCFlist() (1) #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() \ #define LMICbandplan_advanceBeaconChannel() \
do { /* nothing */ } while (0) do { /* nothing */ } while (0)

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -49,6 +49,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS 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)[] = { static CONST_TABLE(u1_t, maxFrameLens)[] = {
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 0, 250+5 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; 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) { 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. // zero the band bits in freq, just in case.
freq &= ~3; freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) { if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel. return 0;
if (freq == 0)
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
} }
bit_t fEnable = (freq != 0); bit_t fEnable = (freq != 0);
if (chidx >= MAX_CHANNELS) if (chidx >= MAX_CHANNELS)
@ -173,28 +191,44 @@ u4_t LMICin866_convFreq(xref2cu1_t ptr) {
return freq; return freq;
} }
// return the next time, but also do channel hopping here ///
// since there's no duty cycle limitation, and no dwell limitation, /// \brief change the TX channel given the desired tx time.
// we simply loop through the channels sequentially. ///
/// \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) { 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++) { // scan all the enabled channels and make a mask of candidates
// Find next channel in given band availmask = 0;
u1_t chnl = LMIC.bands[band].lastchnl; for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) { // not enabled?
if ((chnl = (chnl + 1)) >= MAX_CHANNELS) if ((LMIC.channelMap & (1 << chnl)) == 0)
chnl -= MAX_CHANNELS; continue;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled // not feasible?
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 && if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band continue;
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl; availmask |= 1 << chnl;
return now;
}
}
} }
// 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; return now;
} }

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -47,6 +47,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS, // [6] 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)[] = { static CONST_TABLE(u1_t, maxFrameLens)[] = {
59+5, 59+5, 59+5, 123+5, 250+5, 250+5 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; 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) { 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. // zero the band bits in freq, just in case.
freq &= ~3; freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) { if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel.
if (freq == 0)
return 0;
// can't change a default channel. // can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3)) return 0;
return 0;
} }
bit_t fEnable = (freq != 0); bit_t fEnable = (freq != 0);
if (chidx >= MAX_CHANNELS) if (chidx >= MAX_CHANNELS)
@ -184,28 +203,44 @@ u4_t LMICkr920_convFreq(xref2cu1_t ptr) {
return freq; return freq;
} }
// return the next time, but also do channel hopping here ///
// since there's no duty cycle limitation, and no dwell limitation, /// \brief change the TX channel given the desired tx time.
// we simply loop through the channels sequentially. ///
/// \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) { 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++) { // scan all the enabled channels and make a mask of candidates
// Find next channel in given band availmask = 0;
u1_t chnl = LMIC.bands[band].lastchnl; for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) { // not enabled?
if ((chnl = (chnl + 1)) >= MAX_CHANNELS) if ((LMIC.channelMap & (1 << chnl)) == 0)
chnl -= MAX_CHANNELS; continue;
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled // not feasible?
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 && if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band continue;
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl; availmask |= 1 << chnl;
return now;
}
}
} }
// 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; return now;
} }

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2017, 2019 MCCI Corporation. * Copyright (c) 2017, 2019-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -55,6 +55,14 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS // [14] 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)[] = { static CONST_TABLE(u1_t, maxFrameLens)[] = {
19+5, 61+5, 133+5, 250+5, 250+5, 0, 0,0, 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 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; 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) { 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); LMIC_API_PARAMETER(band);
if (chidx < 72 || chidx >= 72 + MAX_XCHANNELS) return 0; // channels 0..71 are hardwired
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;
} }
bit_t LMIC_disableChannel(u1_t channel) { bit_t LMIC_disableChannel(u1_t channel) {
bit_t result = 0; bit_t result = 0;
if (channel < 72 + MAX_XCHANNELS) { if (channel < 72) {
if (ENABLED_CHANNEL(channel)) { if (ENABLED_CHANNEL(channel)) {
result = 1; result = 1;
if (IS_CHANNEL_125khz(channel)) 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 LMIC_enableChannel(u1_t channel) {
bit_t result = 0; bit_t result = 0;
if (channel < 72 + MAX_XCHANNELS) { if (channel < 72) {
if (!ENABLED_CHANNEL(channel)) { if (!ENABLED_CHANNEL(channel)) {
result = 1; result = 1;
if (IS_CHANNEL_125khz(channel)) if (IS_CHANNEL_125khz(channel))
@ -197,13 +218,7 @@ void LMICus915_updateTx(ostime_t txbeg) {
} else { } else {
// at 500kHz bandwidth, we're allowed more power. // at 500kHz bandwidth, we're allowed more power.
LMIC.txpow = 26; LMIC.txpow = 26;
if (chnl < 64 + 8) { LMIC.freq = US915_500kHz_UPFBASE + (chnl - 64)*US915_500kHz_UPFSTEP;
LMIC.freq = US915_500kHz_UPFBASE + (chnl - 64)*US915_500kHz_UPFSTEP;
}
else {
ASSERT(chnl < 64 + 8 + MAX_XCHANNELS);
LMIC.freq = LMIC.xchFreq[chnl - 72];
}
} }
// Update global duty cycle stats // Update global duty cycle stats

View File

@ -36,36 +36,34 @@
# error "LMICuslike_getFirst500kHzDR() not defined by bandplan" # error "LMICuslike_getFirst500kHzDR() not defined by bandplan"
#endif #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(count>0);
ASSERT(start<end); ASSERT(start<end);
ASSERT(count <= (end - start)); ASSERT(count <= (end - start));
// We used to pick a random channel once and then just increment. That is not per spec. ASSERT((start & 0xF) == 0);
// Now we use a new random number each time, because they are not very expensive. uint16_t const mapStart = start >> 4;
// Regarding the algo below, we cannot pick a number and scan until we hit an enabled channel. uint16_t const mapEntries = (end - start + 15) >> 4;
// That would result in the first enabled channel following a set of disabled ones
// being used more frequently than the other enabled channels.
// Last used channel is in range. It is not a candidate, per spec. int candidate = start + LMIC_findNextChannel(
uint lastTxChan = LMIC.txChnl; LMIC.channelShuffleMap + mapStart,
if (start <= lastTxChan && lastTxChan<end && LMIC.channelMap + mapStart,
// Adjust count only if still enabled. Otherwise, no chance of selection. mapEntries,
ENABLED_CHANNEL(lastTxChan)) { LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl
--count; );
if (count == 0) {
return; // Only one active channel, so keep using it.
}
}
uint nth = os_getRndU1() % count; if (candidate >= 0)
for (u1_t chnl = start; chnl<end; chnl++) { LMIC.txChnl = candidate;
// 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.
} }
@ -87,8 +85,11 @@ void LMICuslike_initDefaultChannels(bit_t fJoin) {
for (u1_t i = 0; i<4; i++) for (u1_t i = 0; i<4; i++)
LMIC.channelMap[i] = 0xFFFF; LMIC.channelMap[i] = 0xFFFF;
LMIC.channelMap[4] = 0x00FF; LMIC.channelMap[4] = 0x00FF;
os_clearMem(LMIC.channelShuffleMap, sizeof(LMIC.channelShuffleMap));
LMIC.activeChannels125khz = 64; LMIC.activeChannels125khz = 64;
LMIC.activeChannels500khz = 8; LMIC.activeChannels500khz = 8;
// choose a random channel.
LMIC.txChnl = 0xFF;
} }
// verify that a given setting is permitted // 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 || channel map appllies to 500kHz (ch 64..71) and in addition
|| all channels 0..63 are turned off or on. MCMC_LADR_CHP_BANK || all channels 0..63 are turned off or on. MCMC_LADR_CHP_BANK
|| is also special, in that it enables subbands. || 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) { if (chpage < MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL) {
// operate on channels 0..15, 16..31, 32..47, 48..63, 64..71 // 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; return 1;
} }
} else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK) { } else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK) {
if (chmap == 0 || (chmap & 0xFF00) != 0) { if ((chmap & 0xFF00) != 0) {
// no bits set, or reserved bitsset , fail. // Reserved bits set, fail.
return 0; return 0;
} }
} else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON || } else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON ||
chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF) { chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF) {
u1_t const en125 = chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON; //
// if disabling all 125kHz chans, you might think we must have
// if disabling all 125kHz chans, must have at least one 500kHz chan // at least one 500kHz chan; but that's a local conclusion.
// don't allow reserved bits to be set in chmap. // Some network servers will disable all (including 500kHz)
if ((! en125 && chmap == 0) || (chmap & 0xFF00) != 0) // 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; return 0;
} else { } else {
return 0; return 0;
@ -186,7 +195,7 @@ bit_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) {
} }
} }
LMICOS_logEventUint32("LMICuslike_mapChannels", (LMIC.activeChannels125khz << 16u)|(LMIC.activeChannels500khz << 0u)); LMICOS_logEventUint32("LMICuslike_mapChannels", ((u4_t)LMIC.activeChannels125khz << 16u)|(LMIC.activeChannels500khz << 0u));
return (LMIC.activeChannels125khz > 0) || (LMIC.activeChannels500khz > 0); return (LMIC.activeChannels125khz > 0) || (LMIC.activeChannels500khz > 0);
} }
@ -230,11 +239,10 @@ bit_t LMICuslike_isDataRateFeasible(dr_t dr) {
#if !defined(DISABLE_JOIN) #if !defined(DISABLE_JOIN)
void LMICuslike_initJoinLoop(void) { void LMICuslike_initJoinLoop(void) {
// set an initial condition so that setNextChannel()'s preconds are met // 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 // 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 // the join. The join logic uses the current txChnl,
// as the first join channel. The join logic uses the current txChnl,
// then changes after the rx window expires; so we need to set a valid // then changes after the rx window expires; so we need to set a valid
// starting point. // starting point.
setNextChannel(0, 64, LMIC.activeChannels125khz); setNextChannel(0, 64, LMIC.activeChannels125khz);
@ -277,10 +285,13 @@ ostime_t LMICuslike_nextJoinState(void) {
if (LMIC.datarate != LMICuslike_getFirst500kHzDR()) { if (LMIC.datarate != LMICuslike_getFirst500kHzDR()) {
// assume that 500 kHz equiv of last 125 kHz channel // assume that 500 kHz equiv of last 125 kHz channel
// is also enabled, and use it next. // is also enabled, and use it next.
LMIC.txChnl_125kHz = LMIC.txChnl;
LMIC.txChnl = 64 + (LMIC.txChnl >> 3); LMIC.txChnl = 64 + (LMIC.txChnl >> 3);
LMICcore_setDrJoin(DRCHG_SET, LMICuslike_getFirst500kHzDR()); LMICcore_setDrJoin(DRCHG_SET, LMICuslike_getFirst500kHzDR());
} }
else { else {
// restore invariant
LMIC.txChnl = LMIC.txChnl_125kHz;
setNextChannel(0, 64, LMIC.activeChannels125khz); setNextChannel(0, 64, LMIC.activeChannels125khz);
// TODO(tmm@mcci.com) parameterize // TODO(tmm@mcci.com) parameterize
@ -290,6 +301,7 @@ ostime_t LMICuslike_nextJoinState(void) {
} }
LMICcore_setDrJoin(DRCHG_SET, dr); LMICcore_setDrJoin(DRCHG_SET, dr);
} }
// tell the main loop that we've already selected a channel.
LMIC.opmode &= ~OP_NEXTCHNL; LMIC.opmode &= ~OP_NEXTCHNL;
// TODO(tmm@mcci.com): change delay to (0:1) secs + a known t0, but randomized; // 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 #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) { void LMICuslike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) {
os_copyMem( os_copyMem(
pStateBuffer->channelMap, pStateBuffer->channelMap,

View File

@ -63,8 +63,14 @@ LMICuslike_isValidBeacon1(const uint8_t *d) {
// provide a default LMICbandplan_joinAcceptChannelClear() // provide a default LMICbandplan_joinAcceptChannelClear()
#define LMICbandplan_joinAcceptChannelClear() do { } while (0) #define LMICbandplan_joinAcceptChannelClear() do { } while (0)
// no CFList on joins for US-like plans /// \brief there's a CFList on joins for US-like plans
#define LMICbandplan_hasJoinCFlist() (0) #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() \ #define LMICbandplan_advanceBeaconChannel() \
do { LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; } while (0) do { LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; } while (0)

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2014-2016 IBM Corporation. * Copyright (c) 2014-2016 IBM Corporation.
* Copyritght (c) 2017 MCCI Corporation. * Copyright (c) 2017-2021 MCCI Corporation.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -44,6 +44,7 @@ typedef u1_t cr_t;
typedef u1_t sf_t; typedef u1_t sf_t;
typedef u1_t bw_t; typedef u1_t bw_t;
typedef u1_t dr_t; typedef u1_t dr_t;
typedef u2_t rxsyms_t;
// Radio parameter set (encodes SF/BW/CR/IH/NOCRC) // Radio parameter set (encodes SF/BW/CR/IH/NOCRC)
// 2..0: Spreading factor // 2..0: Spreading factor
@ -59,7 +60,7 @@ enum { ILLEGAL_RPS = 0xFF };
// Global maximum frame length // Global maximum frame length
enum { STD_PREAMBLE_LEN = 8 }; 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_DEVNONCE = 2 };
enum { LEN_ARTNONCE = 3 }; enum { LEN_ARTNONCE = 3 };
enum { LEN_NETID = 3 }; enum { LEN_NETID = 3 };
@ -226,28 +227,28 @@ enum _dr_configured_t {
}; };
# endif // LMIC_DR_LEGACY # endif // LMIC_DR_LEGACY
#elif defined(CFG_au921) // ========================================= #elif defined(CFG_au915) // =========================================
#include "lorabase_au921.h" #include "lorabase_au915.h"
// per 2.5.3: must be implemented // per 2.5.3: must be implemented
#define LMIC_ENABLE_TxParamSetupReq 1 #define LMIC_ENABLE_TxParamSetupReq 1
enum { DR_DFLTMIN = AU921_DR_SF7 }; // DR5 enum { DR_DFLTMIN = AU915_DR_SF7 }; // DR5
// DR_PAGE is a debugging parameter; it must be defined but it has no use in arduino-lmic // DR_PAGE is a debugging parameter; it must be defined but it has no use in arduino-lmic
enum { DR_PAGE = DR_PAGE_AU921 }; enum { DR_PAGE = DR_PAGE_AU915 };
//enum { CHNL_PING = 0 }; // used only for default init of state (follows beacon - rotating) //enum { CHNL_PING = 0 }; // used only for default init of state (follows beacon - rotating)
enum { FREQ_PING = AU921_500kHz_DNFBASE + 0*AU921_500kHz_DNFSTEP }; // default ping freq enum { FREQ_PING = AU915_500kHz_DNFBASE + 0*AU915_500kHz_DNFSTEP }; // default ping freq
enum { DR_PING = AU921_DR_SF10CR }; // default ping DR enum { DR_PING = AU915_DR_SF10CR }; // default ping DR
//enum { CHNL_DNW2 = 0 }; //enum { CHNL_DNW2 = 0 };
enum { FREQ_DNW2 = AU921_500kHz_DNFBASE + 0*AU921_500kHz_DNFSTEP }; enum { FREQ_DNW2 = AU915_500kHz_DNFBASE + 0*AU915_500kHz_DNFSTEP };
enum { DR_DNW2 = AU921_DR_SF12CR }; // DR8 enum { DR_DNW2 = AU915_DR_SF12CR }; // DR8
enum { CHNL_BCN = 0 }; // used only for default init of state (rotating beacon scheme) enum { CHNL_BCN = 0 }; // used only for default init of state (rotating beacon scheme)
enum { DR_BCN = AU921_DR_SF10CR }; enum { DR_BCN = AU915_DR_SF10CR };
enum { AIRTIME_BCN = 72192 }; // micros ... TODO(tmm@mcci.com) check. enum { AIRTIME_BCN = 72192 }; // micros ... TODO(tmm@mcci.com) check.
enum { LMIC_REGION_EIRP = AU921_LMIC_REGION_EIRP }; // region uses EIRP enum { LMIC_REGION_EIRP = AU915_LMIC_REGION_EIRP }; // region uses EIRP
enum { enum {
// Beacon frame format AU DR10/SF10 500kHz // Beacon frame format AU DR10/SF10 500kHz
@ -264,20 +265,20 @@ enum {
# if LMIC_DR_LEGACY # if LMIC_DR_LEGACY
enum _dr_configured_t { enum _dr_configured_t {
DR_SF12 = AU921_DR_SF12, DR_SF12 = AU915_DR_SF12,
DR_SF11 = AU921_DR_SF11, DR_SF11 = AU915_DR_SF11,
DR_SF10 = AU921_DR_SF10, DR_SF10 = AU915_DR_SF10,
DR_SF9 = AU921_DR_SF9, DR_SF9 = AU915_DR_SF9,
DR_SF8 = AU921_DR_SF8, DR_SF8 = AU915_DR_SF8,
DR_SF7 = AU921_DR_SF7, DR_SF7 = AU915_DR_SF7,
DR_SF8C = AU921_DR_SF8C, DR_SF8C = AU915_DR_SF8C,
DR_NONE = AU921_DR_NONE, DR_NONE = AU915_DR_NONE,
DR_SF12CR = AU921_DR_SF12CR, DR_SF12CR = AU915_DR_SF12CR,
DR_SF11CR = AU921_DR_SF11CR, DR_SF11CR = AU915_DR_SF11CR,
DR_SF10CR = AU921_DR_SF10CR, DR_SF10CR = AU915_DR_SF10CR,
DR_SF9CR = AU921_DR_SF9CR, DR_SF9CR = AU915_DR_SF9CR,
DR_SF8CR = AU921_DR_SF8CR, DR_SF8CR = AU915_DR_SF8CR,
DR_SF7CR = AU921_DR_SF7CR DR_SF7CR = AU915_DR_SF7CR
}; };
# endif // LMIC_DR_LEGACY # endif // LMIC_DR_LEGACY
@ -444,6 +445,13 @@ enum {
LEN_JA = 17, LEN_JA = 17,
LEN_JAEXT = 17+16 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 { enum {
// Data frame format // Data frame format
OFF_DAT_HDR = 0, OFF_DAT_HDR = 0,
@ -581,20 +589,20 @@ enum {
// Bit fields byte#3 of MCMD_LinkADRReq payload // Bit fields byte#3 of MCMD_LinkADRReq payload
enum { enum {
MCMD_LinkADRReq_Redundancy_RFU = 0x80, MCMD_LinkADRReq_Redundancy_RFU = 0x80, ///< mask for RFU bit
MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK= 0x70, MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK= 0x70, ///< mask for the channel-mask control field.
MCMD_LinkADRReq_Redundancy_NbTrans_MASK = 0x0F, 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_DIRECT = 0x00, ///< EU-like: direct masking for EU
MCMD_LinkADRReq_ChMaskCntl_EULIKE_ALL_ON = 0x60, // EU: enable everything. 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_500K = 0x40, ///< US-like: 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_SPECIAL = 0x50, ///< US-like: first special for us-like
MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK = 0x50, // special: bits are banks. MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK = 0x50, ///< US-like: special: bits are banks.
MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON = 0x60, // special channel page enable, bits applied to 64..71 MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON = 0x60, ///< US-like: 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_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 // Bit fields byte#0 of MCMD_LinkADRReq payload
@ -637,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))) #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 // 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; } 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 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 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 // BEG: Keep in sync with lorabase.hpp

View File

@ -68,6 +68,7 @@ enum {
AS923_FREQ_MAX = 928000000 AS923_FREQ_MAX = 928000000
}; };
enum { enum {
AS923_JP_TX_EIRP_MAX_DBM = 13, // 13 dBm = 19.95mW < 20mW
AS923_TX_EIRP_MAX_DBM = 16 // 16 dBm AS923_TX_EIRP_MAX_DBM = 16 // 16 dBm
}; };
enum { DR_PAGE_AS923 = 0x10 * (LMIC_REGION_as923 - 1) }; 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 { AS923_LMIC_REGION_EIRP = 1 }; // region uses EIRP
enum { AS923JP_LBT_US = 5000 }; // microseconds of LBT time -- 5000 ==> enum { AS923JP_LBT_US = 5000 }; // microseconds of LBT time -- 5000 ==>
// 5 ms. We use us rather than ms for // 5 ms. We use us rather than ms for
// future 128us support, and just for // future 128us support, and just for
// backward compatibility -- there // backward compatibility -- there
// is code that uses the _US constant, // is code that uses the _US constant,
// and it's awkward to break it. // and it's awkward to break it.
enum { AS923JP_LBT_DB_MAX = -80 }; // maximum channel strength in dB; if TX 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 // 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 // in the future via a config. But this code base needs major changes for

View File

@ -28,8 +28,8 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#ifndef _lorabase_au921_h_ #ifndef _lorabase_au915_h_
#define _lorabase_au921_h_ #define _lorabase_au915_h_
#ifndef _LMIC_CONFIG_PRECONDITIONS_H_ #ifndef _LMIC_CONFIG_PRECONDITIONS_H_
# include "lmic_config_preconditions.h" # include "lmic_config_preconditions.h"
@ -37,53 +37,53 @@
/****************************************************************************\ /****************************************************************************\
| |
| Basic definitions for AS921 (always in scope) | Basic definitions for AU 915 (always in scope)
| |
\****************************************************************************/ \****************************************************************************/
// Frequency plan for AU 921 MHz // Frequency plan for AU 915 MHz
enum _dr_as921_t { enum _dr_au915_t {
AU921_DR_SF12 = 0, AU915_DR_SF12 = 0,
AU921_DR_SF11, AU915_DR_SF11,
AU921_DR_SF10, AU915_DR_SF10,
AU921_DR_SF9, AU915_DR_SF9,
AU921_DR_SF8, AU915_DR_SF8,
AU921_DR_SF7, AU915_DR_SF7,
AU921_DR_SF8C, AU915_DR_SF8C,
AU921_DR_NONE, AU915_DR_NONE,
// Devices behind a router: // Devices behind a router:
AU921_DR_SF12CR = 8, AU915_DR_SF12CR = 8,
AU921_DR_SF11CR, AU915_DR_SF11CR,
AU921_DR_SF10CR, AU915_DR_SF10CR,
AU921_DR_SF9CR, AU915_DR_SF9CR,
AU921_DR_SF8CR, AU915_DR_SF8CR,
AU921_DR_SF7CR AU915_DR_SF7CR
}; };
// Default frequency plan for AU 921MHz // Default frequency plan for AU 915MHz
enum { enum {
AU921_125kHz_UPFBASE = 915200000, AU915_125kHz_UPFBASE = 915200000,
AU921_125kHz_UPFSTEP = 200000, AU915_125kHz_UPFSTEP = 200000,
AU921_500kHz_UPFBASE = 915900000, AU915_500kHz_UPFBASE = 915900000,
AU921_500kHz_UPFSTEP = 1600000, AU915_500kHz_UPFSTEP = 1600000,
AU921_500kHz_DNFBASE = 923300000, AU915_500kHz_DNFBASE = 923300000,
AU921_500kHz_DNFSTEP = 600000 AU915_500kHz_DNFSTEP = 600000
}; };
enum { enum {
AU921_FREQ_MIN = 915000000, AU915_FREQ_MIN = 915000000,
AU921_FREQ_MAX = 928000000 AU915_FREQ_MAX = 928000000
}; };
enum { enum {
AU921_TX_EIRP_MAX_DBM = 30 // 30 dBm AU915_TX_EIRP_MAX_DBM = 30 // 30 dBm
}; };
enum { enum {
// initial value of UplinkDwellTime before TxParamSetupReq received. // initial value of UplinkDwellTime before TxParamSetupReq received.
AU921_INITIAL_TxParam_UplinkDwellTime = 1, AU915_INITIAL_TxParam_UplinkDwellTime = 1,
AU921_UPLINK_DWELL_TIME_osticks = sec2osticks(20), AU915_UPLINK_DWELL_TIME_osticks = sec2osticks(20),
}; };
enum { DR_PAGE_AU921 = 0x10 * (LMIC_REGION_au921 - 1) }; enum { DR_PAGE_AU915 = 0x10 * (LMIC_REGION_au915 - 1) };
enum { AU921_LMIC_REGION_EIRP = 1 }; // region uses EIRP enum { AU915_LMIC_REGION_EIRP = 1 }; // region uses EIRP
#endif /* _lorabase_au921_h_ */ #endif /* _lorabase_au915_h_ */

View File

@ -139,6 +139,8 @@ void os_runloop () {
void os_runloop_once() { void os_runloop_once() {
osjob_t* j = NULL; osjob_t* j = NULL;
hal_processPendingIRQs();
hal_disableIRQs(); hal_disableIRQs();
// check for runnable jobs // check for runnable jobs
if(OS.runnablejobs) { if(OS.runnablejobs) {

View File

@ -119,12 +119,13 @@ void radio_monitor_rssi(ostime_t n, oslmic_radio_rssi_t *pRssi);
//================================================================================ //================================================================================
#ifndef RX_RAMPUP #ifndef RX_RAMPUP_DEFAULT
// RX_RAMPUP specifies the extra time we must allow to set up an RX event due //! \brief RX_RAMPUP_DEFAULT specifies the extra time we must allow to set up an RX event due
// to platform issues. It's specified in units of ostime_t. It must reflect //! to platform issues. It's specified in units of ostime_t. It must reflect
// platform jitter and latency, as well as the speed of the LMIC when running //! platform jitter and latency, as well as the speed of the LMIC when running
// on this plaform. //! on this plaform. It's not used directly; clients call os_getRadioRxRampup(),
#define RX_RAMPUP (us2osticks(2000)) //! which might adaptively vary this based on observed timeouts.
#define RX_RAMPUP_DEFAULT (us2osticks(10000))
#endif #endif
#ifndef TX_RAMPUP #ifndef TX_RAMPUP
@ -132,7 +133,7 @@ void radio_monitor_rssi(ostime_t n, oslmic_radio_rssi_t *pRssi);
// to platform issues. It's specified in units of ostime_t. It must reflect // to platform issues. It's specified in units of ostime_t. It must reflect
// platform jitter and latency, as well as the speed of the LMIC when running // platform jitter and latency, as well as the speed of the LMIC when running
// on this plaform. // on this plaform.
#define TX_RAMPUP (us2osticks(2000)) #define TX_RAMPUP (us2osticks(10000))
#endif #endif
#ifndef OSTICKS_PER_SEC #ifndef OSTICKS_PER_SEC
@ -196,6 +197,9 @@ void os_setTimedCallback (xref2osjob_t job, ostime_t time, osjobcb_t cb);
#ifndef os_clearCallback #ifndef os_clearCallback
void os_clearCallback (xref2osjob_t job); void os_clearCallback (xref2osjob_t job);
#endif #endif
#ifndef os_getRadioRxRampup
ostime_t os_getRadioRxRampup (void);
#endif
#ifndef os_getTime #ifndef os_getTime
ostime_t os_getTime (void); ostime_t os_getTime (void);
#endif #endif
@ -234,7 +238,7 @@ void os_wmsbf4 (xref2u1_t buf, u4_t value);
u2_t os_rlsbf2 (xref2cu1_t buf); u2_t os_rlsbf2 (xref2cu1_t buf);
#endif #endif
#ifndef os_wlsbf2 #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); void os_wlsbf2 (xref2u1_t buf, u2_t value);
#endif #endif

View File

@ -26,6 +26,8 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
//! \file
#define LMIC_DR_LEGACY 0 #define LMIC_DR_LEGACY 0
#include "lmic.h" #include "lmic.h"
@ -249,6 +251,14 @@
#define SX1276_RSSI_ADJUST_LF -164 // add to rssi value to get dB (LF) #define SX1276_RSSI_ADJUST_LF -164 // add to rssi value to get dB (LF)
#define SX1276_RSSI_ADJUST_HF -157 // add to rssi value to get dB (HF) #define SX1276_RSSI_ADJUST_HF -157 // add to rssi value to get dB (HF)
#ifdef CFG_sx1276_radio
# define SX127X_RSSI_ADJUST_LF SX1276_RSSI_ADJUST_LF
# define SX127X_RSSI_ADJUST_HF SX1276_RSSI_ADJUST_HF
#else
# define SX127X_RSSI_ADJUST_LF SX1272_RSSI_ADJUST
# define SX127X_RSSI_ADJUST_HF SX1272_RSSI_ADJUST
#endif
// per datasheet 2.5.2 (but note that we ought to ask Semtech to confirm, because // per datasheet 2.5.2 (but note that we ought to ask Semtech to confirm, because
// datasheet is unclear). // datasheet is unclear).
#define SX127X_RX_POWER_UP us2osticks(500) // delay this long to let the receiver power up. #define SX127X_RX_POWER_UP us2osticks(500) // delay this long to let the receiver power up.
@ -372,10 +382,12 @@ static void writeReg (u1_t addr, u1_t data ) {
hal_spi_write(addr | 0x80, &data, 1); 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) { static u1_t readReg (u1_t addr) {
u1_t buf[1]; hal_spi_read(addr & 0x7f, reg_value_buf, 1);
hal_spi_read(addr & 0x7f, buf, 1); return reg_value_buf[0];
return buf[0];
} }
static void writeBuf (u1_t addr, xref2u1_t buf, u1_t len) { static void writeBuf (u1_t addr, xref2u1_t buf, u1_t len) {
@ -459,10 +471,15 @@ static void configLoraModem () {
// set ModemConfig1 // set ModemConfig1
writeReg(LORARegModemConfig1, mc1); writeReg(LORARegModemConfig1, mc1);
mc2 = (SX1272_MC2_SF7 + ((sf-1)<<4)); mc2 = (SX1272_MC2_SF7 + ((sf-1)<<4) + ((LMIC.rxsyms >> 8) & 0x3) );
if (getNocrc(LMIC.rps) == 0) { if (getNocrc(LMIC.rps) == 0) {
mc2 |= SX1276_MC2_RX_PAYLOAD_CRCON; mc2 |= SX1276_MC2_RX_PAYLOAD_CRCON;
} }
#if CFG_TxContinuousMode
// Only for testing
// set ModemConfig2 (sf, TxContinuousMode=1, AgcAutoOn=1 SymbTimeoutHi=00)
mc2 |= 0x8;
#endif
writeReg(LORARegModemConfig2, mc2); writeReg(LORARegModemConfig2, mc2);
mc3 = SX1276_MC3_AGCAUTO; mc3 = SX1276_MC3_AGCAUTO;
@ -519,15 +536,18 @@ static void configLoraModem () {
// set ModemConfig1 // set ModemConfig1
writeReg(LORARegModemConfig1, mc1); writeReg(LORARegModemConfig1, mc1);
// set ModemConfig2 (sf, AgcAutoOn=1 SymbTimeoutHi=00) // set ModemConfig2 (sf, AgcAutoOn=1 SymbTimeoutHi)
writeReg(LORARegModemConfig2, (SX1272_MC2_SF7 + ((sf-1)<<4)) | 0x04); u1_t mc2;
mc2 = (SX1272_MC2_SF7 + ((sf-1)<<4)) | 0x04 | ((LMIC.rxsyms >> 8) & 0x3);
#if CFG_TxContinuousMode #if CFG_TxContinuousMode
// Only for testing // Only for testing
// set ModemConfig2 (sf, TxContinuousMode=1, AgcAutoOn=1 SymbTimeoutHi=00) // set ModemConfig2 (sf, TxContinuousMode=1, AgcAutoOn=1 SymbTimeoutHi=00)
writeReg(LORARegModemConfig2, (SX1272_MC2_SF7 + ((sf-1)<<4)) | 0x06); mc2 |= 0x8;
#endif #endif
writeReg(LORARegModemConfig2, mc2);
#else #else
#error Missing CFG_sx1272_radio/CFG_sx1276_radio #error Missing CFG_sx1272_radio/CFG_sx1276_radio
#endif /* CFG_sx1272_radio */ #endif /* CFG_sx1272_radio */
@ -650,9 +670,9 @@ static void configPower () {
if (req_pw >= 20) { if (req_pw >= 20) {
policy = LMICHAL_radio_tx_power_policy_20dBm; policy = LMICHAL_radio_tx_power_policy_20dBm;
eff_pw = 20; eff_pw = 20;
} else if (eff_pw >= 14) { } else if (req_pw >= 14) {
policy = LMICHAL_radio_tx_power_policy_paboost; policy = LMICHAL_radio_tx_power_policy_paboost;
if (eff_pw > 17) { if (req_pw > 17) {
eff_pw = 17; eff_pw = 17;
} else { } else {
eff_pw = req_pw; eff_pw = req_pw;
@ -765,6 +785,14 @@ static void txfsk () {
hal_pin_rxtx(1); hal_pin_rxtx(1);
// now we actually start the transmission // now we actually start the transmission
if (LMIC.txend) {
u4_t nLate = hal_waitUntil(LMIC.txend); // busy wait until exact tx time
if (nLate > 0) {
LMIC.radio.txlate_ticks += nLate;
++LMIC.radio.txlate_count;
}
}
LMICOS_logEventUint32("+Tx FSK", LMIC.dataLen);
opmode(OPMODE_TX); opmode(OPMODE_TX);
} }
@ -809,6 +837,14 @@ static void txlora () {
hal_pin_rxtx(1); hal_pin_rxtx(1);
// now we actually start the transmission // now we actually start the transmission
if (LMIC.txend) {
u4_t nLate = hal_waitUntil(LMIC.txend); // busy wait until exact tx time
if (nLate) {
LMIC.radio.txlate_ticks += nLate;
++LMIC.radio.txlate_count;
}
}
LMICOS_logEventUint32("+Tx LoRa", LMIC.dataLen);
opmode(OPMODE_TX); opmode(OPMODE_TX);
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
@ -870,6 +906,18 @@ static CONST_TABLE(u1_t, rxlorairqmask)[] = {
[RXMODE_RSSI] = 0x00, [RXMODE_RSSI] = 0x00,
}; };
//! \brief handle late RX events.
//! \param nLate is the number of `ostime_t` ticks that the event was late.
//! \details If nLate is non-zero, increment the count of events, totalize
//! the number of ticks late, and (if implemented) adjust the estimate of
//! what would be best to return from `os_getRadioRxRampup()`.
static void rxlate (u4_t nLate) {
if (nLate) {
LMIC.radio.rxlate_ticks += nLate;
++LMIC.radio.rxlate_count;
}
}
// start LoRa receiver (time=LMIC.rxtime, timeout=LMIC.rxsyms, result=LMIC.frame[LMIC.dataLen]) // start LoRa receiver (time=LMIC.rxtime, timeout=LMIC.rxsyms, result=LMIC.frame[LMIC.dataLen])
static void rxlora (u1_t rxmode) { static void rxlora (u1_t rxmode) {
// select LoRa modem (from sleep mode) // select LoRa modem (from sleep mode)
@ -914,7 +962,7 @@ static void rxlora (u1_t rxmode) {
} }
// set symbol timeout (for single rx) // set symbol timeout (for single rx)
writeReg(LORARegSymbTimeoutLsb, LMIC.rxsyms); writeReg(LORARegSymbTimeoutLsb, (uint8_t) LMIC.rxsyms);
// set sync word // set sync word
writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE); writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE);
@ -933,13 +981,16 @@ static void rxlora (u1_t rxmode) {
// now instruct the radio to receive // now instruct the radio to receive
if (rxmode == RXMODE_SINGLE) { // single rx if (rxmode == RXMODE_SINGLE) { // single rx
hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time u4_t nLate = hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time
opmode(OPMODE_RX_SINGLE); opmode(OPMODE_RX_SINGLE);
LMICOS_logEventUint32("+Rx LoRa Single", nLate);
rxlate(nLate);
#if LMIC_DEBUG_LEVEL > 0 #if LMIC_DEBUG_LEVEL > 0
ostime_t now = os_getTime(); ostime_t now = os_getTime();
LMIC_DEBUG_PRINTF("start single rx: now-rxtime: %"LMIC_PRId_ostime_t"\n", now - LMIC.rxtime); LMIC_DEBUG_PRINTF("start single rx: now-rxtime: %"LMIC_PRId_ostime_t"\n", now - LMIC.rxtime);
#endif #endif
} else { // continous rx (scan or rssi) } else { // continous rx (scan or rssi)
LMICOS_logEventUint32("+Rx LoRa Continuous", rxmode);
opmode(OPMODE_RX); opmode(OPMODE_RX);
} }
@ -963,8 +1014,14 @@ static void rxlora (u1_t rxmode) {
} }
static void rxfsk (u1_t rxmode) { static void rxfsk (u1_t rxmode) {
// only single rx (no continuous scanning, no noise sampling) // only single or continuous rx (no noise sampling)
ASSERT( rxmode == RXMODE_SINGLE ); if (rxmode == RXMODE_SCAN) {
// indicate no bytes received.
LMIC.dataLen = 0;
// complete the request by scheduling the job.
os_setCallback(&LMIC.osjob, LMIC.osjob.func);
}
// select FSK modem (from sleep mode) // select FSK modem (from sleep mode)
//writeReg(RegOpMode, 0x00); // (not LoRa) //writeReg(RegOpMode, 0x00); // (not LoRa)
opmodeFSK(); opmodeFSK();
@ -995,8 +1052,15 @@ static void rxfsk (u1_t rxmode) {
hal_pin_rxtx(0); hal_pin_rxtx(0);
// now instruct the radio to receive // now instruct the radio to receive
hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time if (rxmode == RXMODE_SINGLE) {
opmode(OPMODE_RX); // no single rx mode available in FSK u4_t nLate = hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time
opmode(OPMODE_RX); // no single rx mode available in FSK
LMICOS_logEventUint32("+Rx FSK", nLate);
rxlate(nLate);
} else {
LMICOS_logEvent("+Rx FSK Continuous");
opmode(OPMODE_RX);
}
} }
static void startrx (u1_t rxmode) { static void startrx (u1_t rxmode) {
@ -1010,10 +1074,26 @@ static void startrx (u1_t rxmode) {
// or timed out, and the corresponding IRQ will inform us about completion. // or timed out, and the corresponding IRQ will inform us about completion.
} }
// get random seed from wideband noise rssi //! \brief Initialize radio at system startup.
//!
//! \details This procedure is called during initialization by the `os_init()`
//! routine. It does a hardware reset of the radio, checks the version and confirms
//! that we're operating a suitable chip, and gets a random seed from wideband
//! noise rssi. It then puts the radio to sleep.
//!
//! \result True if successful, false if it doesn't look like the right radio is attached.
//!
//! \pre
//! Preconditions must be observed, or you'll get hangs during initialization.
//!
//! - The `hal_pin_..()` functions must be ready for use.
//! - The `hal_waitUntl()` function must be ready for use. This may mean that interrupts
//! are enabled.
//! - The `hal_spi_..()` functions must be ready for use.
//!
//! Generally, all these are satisfied by a call to `hal_init_with_pinmap()`.
//!
int radio_init () { int radio_init () {
hal_disableIRQs();
requestModuleActive(1); requestModuleActive(1);
// manually reset radio // manually reset radio
@ -1076,7 +1156,6 @@ int radio_init () {
opmode(OPMODE_SLEEP); opmode(OPMODE_SLEEP);
hal_enableIRQs();
return 1; return 1;
} }
@ -1095,19 +1174,23 @@ u1_t radio_rand1 () {
} }
u1_t radio_rssi () { u1_t radio_rssi () {
hal_disableIRQs();
u1_t r = readReg(LORARegRssiValue); u1_t r = readReg(LORARegRssiValue);
hal_enableIRQs();
return r; return r;
} }
// monitor rssi for specified number of ostime_t ticks, and return statistics /// \brief get the current RSSI on the current channel.
// This puts the radio into RX continuous mode, waits long enough for the ///
// oscillators to start and the PLL to lock, and then measures for the specified /// monitor rssi for specified number of ostime_t ticks, and return statistics
// period of time. The radio is then returned to idle. /// This puts the radio into RX continuous mode, waits long enough for the
// /// oscillators to start and the PLL to lock, and then measures for the specified
// RSSI returned is expressed in units of dB, and is offset according to the /// period of time. The radio is then returned to idle.
// current radio setting per section 5.5.5 of Semtech 1276 datasheet. ///
/// RSSI returned is expressed in units of dB, and is offset according to the
/// current radio setting per section 5.5.5 of Semtech 1276 datasheet.
///
/// \param nTicks How long to monitor
/// \param pRssi pointer to structure to fill in with RSSI data.
///
void radio_monitor_rssi(ostime_t nTicks, oslmic_radio_rssi_t *pRssi) { void radio_monitor_rssi(ostime_t nTicks, oslmic_radio_rssi_t *pRssi) {
uint8_t rssiMax, rssiMin; uint8_t rssiMax, rssiMin;
uint16_t rssiSum; uint16_t rssiSum;
@ -1145,13 +1228,8 @@ void radio_monitor_rssi(ostime_t nTicks, oslmic_radio_rssi_t *pRssi) {
tBegin = os_getTime(); tBegin = os_getTime();
rssiMax = 0; rssiMax = 0;
/* XXX(tanupoo) /* Per bug report from tanupoo, it's critical that interrupts be enabled
* In this loop, micros() in os_getTime() returns a past time sometimes. * in the loop below so that `os_getTime()` always advances.
* At least, it happens on Dragino LoRa Mini.
* the return value of micros() looks not to be stable in IRQ disabled.
* Once it happens, this loop never exit infinitely.
* In order to prevent it, it enables IRQ before calling os_getTime(),
* disable IRQ again after that.
*/ */
do { do {
ostime_t now; ostime_t now;
@ -1164,10 +1242,7 @@ void radio_monitor_rssi(ostime_t nTicks, oslmic_radio_rssi_t *pRssi) {
rssiMin = rssiNow; rssiMin = rssiNow;
rssiSum += rssiNow; rssiSum += rssiNow;
++rssiN; ++rssiN;
// TODO(tmm@mcci.com) move this to os_getTime().
hal_enableIRQs();
now = os_getTime(); now = os_getTime();
hal_disableIRQs();
notDone = now - (tBegin + nTicks) < 0; notDone = now - (tBegin + nTicks) < 0;
} while (notDone); } while (notDone);
@ -1210,6 +1285,7 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
writeReg(LORARegFifoAddrPtr, 0x00); writeReg(LORARegFifoAddrPtr, 0x00);
u1_t s = readReg(RegOpMode); u1_t s = readReg(RegOpMode);
u1_t c = readReg(LORARegModemConfig2); u1_t c = readReg(LORARegModemConfig2);
LMICOS_logEventUint32("+Tx LoRa Continuous", (r << 8) + c);
opmode(OPMODE_TX); opmode(OPMODE_TX);
return; return;
#else /* ! CFG_TxContinuousMode */ #else /* ! CFG_TxContinuousMode */
@ -1240,9 +1316,22 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
readBuf(RegFifo, LMIC.frame, LMIC.dataLen); readBuf(RegFifo, LMIC.frame, LMIC.dataLen);
// read rx quality parameters // read rx quality parameters
LMIC.snr = readReg(LORARegPktSnrValue); // SNR [dB] * 4 LMIC.snr = readReg(LORARegPktSnrValue); // SNR [dB] * 4
LMIC.rssi = readReg(LORARegPktRssiValue); u1_t const rRssi = readReg(LORARegPktRssiValue);
LMIC_X_DEBUG_PRINTF("RX snr=%u rssi=%d\n", LMIC.snr/4, SX127X_RSSI_ADJUST_HF + LMIC.rssi); s2_t rssi = rRssi;
LMIC.rssi = LMIC.rssi - 125 + 64; // RSSI [dBm] (-196...+63) if (LMIC.freq > SX127X_FREQ_LF_MAX)
rssi += SX127X_RSSI_ADJUST_HF;
else
rssi += SX127X_RSSI_ADJUST_LF;
if (LMIC.snr < 0)
rssi = rssi - (-LMIC.snr >> 2);
else if (rssi > -100) {
// correct nonlinearity -- this is the same as multiplying rRssi * 16/15 initially.
rssi += (rRssi / 15);
}
LMIC_X_DEBUG_PRINTF("RX snr=%u rssi=%d\n", LMIC.snr/4, rssi);
// ugh compatibility requires a biased range. RSSI
LMIC.rssi = (s1_t) (RSSI_OFF + (rssi < -196 ? -196 : rssi > 63 ? 63 : rssi)); // RSSI [dBm] (-196...+63)
} else if( flags & IRQ_LORA_RXTOUT_MASK ) { } else if( flags & IRQ_LORA_RXTOUT_MASK ) {
// indicate timeout // indicate timeout
LMIC.dataLen = 0; LMIC.dataLen = 0;
@ -1260,7 +1349,7 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
u1_t flags1 = readReg(FSKRegIrqFlags1); u1_t flags1 = readReg(FSKRegIrqFlags1);
u1_t flags2 = readReg(FSKRegIrqFlags2); u1_t flags2 = readReg(FSKRegIrqFlags2);
LMICOS_logEventUint32("radio_irq_handler_v2: FSK", (flags2 << UINT32_C(8)) | flags1); LMICOS_logEventUint32("*radio_irq_handler_v2: FSK", ((u2_t)flags2 << 8) | flags1);
if( flags2 & IRQ_FSK2_PACKETSENT_MASK ) { if( flags2 & IRQ_FSK2_PACKETSENT_MASK ) {
// save exact tx time // save exact tx time
@ -1273,8 +1362,9 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
// now read the FIFO // now read the FIFO
readBuf(RegFifo, LMIC.frame, LMIC.dataLen); readBuf(RegFifo, LMIC.frame, LMIC.dataLen);
// read rx quality parameters // read rx quality parameters
LMIC.snr = 0; // determine snr LMIC.snr = 0; // SX127x doesn't give SNR for FSK.
LMIC.rssi = 0; // determine rssi LMIC.rssi = -64 + RSSI_OFF; // SX127x doesn't give packet RSSI for FSK,
// so substitute a dummy value.
} else if( flags1 & IRQ_FSK1_TIMEOUT_MASK ) { } else if( flags1 & IRQ_FSK1_TIMEOUT_MASK ) {
// indicate timeout // indicate timeout
LMIC.dataLen = 0; LMIC.dataLen = 0;
@ -1294,8 +1384,32 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
#endif /* ! CFG_TxContinuousMode */ #endif /* ! CFG_TxContinuousMode */
} }
/*!
\brief Initiate a radio operation.
\param mode Selects the operation to be performed.
The requested radio operation is initiated. Some operations complete
immediately; others require hardware to do work, and don't complete until
an interrupt occurs. In that case, `LMIC.osjob` is scheduled. Because the
interrupt may occur right away, it's important that the caller initialize
`LMIC.osjob` before calling this routine.
- `RADIO_RST` causes the radio to be put to sleep. No interrupt follows;
when control returns, the radio is ready for the next operation.
- `RADIO_TX` and `RADIO_TX_AT` launch the transmission of a frame. An interrupt will
occur, which will cause `LMIC.osjob` to be scheduled with its current
function.
- `RADIO_RX` and `RADIO_RX_ON` launch either single or continuous receives.
An interrupt will occur when a packet is recieved or the receive times out,
which will cause `LMIC.osjob` to be scheduled with its current function.
*/
void os_radio (u1_t mode) { void os_radio (u1_t mode) {
hal_disableIRQs();
switch (mode) { switch (mode) {
case RADIO_RST: case RADIO_RST:
// put radio to sleep // put radio to sleep
@ -1304,9 +1418,16 @@ void os_radio (u1_t mode) {
case RADIO_TX: case RADIO_TX:
// transmit frame now // transmit frame now
LMIC.txend = 0;
starttx(); // buf=LMIC.frame, len=LMIC.dataLen starttx(); // buf=LMIC.frame, len=LMIC.dataLen
break; break;
case RADIO_TX_AT:
if (LMIC.txend == 0)
LMIC.txend = 1;
starttx();
break;
case RADIO_RX: case RADIO_RX:
// receive frame now (exactly at rxtime) // receive frame now (exactly at rxtime)
startrx(RXMODE_SINGLE); // buf=LMIC.frame, time=LMIC.rxtime, timeout=LMIC.rxsyms startrx(RXMODE_SINGLE); // buf=LMIC.frame, time=LMIC.rxtime, timeout=LMIC.rxsyms
@ -1317,5 +1438,8 @@ void os_radio (u1_t mode) {
startrx(RXMODE_SCAN); // buf=LMIC.frame startrx(RXMODE_SCAN); // buf=LMIC.frame
break; break;
} }
hal_enableIRQs(); }
ostime_t os_getRadioRxRampup (void) {
return RX_RAMPUP_DEFAULT;
} }

562
src/ttn.c Normal file
View File

@ -0,0 +1,562 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* High-level C API for ttn-esp32.
*******************************************************************************/
#include "ttn.h"
#include "esp_event.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "hal/hal_esp32.h"
#include "lmic/lmic.h"
#include "ttn_logging.h"
#include "ttn_provisioning.h"
#include "ttn_nvs.h"
#include "ttn_rtc.h"
#define TAG "ttn"
#define DEFAULT_MAX_TX_POWER -1000
/**
* @brief Reason the user code is waiting
*/
typedef enum
{
TTN_WAITING_NONE,
TTN_WAITING_FOR_JOIN,
TTN_WAITING_FOR_TRANSMISSION
} ttn_waiting_reason_t;
/**
* @brief Event type
*/
typedef enum
{
TTN_EVENT_NONE,
TTN_EVNT_JOIN_COMPLETED,
TTN_EVENT_JOIN_FAILED,
TTN_EVENT_MESSAGE_RECEIVED,
TTN_EVENT_TRANSMISSION_COMPLETED,
TTN_EVENT_TRANSMISSION_FAILED
} ttn_event_t;
/**
* @brief Event message sent from LMIC task to waiting client task
*/
typedef struct
{
ttn_event_t event;
uint8_t port;
const uint8_t *message;
size_t message_size;
} ttn_lmic_event_t;
static bool is_started;
static bool has_joined;
static QueueHandle_t lmic_event_queue;
static ttn_message_cb message_callback;
static ttn_waiting_reason_t waiting_reason;
static ttn_rf_settings_t last_rf_settings[4];
static ttn_rx_tx_window_t current_rx_tx_window;
static int subband = 2;
static ttn_data_rate_t join_data_rate = TTN_DR_JOIN_DEFAULT;
static int max_tx_power = DEFAULT_MAX_TX_POWER;
static void start(void);
static void stop(void);
static bool join_core(void);
static void config_rf_params(void);
static void event_callback(void *user_data, ev_t event);
static void message_received_callback(void *user_data, uint8_t port, const uint8_t *message, size_t message_size);
static void message_transmitted_callback(void *user_data, int success);
static void save_rf_settings(ttn_rf_settings_t *rf_settings);
static void clear_rf_settings(ttn_rf_settings_t *rf_settings);
void ttn_init(void)
{
#if defined(TTN_IS_DISABLED)
ESP_LOGE(TAG, "TTN is disabled. Configure a frequency plan using 'make menuconfig'");
ASSERT(0);
#endif
message_callback = NULL;
hal_esp32_init_critical_section();
}
void ttn_configure_pins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1)
{
hal_esp32_configure_pins(spi_host, nss, rxtx, rst, dio0, dio1);
#if LMIC_ENABLE_event_logging
ttn_log_init();
#endif
}
void ttn_set_subband(int band)
{
subband = band;
}
void start(void)
{
if (is_started)
return;
LMIC_registerEventCb(event_callback, NULL);
LMIC_registerRxMessageCb(message_received_callback, NULL);
os_init_ex(NULL);
hal_esp32_enter_critical_section();
LMIC_reset();
LMIC_setClockError(MAX_CLOCK_ERROR * 4 / 100);
waiting_reason = TTN_WAITING_NONE;
hal_esp32_leave_critical_section();
lmic_event_queue = xQueueCreate(4, sizeof(ttn_lmic_event_t));
ASSERT(lmic_event_queue != NULL);
hal_esp32_start_lmic_task();
is_started = true;
}
void stop(void)
{
if (!is_started)
return;
hal_esp32_enter_critical_section();
LMIC_shutdown();
hal_esp32_stop_lmic_task();
waiting_reason = TTN_WAITING_NONE;
hal_esp32_leave_critical_section();
}
void ttn_shutdown(void)
{
stop();
}
bool ttn_provision(const char *dev_eui, const char *app_eui, const char *app_key)
{
if (!ttn_provisioning_decode_keys(dev_eui, app_eui, app_key))
return false;
return ttn_provisioning_save_keys();
}
bool ttn_provision_transiently(const char *dev_eui, const char *app_eui, const char *app_key)
{
return ttn_provisioning_decode_keys(dev_eui, app_eui, app_key);
}
bool ttn_provision_with_mac(const char *app_eui, const char *app_key)
{
if (!ttn_provisioning_from_mac(app_eui, app_key))
return false;
return ttn_provisioning_save_keys();
}
void ttn_start_provisioning_task(void)
{
#if defined(TTN_HAS_AT_COMMANDS)
ttn_provisioning_start_task();
#else
ESP_LOGE(TAG, "AT commands are disabled. Change the configuration using 'make menuconfig'");
ASSERT(0);
esp_restart();
#endif
}
void ttn_wait_for_provisioning(void)
{
#if defined(TTN_HAS_AT_COMMANDS)
if (ttn_is_provisioned())
{
ESP_LOGI(TAG, "Device is already provisioned");
return;
}
while (!ttn_provisioning_have_keys())
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "Device successfully provisioned");
#else
ESP_LOGE(TAG, "AT commands are disabled. Change the configuration using 'make menuconfig'");
ASSERT(0);
esp_restart();
#endif
}
bool ttn_join_with_keys(const char *dev_eui, const char *app_eui, const char *app_key)
{
if (!ttn_provisioning_decode_keys(dev_eui, app_eui, app_key))
return false;
return join_core();
}
bool ttn_join(void)
{
if (!ttn_provisioning_have_keys())
{
if (!ttn_provisioning_restore_keys(false))
return false;
}
return join_core();
}
bool ttn_resume_after_deep_sleep(void)
{
if (!ttn_provisioning_have_keys())
{
if (!ttn_provisioning_restore_keys(false))
return false;
}
if (!ttn_provisioning_have_keys())
{
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
return false;
}
start();
if (!ttn_rtc_restore())
return false;
has_joined = true;
return true;
}
bool ttn_resume_after_power_off(int off_duration)
{
if (!ttn_provisioning_have_keys())
{
if (!ttn_provisioning_restore_keys(false))
return false;
}
if (!ttn_provisioning_have_keys())
{
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
return false;
}
start();
if (!ttn_nvs_restore(off_duration))
return false;
has_joined = true;
return true;
}
// Called immediately before sending join request message
void config_rf_params(void)
{
#if defined(CFG_us915) || defined(CFG_au915)
if (subband != 0)
LMIC_selectSubBand(subband - 1);
#endif
if (join_data_rate != TTN_DR_JOIN_DEFAULT || max_tx_power != DEFAULT_MAX_TX_POWER)
{
dr_t dr = join_data_rate == TTN_DR_JOIN_DEFAULT ? LMIC.datarate : (dr_t)join_data_rate;
s1_t txpow = max_tx_power == DEFAULT_MAX_TX_POWER ? LMIC.adrTxPow : max_tx_power;
LMIC_setDrTxpow(dr, txpow);
}
}
bool join_core(void)
{
if (!ttn_provisioning_have_keys())
{
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
return false;
}
start();
has_joined = true;
hal_esp32_enter_critical_section();
xQueueReset(lmic_event_queue);
waiting_reason = TTN_WAITING_FOR_JOIN;
LMIC_startJoining();
config_rf_params();
hal_esp32_wake_up();
hal_esp32_leave_critical_section();
ttn_lmic_event_t event;
xQueueReceive(lmic_event_queue, &event, portMAX_DELAY);
has_joined = event.event == TTN_EVNT_JOIN_COMPLETED;
return has_joined;
}
ttn_response_code_t ttn_transmit_message(const uint8_t *payload, size_t length, ttn_port_t port, bool confirm)
{
hal_esp32_enter_critical_section();
if (waiting_reason != TTN_WAITING_NONE || (LMIC.opmode & OP_TXRXPEND) != 0)
{
hal_esp32_leave_critical_section();
return TTN_ERROR_TRANSMISSION_FAILED;
}
waiting_reason = TTN_WAITING_FOR_TRANSMISSION;
LMIC.client.txMessageCb = message_transmitted_callback;
LMIC.client.txMessageUserData = NULL;
LMIC_setTxData2(port, (xref2u1_t)payload, length, confirm);
hal_esp32_wake_up();
hal_esp32_leave_critical_section();
while (true)
{
ttn_lmic_event_t result;
xQueueReceive(lmic_event_queue, &result, portMAX_DELAY);
switch (result.event)
{
case TTN_EVENT_MESSAGE_RECEIVED:
if (message_callback != NULL)
message_callback(result.message, result.message_size, result.port);
break;
case TTN_EVENT_TRANSMISSION_COMPLETED:
return TTN_SUCCESSFUL_TRANSMISSION;
case TTN_EVENT_TRANSMISSION_FAILED:
return TTN_ERROR_TRANSMISSION_FAILED;
default:
ASSERT(0);
}
}
}
void ttn_on_message(ttn_message_cb callback)
{
message_callback = callback;
}
bool ttn_is_provisioned(void)
{
if (ttn_provisioning_have_keys())
return true;
ttn_provisioning_restore_keys(true);
return ttn_provisioning_have_keys();
}
void ttn_prepare_for_deep_sleep(void)
{
ttn_rtc_save();
stop();
}
void ttn_prepare_for_power_off(void)
{
ttn_nvs_save();
stop();
}
void ttn_wait_for_idle(void)
{
while (true)
{
TickType_t ticks_to_wait = ttn_busy_duration();
if (ticks_to_wait == 0)
return;
vTaskDelay(ticks_to_wait);
}
}
TickType_t ttn_busy_duration(void)
{
TickType_t duration = hal_esp32_get_timer_duration();
if (duration != 0)
return duration; // busy or timer scheduled
if ((LMIC.opmode & (OP_JOINING | OP_TXDATA | OP_POLL | OP_TXRXPEND)) != 0)
return pdMS_TO_TICKS(100); // pending action
return 0; // idle
}
void ttn_set_rssi_cal(int8_t rssi_cal)
{
hal_esp32_set_rssi_cal(rssi_cal);
}
bool ttn_adr_enabled(void)
{
return LMIC.adrEnabled != 0;
}
void ttn_set_adr_enabled(bool enabled)
{
hal_esp32_enter_critical_section();
LMIC_setAdrMode(enabled);
hal_esp32_leave_critical_section();
}
void ttn_set_data_rate(ttn_data_rate_t data_rate)
{
join_data_rate = data_rate;
if (has_joined)
{
hal_esp32_enter_critical_section();
LMIC_setDrTxpow(data_rate, LMIC.adrTxPow);
hal_esp32_leave_critical_section();
}
}
void ttn_set_max_tx_pow(int tx_pow)
{
max_tx_power = tx_pow;
if (has_joined)
{
hal_esp32_enter_critical_section();
LMIC_setDrTxpow(LMIC.datarate, tx_pow);
hal_esp32_leave_critical_section();
}
}
ttn_rf_settings_t ttn_get_rf_settings(ttn_rx_tx_window_t window)
{
int index = ((int)window) & 0x03;
return last_rf_settings[index];
}
ttn_rf_settings_t ttn_tx_settings(void)
{
return last_rf_settings[TTN_WINDOW_TX];
}
ttn_rf_settings_t ttn_rx1_settings(void)
{
return last_rf_settings[TTN_WINDOW_RX1];
}
ttn_rf_settings_t ttn_rx2_settings(void)
{
return last_rf_settings[TTN_WINDOW_RX2];
}
ttn_rx_tx_window_t ttn_rx_tx_window(void)
{
return current_rx_tx_window;
}
int ttn_rssi(void)
{
return LMIC.rssi;
}
// --- Callbacks ---
#if CONFIG_LOG_DEFAULT_LEVEL >= 3 || LMIC_ENABLE_event_logging
static const char *event_names[] = {LMIC_EVENT_NAME_TABLE__INIT};
#endif
// Called by LMIC when an LMIC event (join, join failed, reset etc.) occurs
void event_callback(void *user_data, ev_t event)
{
// update monitoring information
switch (event)
{
case EV_TXSTART:
current_rx_tx_window = TTN_WINDOW_TX;
save_rf_settings(&last_rf_settings[TTN_WINDOW_TX]);
clear_rf_settings(&last_rf_settings[TTN_WINDOW_RX1]);
clear_rf_settings(&last_rf_settings[TTN_WINDOW_RX2]);
break;
case EV_RXSTART:
if (current_rx_tx_window != TTN_WINDOW_RX1)
{
current_rx_tx_window = TTN_WINDOW_RX1;
save_rf_settings(&last_rf_settings[TTN_WINDOW_RX1]);
}
else
{
current_rx_tx_window = TTN_WINDOW_RX2;
save_rf_settings(&last_rf_settings[TTN_WINDOW_RX2]);
}
break;
default:
current_rx_tx_window = TTN_WINDOW_IDLE;
break;
};
#if LMIC_ENABLE_event_logging
ttn_log_event(event, event_names[event], 0);
#elif CONFIG_LOG_DEFAULT_LEVEL >= 3
ESP_LOGI(TAG, "event %s", event_names[event]);
#endif
ttn_event_t ttn_event = TTN_EVENT_NONE;
if (waiting_reason == TTN_WAITING_FOR_JOIN)
{
if (event == EV_JOINED)
{
ttn_event = TTN_EVNT_JOIN_COMPLETED;
}
else if (event == EV_REJOIN_FAILED || event == EV_RESET)
{
ttn_event = TTN_EVENT_JOIN_FAILED;
}
}
if (ttn_event == TTN_EVENT_NONE)
return;
ttn_lmic_event_t result = {.event = ttn_event};
waiting_reason = TTN_WAITING_NONE;
xQueueSend(lmic_event_queue, &result, pdMS_TO_TICKS(100));
}
// Called by LMIC when a message has been received
void message_received_callback(void *user_data, uint8_t port, const uint8_t *message, size_t message_size)
{
ttn_lmic_event_t result = {
.event = TTN_EVENT_MESSAGE_RECEIVED, .port = port, .message = message, .message_size = message_size};
xQueueSend(lmic_event_queue, &result, pdMS_TO_TICKS(100));
}
// Called by LMIC when a message has been transmitted (or the transmission failed)
void message_transmitted_callback(void *user_data, int success)
{
waiting_reason = TTN_WAITING_NONE;
ttn_lmic_event_t result = {.event = success ? TTN_EVENT_TRANSMISSION_COMPLETED : TTN_EVENT_TRANSMISSION_FAILED};
xQueueSend(lmic_event_queue, &result, pdMS_TO_TICKS(100));
}
// --- Helpers
void save_rf_settings(ttn_rf_settings_t *rf_settings)
{
rf_settings->spreading_factor = (ttn_spreading_factor_t)(getSf(LMIC.rps) + 1);
rf_settings->bandwidth = (ttn_bandwidth_t)(getBw(LMIC.rps) + 1);
rf_settings->frequency = LMIC.freq;
}
void clear_rf_settings(ttn_rf_settings_t *rf_settings)
{
memset(rf_settings, 0, sizeof(*rf_settings));
}

313
src/ttn_logging.c Normal file
View File

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

50
src/ttn_logging.h Normal file
View File

@ -0,0 +1,50 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Circular buffer for detailed logging without affecting LMIC timing.
*******************************************************************************/
#ifndef TTN_LOGGING_H
#define TTN_LOGGING_H
#if LMIC_ENABLE_event_logging
#include <freertos/FreeRTOS.h>
#include <freertos/ringbuf.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Logging functions.
*
* Logs internal information from LMIC in an asynchrnous fashion in order
* not to distrub the sensitive LORA timing.
*
* A ring buffer and a separate logging task is ued. The LMIC core records
* relevant values from the current LORA settings and writes them to a ring
* buffer. The logging tasks receives the message and the values, formats
* them and outputs them via the regular ESP-IDF logging mechanism.
*
* In order to activate the detailed logging, set the macro
* `LMIC_ENABLE_event_logging` to 1.
*/
void ttn_log_init(void);
void ttn_log_event(int event, const char *message, uint32_t datum);
#ifdef __cplusplus
}
#endif
#endif
#endif

122
src/ttn_nvs.c Normal file
View File

@ -0,0 +1,122 @@
/*******************************************************************************
*
* 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"
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, &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 *)&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 *)&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)
memset(&LMIC.radio, 0, sizeof(LMIC) - LMIC_OFFSET(radio));
return res == ESP_OK;
}

30
src/ttn_nvs.h Normal file
View File

@ -0,0 +1,30 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Functions for storing and retrieving TTN communication state from NVS.
*******************************************************************************/
#ifndef TTN_NVS_H
#define TTN_NVS_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C"
{
#endif
void ttn_nvs_save();
bool ttn_nvs_restore(int off_duration);
#ifdef __cplusplus
}
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More