mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2025-07-16 23:52:52 +02:00
Compare commits
69 Commits
lmic-early
...
569afe84e9
Author | SHA1 | Date | |
---|---|---|---|
569afe84e9 | |||
4e0587bb8e | |||
877784640d | |||
79727fdae2 | |||
50d45e7fb7 | |||
bbdb44cdd6 | |||
e7d9caefe1 | |||
68d5669833 | |||
46ad09736b | |||
1b7279518b | |||
c1517d3c82 | |||
ffcdd12d48 | |||
0a7353c146 | |||
54a91a27db | |||
ee8eead337 | |||
f06be4c069 | |||
5a7b3b0fcb | |||
9fd24c65ec | |||
31b00f05a8 | |||
ba481ceac5 | |||
b626ccb61a | |||
f433e826a7 | |||
0130928601 | |||
e71d584fca | |||
de4297a8f4 | |||
ee91ccc613 | |||
7df10bd6bc | |||
bd728887cf | |||
1df2c50f6f | |||
8fa345a5d4 | |||
e34dbcb467 | |||
72b878f1d9 | |||
17e432d714 | |||
5c1db030f2 | |||
eb5792fbb9 | |||
37d7f8d517 | |||
62e829b0d3 | |||
6df4b40122 | |||
7fa43dbbdb | |||
ba908c0b93 | |||
281ba52155 | |||
36edf92944 | |||
f421db44d7 | |||
8e2886db27 | |||
973a7c41c8 | |||
99bab17d4b | |||
1913840679 | |||
5c695fd223 | |||
3086b1bb58 | |||
f57c505d7e | |||
ba22c5a745 | |||
5b22f228da | |||
84b2f88f0c | |||
fffe7bd8b3 | |||
bb41c4960f | |||
f70ad2a996 | |||
fdacae73f8 | |||
9489f34542 | |||
2a31ef0096 | |||
e15d936bb4 | |||
a8cd58214d | |||
b3e0b567b3 | |||
0853fe05ec | |||
625968cd99 | |||
8d7d157445 | |||
b73ed23a97 | |||
513ac44268 | |||
45778d2186 | |||
e29477fa7e |
3
.clang-format
Normal file
3
.clang-format
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
BasedOnStyle: Microsoft
|
||||||
|
IndentWidth: 4
|
||||||
|
ColumnLimit: 120
|
5
.vscode/c_cpp_properties.json
vendored
5
.vscode/c_cpp_properties.json
vendored
@ -22,14 +22,15 @@
|
|||||||
"${workspaceRoot}/examples/send_recv/build/include",
|
"${workspaceRoot}/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
|
||||||
|
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
21
Kconfig
@ -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
|
||||||
|
2
LICENSE
2
LICENSE
@ -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
|
||||||
|
27
README.md
27
README.md
@ -2,15 +2,30 @@
|
|||||||
|
|
||||||
**The Things Network device library for ESP-IDF (ESP32) supporting devices with Semtech SX127x chips**
|
**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
|
||||||
|
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
COMPONENT_ADD_INCLUDEDIRS := include
|
|
||||||
COMPONENT_SRCDIRS := src src/aes src/hal src/lmic
|
|
5
doc/.gitignore
vendored
Normal file
5
doc/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
xml/
|
||||||
|
html/
|
||||||
|
api.md
|
||||||
|
node_modules/
|
||||||
|
ttn-esp32.zip
|
2619
doc/Doxyfile
Normal file
2619
doc/Doxyfile
Normal file
File diff suppressed because it is too large
Load Diff
21
doc/doc.dox
Normal file
21
doc/doc.dox
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*!
|
||||||
|
\mainpage ttn-esp32 API Documentation
|
||||||
|
|
||||||
|
ttn-esp32 is a C and C++ library for the ESP-IDF (ESP32) framework
|
||||||
|
for The Things Network library end devices using Semtech SX127x chips.
|
||||||
|
|
||||||
|
|
||||||
|
\section cplusplus C++ API
|
||||||
|
|
||||||
|
\ref TheThingsNetwork class
|
||||||
|
|
||||||
|
\ref TTNRFSettings struct
|
||||||
|
|
||||||
|
|
||||||
|
\section c C API
|
||||||
|
|
||||||
|
\ref c_api
|
||||||
|
|
||||||
|
\ref ttn_rf_settings_t struct
|
||||||
|
|
||||||
|
*/
|
@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
Doxygen Awesome
|
||||||
|
https://github.com/jothepro/doxygen-awesome-css
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 jothepro
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
|
||||||
|
#MSearchBox {
|
||||||
|
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchField {
|
||||||
|
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height));
|
||||||
|
}
|
||||||
|
}
|
107
doc/doxygen-awesome-css/doxygen-awesome-sidebar-only.css
Normal file
107
doc/doxygen-awesome-css/doxygen-awesome-sidebar-only.css
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
|
||||||
|
Doxygen Awesome
|
||||||
|
https://github.com/jothepro/doxygen-awesome-css
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 jothepro
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
html {
|
||||||
|
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
|
||||||
|
* Make sure it is wide enought to contain the page title (logo + title + version)
|
||||||
|
*/
|
||||||
|
--side-nav-fixed-width: 340px;
|
||||||
|
--menu-display: none;
|
||||||
|
|
||||||
|
--top-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
html {
|
||||||
|
--searchbar-background: var(--page-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#side-nav {
|
||||||
|
min-width: var(--side-nav-fixed-width);
|
||||||
|
max-width: var(--side-nav-fixed-width);
|
||||||
|
top: var(--top-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-tree, #side-nav {
|
||||||
|
height: calc(100vh - var(--top-height)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-tree {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top {
|
||||||
|
display: block;
|
||||||
|
border-bottom: none;
|
||||||
|
height: var(--top-height);
|
||||||
|
margin-bottom: calc(0px - var(--top-height));
|
||||||
|
max-width: var(--side-nav-fixed-width);
|
||||||
|
background: var(--side-nav-background);
|
||||||
|
}
|
||||||
|
#main-nav {
|
||||||
|
float: left;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-resizable-handle {
|
||||||
|
cursor: default;
|
||||||
|
width: 1px !important;
|
||||||
|
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-path {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
left: var(--side-nav-fixed-width);
|
||||||
|
bottom: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#doc-content {
|
||||||
|
height: calc(100vh - 31px) !important;
|
||||||
|
padding-bottom: calc(3 * var(--spacing-large));
|
||||||
|
padding-top: calc(var(--top-height) - 80px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-left: var(--side-nav-fixed-width) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchBox {
|
||||||
|
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchField {
|
||||||
|
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#MSearchResultsWindow {
|
||||||
|
left: var(--spacing-medium) !important;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
}
|
1478
doc/doxygen-awesome-css/doxygen-awesome.css
Normal file
1478
doc/doxygen-awesome-css/doxygen-awesome.css
Normal file
File diff suppressed because it is too large
Load Diff
5
doc/generate.sh
Executable file
5
doc/generate.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
rm -rf html ttn-esp32.zip
|
||||||
|
doxygen Doxyfile
|
||||||
|
cd html
|
||||||
|
zip -r ../ttn-esp32.zip .
|
10
examples/deep_sleep/CMakeLists.txt
Normal file
10
examples/deep_sleep/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
|
||||||
|
# Update the below line to match the path to the ttn-esp32 library,
|
||||||
|
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
|
||||||
|
list(APPEND EXTRA_COMPONENT_DIRS "../..")
|
||||||
|
|
||||||
|
#add_definitions(-DLMIC_ENABLE_event_logging=1)
|
||||||
|
|
||||||
|
project(deep_sleep)
|
4
examples/deep_sleep/main/CMakeLists.txt
Normal file
4
examples/deep_sleep/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "main.cpp"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES ttn-esp32)
|
133
examples/deep_sleep/main/main.cpp
Normal file
133
examples/deep_sleep/main/main.cpp
Normal 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();
|
||||||
|
}
|
10
examples/deep_sleep_in_c/CMakeLists.txt
Normal file
10
examples/deep_sleep_in_c/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
|
||||||
|
# Update the below line to match the path to the ttn-esp32 library,
|
||||||
|
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
|
||||||
|
list(APPEND EXTRA_COMPONENT_DIRS "../..")
|
||||||
|
|
||||||
|
#add_definitions(-DLMIC_ENABLE_event_logging=1)
|
||||||
|
|
||||||
|
project(deep_sleep)
|
4
examples/deep_sleep_in_c/main/CMakeLists.txt
Normal file
4
examples/deep_sleep_in_c/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "main.c"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES ttn-esp32)
|
135
examples/deep_sleep_in_c/main/main.c
Normal file
135
examples/deep_sleep_in_c/main/main.c
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Manuel Bleichenbacher
|
||||||
|
*
|
||||||
|
* Licensed under MIT License
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*
|
||||||
|
* Sample program (in C) sending messages and going to deep sleep in-between.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_sleep.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "ttn.h"
|
||||||
|
|
||||||
|
// NOTE:
|
||||||
|
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
|
||||||
|
// Go to Components / The Things Network, select the appropriate values and save.
|
||||||
|
|
||||||
|
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
|
||||||
|
// > Your device > Activation information)
|
||||||
|
|
||||||
|
// AppEUI (sometimes called JoinEUI)
|
||||||
|
const char *appEui = "????????????????";
|
||||||
|
// DevEUI
|
||||||
|
const char *devEui = "????????????????";
|
||||||
|
// AppKey
|
||||||
|
const char *appKey = "????????????????????????????????";
|
||||||
|
|
||||||
|
// Pins and other resources
|
||||||
|
#define TTN_SPI_HOST SPI2_HOST
|
||||||
|
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
|
||||||
|
#define TTN_PIN_SPI_SCLK 5
|
||||||
|
#define TTN_PIN_SPI_MOSI 27
|
||||||
|
#define TTN_PIN_SPI_MISO 19
|
||||||
|
#define TTN_PIN_NSS 18
|
||||||
|
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
|
||||||
|
#define TTN_PIN_RST 14
|
||||||
|
#define TTN_PIN_DIO0 26
|
||||||
|
#define TTN_PIN_DIO1 35
|
||||||
|
|
||||||
|
#define TX_INTERVAL 60 // in sec
|
||||||
|
|
||||||
|
// This counter value is retained (in RTC memory) during deep sleep
|
||||||
|
RTC_DATA_ATTR int counter_in_rtc_mem;
|
||||||
|
|
||||||
|
|
||||||
|
void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
|
||||||
|
{
|
||||||
|
printf("Message of %d bytes received on port %d:", length, port);
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
printf(" %02x", message[i]);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
esp_err_t err;
|
||||||
|
// Initialize the GPIO ISR handler service
|
||||||
|
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
|
||||||
|
err = nvs_flash_init();
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
// Initialize SPI bus
|
||||||
|
spi_bus_config_t spi_bus_config = {
|
||||||
|
.miso_io_num = TTN_PIN_SPI_MISO,
|
||||||
|
.mosi_io_num = TTN_PIN_SPI_MOSI,
|
||||||
|
.sclk_io_num = TTN_PIN_SPI_SCLK,
|
||||||
|
.quadwp_io_num = -1,
|
||||||
|
.quadhd_io_num = -1
|
||||||
|
};
|
||||||
|
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
// Initialize TTN
|
||||||
|
ttn_init();
|
||||||
|
|
||||||
|
// Configure the SX127x pins
|
||||||
|
ttn_configure_pins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
|
||||||
|
|
||||||
|
// The below line can be commented after the first run as the data is saved in NVS
|
||||||
|
ttn_provision(devEui, appEui, appKey);
|
||||||
|
|
||||||
|
// Register callback for received messages
|
||||||
|
ttn_on_message(messageReceived);
|
||||||
|
|
||||||
|
// ttn_set_adr_enabled(false);
|
||||||
|
// ttn_set_data_rate(TTN_DR_US915_SF7);
|
||||||
|
// ttn_set_max_tx_pow(14);
|
||||||
|
|
||||||
|
if (ttn_resume_after_deep_sleep()) {
|
||||||
|
printf("Resumed from deep sleep.\n");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
printf("Joining...\n");
|
||||||
|
if (ttn_join())
|
||||||
|
{
|
||||||
|
printf("Joined.\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Join failed. Goodbye\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Sending message...\n");
|
||||||
|
counter_in_rtc_mem++;
|
||||||
|
char message[20];
|
||||||
|
sprintf(message, "Hello %d", counter_in_rtc_mem);
|
||||||
|
ttn_response_code_t res = ttn_transmit_message((uint8_t *)message, strlen(message), 1, false);
|
||||||
|
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
|
||||||
|
|
||||||
|
// Wait until TTN communication is idle and save state
|
||||||
|
ttn_wait_for_idle();
|
||||||
|
ttn_prepare_for_deep_sleep();
|
||||||
|
|
||||||
|
// Schedule wake up
|
||||||
|
esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000LL);
|
||||||
|
|
||||||
|
printf("Going to deep sleep...\n");
|
||||||
|
esp_deep_sleep_start();
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
PROJECT_NAME := hello_world
|
|
||||||
|
|
||||||
EXTRA_COMPONENT_DIRS := $(abspath ../..)
|
|
||||||
|
|
||||||
include $(IDF_PATH)/make/project.mk
|
|
@ -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)
|
||||||
|
@ -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())
|
||||||
{
|
{
|
||||||
|
10
examples/hello_world_in_c/CMakeLists.txt
Normal file
10
examples/hello_world_in_c/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
|
||||||
|
# Update the below line to match the path to the ttn-esp32 library,
|
||||||
|
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
|
||||||
|
list(APPEND EXTRA_COMPONENT_DIRS "../..")
|
||||||
|
|
||||||
|
#add_definitions(-DLMIC_ENABLE_event_logging=1)
|
||||||
|
|
||||||
|
project(hello_world)
|
4
examples/hello_world_in_c/main/CMakeLists.txt
Normal file
4
examples/hello_world_in_c/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "main.c"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES ttn-esp32)
|
117
examples/hello_world_in_c/main/main.c
Normal file
117
examples/hello_world_in_c/main/main.c
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Manuel Bleichenbacher
|
||||||
|
*
|
||||||
|
* Licensed under MIT License
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*
|
||||||
|
* Sample program for C showing how to send and receive messages.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
|
||||||
|
#include "ttn.h"
|
||||||
|
|
||||||
|
// NOTE:
|
||||||
|
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
|
||||||
|
// Go to Components / The Things Network, select the appropriate values and save.
|
||||||
|
|
||||||
|
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
|
||||||
|
// > Your device > Activation information)
|
||||||
|
|
||||||
|
// AppEUI (sometimes called JoinEUI)
|
||||||
|
const char *appEui = "????????????????";
|
||||||
|
// DevEUI
|
||||||
|
const char *devEui = "????????????????";
|
||||||
|
// AppKey
|
||||||
|
const char *appKey = "????????????????????????????????";
|
||||||
|
|
||||||
|
// Pins and other resources
|
||||||
|
#define TTN_SPI_HOST SPI2_HOST
|
||||||
|
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
|
||||||
|
#define TTN_PIN_SPI_SCLK 5
|
||||||
|
#define TTN_PIN_SPI_MOSI 27
|
||||||
|
#define TTN_PIN_SPI_MISO 19
|
||||||
|
#define TTN_PIN_NSS 18
|
||||||
|
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
|
||||||
|
#define TTN_PIN_RST 14
|
||||||
|
#define TTN_PIN_DIO0 26
|
||||||
|
#define TTN_PIN_DIO1 35
|
||||||
|
|
||||||
|
#define TX_INTERVAL 30
|
||||||
|
static uint8_t msgData[] = "Hello, world";
|
||||||
|
|
||||||
|
|
||||||
|
void sendMessages(void* pvParameter)
|
||||||
|
{
|
||||||
|
while (1) {
|
||||||
|
printf("Sending message...\n");
|
||||||
|
ttn_response_code_t res = ttn_transmit_message(msgData, sizeof(msgData) - 1, 1, false);
|
||||||
|
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
|
||||||
|
|
||||||
|
vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
|
||||||
|
{
|
||||||
|
printf("Message of %d bytes received on port %d:", length, port);
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
printf(" %02x", message[i]);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
esp_err_t err;
|
||||||
|
// Initialize the GPIO ISR handler service
|
||||||
|
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
|
||||||
|
err = nvs_flash_init();
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
// Initialize SPI bus
|
||||||
|
spi_bus_config_t spi_bus_config = {
|
||||||
|
.miso_io_num = TTN_PIN_SPI_MISO,
|
||||||
|
.mosi_io_num = TTN_PIN_SPI_MOSI,
|
||||||
|
.sclk_io_num = TTN_PIN_SPI_SCLK,
|
||||||
|
.quadwp_io_num = -1,
|
||||||
|
.quadhd_io_num = -1
|
||||||
|
};
|
||||||
|
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
// Initialize TTN
|
||||||
|
ttn_init();
|
||||||
|
|
||||||
|
// Configure the SX127x pins
|
||||||
|
ttn_configure_pins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
|
||||||
|
|
||||||
|
// The below line can be commented after the first run as the data is saved in NVS
|
||||||
|
ttn_provision(devEui, appEui, appKey);
|
||||||
|
|
||||||
|
// Register callback for received messages
|
||||||
|
ttn_on_message(messageReceived);
|
||||||
|
|
||||||
|
// ttn_set_adr_enabled(false);
|
||||||
|
// ttn_set_data_rate(TTN_DR_US915_SF7);
|
||||||
|
// ttn_set_max_tx_pow(14);
|
||||||
|
|
||||||
|
printf("Joining...\n");
|
||||||
|
if (ttn_join())
|
||||||
|
{
|
||||||
|
printf("Joined.\n");
|
||||||
|
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, NULL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Join failed. Goodbye\n");
|
||||||
|
}
|
||||||
|
}
|
@ -1,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)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
PROJECT_NAME := mac_address
|
|
||||||
|
|
||||||
EXTRA_COMPONENT_DIRS := $(abspath ../..)
|
|
||||||
|
|
||||||
include $(IDF_PATH)/make/project.mk
|
|
@ -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)
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
examples/monitoring/CMakeLists.txt
Normal file
8
examples/monitoring/CMakeLists.txt
Normal 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)
|
4
examples/monitoring/main/CMakeLists.txt
Normal file
4
examples/monitoring/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "main.cpp"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES ttn-esp32)
|
@ -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
|
10
examples/power_off/CMakeLists.txt
Normal file
10
examples/power_off/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
|
||||||
|
# Update the below line to match the path to the ttn-esp32 library,
|
||||||
|
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
|
||||||
|
list(APPEND EXTRA_COMPONENT_DIRS "../..")
|
||||||
|
|
||||||
|
#add_definitions(-DLMIC_ENABLE_event_logging=1)
|
||||||
|
|
||||||
|
project(power_off)
|
4
examples/power_off/main/CMakeLists.txt
Normal file
4
examples/power_off/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "main.cpp"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES ttn-esp32)
|
126
examples/power_off/main/main.cpp
Normal file
126
examples/power_off/main/main.cpp
Normal 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));
|
||||||
|
}
|
10
examples/power_off_in_c/CMakeLists.txt
Normal file
10
examples/power_off_in_c/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
|
||||||
|
# Update the below line to match the path to the ttn-esp32 library,
|
||||||
|
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
|
||||||
|
list(APPEND EXTRA_COMPONENT_DIRS "../..")
|
||||||
|
|
||||||
|
#add_definitions(-DLMIC_ENABLE_event_logging=1)
|
||||||
|
|
||||||
|
project(power_off)
|
4
examples/power_off_in_c/main/CMakeLists.txt
Normal file
4
examples/power_off_in_c/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "main.c"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES ttn-esp32)
|
128
examples/power_off_in_c/main/main.c
Normal file
128
examples/power_off_in_c/main/main.c
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Manuel Bleichenbacher
|
||||||
|
*
|
||||||
|
* Licensed under MIT License
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*
|
||||||
|
* Sample program (in C) sending messages and powering off in-between.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_sleep.h"
|
||||||
|
|
||||||
|
#include "ttn.h"
|
||||||
|
|
||||||
|
// NOTE:
|
||||||
|
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
|
||||||
|
// Go to Components / The Things Network, select the appropriate values and save.
|
||||||
|
|
||||||
|
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
|
||||||
|
// > Your device > Activation information)
|
||||||
|
|
||||||
|
// AppEUI (sometimes called JoinEUI)
|
||||||
|
const char *appEui = "????????????????";
|
||||||
|
// DevEUI
|
||||||
|
const char *devEui = "????????????????";
|
||||||
|
// AppKey
|
||||||
|
const char *appKey = "????????????????????????????????";
|
||||||
|
|
||||||
|
// Pins and other resources
|
||||||
|
#define TTN_SPI_HOST SPI2_HOST
|
||||||
|
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
|
||||||
|
#define TTN_PIN_SPI_SCLK 5
|
||||||
|
#define TTN_PIN_SPI_MOSI 27
|
||||||
|
#define TTN_PIN_SPI_MISO 19
|
||||||
|
#define TTN_PIN_NSS 18
|
||||||
|
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
|
||||||
|
#define TTN_PIN_RST 14
|
||||||
|
#define TTN_PIN_DIO0 26
|
||||||
|
#define TTN_PIN_DIO1 35
|
||||||
|
|
||||||
|
#define TX_INTERVAL 60 // in sec
|
||||||
|
static uint8_t msgData[] = "Hello, world";
|
||||||
|
|
||||||
|
|
||||||
|
void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
|
||||||
|
{
|
||||||
|
printf("Message of %d bytes received on port %d:", length, port);
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
printf(" %02x", message[i]);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
esp_err_t err;
|
||||||
|
// Initialize the GPIO ISR handler service
|
||||||
|
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
|
||||||
|
err = nvs_flash_init();
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
// Initialize SPI bus
|
||||||
|
spi_bus_config_t spi_bus_config = {
|
||||||
|
.miso_io_num = TTN_PIN_SPI_MISO,
|
||||||
|
.mosi_io_num = TTN_PIN_SPI_MOSI,
|
||||||
|
.sclk_io_num = TTN_PIN_SPI_SCLK,
|
||||||
|
.quadwp_io_num = -1,
|
||||||
|
.quadhd_io_num = -1
|
||||||
|
};
|
||||||
|
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
// Initialize TTN
|
||||||
|
ttn_init();
|
||||||
|
|
||||||
|
// Configure the SX127x pins
|
||||||
|
ttn_configure_pins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
|
||||||
|
|
||||||
|
// The below line can be commented after the first run as the data is saved in NVS
|
||||||
|
ttn_provision(devEui, appEui, appKey);
|
||||||
|
|
||||||
|
// Register callback for received messages
|
||||||
|
ttn_on_message(messageReceived);
|
||||||
|
|
||||||
|
// ttn_set_adr_enabled(false);
|
||||||
|
// ttn_set_data_rate(TTN_DR_US915_SF7);
|
||||||
|
// ttn_set_max_tx_pow(14);
|
||||||
|
|
||||||
|
if (ttn_resume_after_power_off(60)) {
|
||||||
|
printf("Resumed from power off.\n");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
printf("Joining...\n");
|
||||||
|
if (ttn_join())
|
||||||
|
{
|
||||||
|
printf("Joined.\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Join failed. Goodbye\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Sending message...\n");
|
||||||
|
ttn_response_code_t res = ttn_transmit_message(msgData, sizeof(msgData) - 1, 1, false);
|
||||||
|
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
|
||||||
|
|
||||||
|
// Wait until TTN communication is idle and save state
|
||||||
|
ttn_wait_for_idle();
|
||||||
|
ttn_prepare_for_power_off();
|
||||||
|
|
||||||
|
printf("Power off...\n");
|
||||||
|
// Do whatever is needed to power off the device.
|
||||||
|
// For testing, press reset button to simulate power cycle.
|
||||||
|
while (true)
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
}
|
@ -1,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)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
PROJECT_NAME := provisioning
|
|
||||||
|
|
||||||
EXTRA_COMPONENT_DIRS := $(abspath ../..)
|
|
||||||
|
|
||||||
include $(IDF_PATH)/make/project.mk
|
|
@ -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)
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
|
@ -1,5 +0,0 @@
|
|||||||
PROJECT_NAME := send_recv
|
|
||||||
|
|
||||||
EXTRA_COMPONENT_DIRS := $(abspath ../..)
|
|
||||||
|
|
||||||
include $(IDF_PATH)/make/project.mk
|
|
@ -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
822
include/ttn.h
Normal file
@ -0,0 +1,822 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2021 Manuel Bleichenbacher
|
||||||
|
*
|
||||||
|
* Licensed under MIT License
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*
|
||||||
|
* High-level C API for ttn-esp32.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup c_api C API
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TTN_C_H
|
||||||
|
#define TTN_C_H
|
||||||
|
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @addtogroup c_api
|
||||||
|
*
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constant for indicating that a pin is not connected
|
||||||
|
*/
|
||||||
|
#define TTN_NOT_CONNECTED 0xff
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Integer data type for specifiying the port of an uplink or downlink message.
|
||||||
|
*/
|
||||||
|
typedef uint8_t ttn_port_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Response codes
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/** @brief Transmission failed error */
|
||||||
|
TTN_ERROR_TRANSMISSION_FAILED = -1,
|
||||||
|
/** @brief Unexpected or internal error */
|
||||||
|
TTN_ERROR_UNEXPECTED = -10,
|
||||||
|
/** @brief Successful transmission of an uplink message */
|
||||||
|
TTN_SUCCESSFUL_TRANSMISSION = 1,
|
||||||
|
/** @brief Successful receipt of a downlink message */
|
||||||
|
TTN_SUCCESSFUL_RECEIVE = 2
|
||||||
|
} ttn_response_code_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RX/TX window
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief Outside RX/TX window
|
||||||
|
*/
|
||||||
|
TTN_WINDOW_IDLE = 0,
|
||||||
|
/**
|
||||||
|
* @brief Transmission window (up to RX1 window)
|
||||||
|
*/
|
||||||
|
TTN_WINDOW_TX = 1,
|
||||||
|
/**
|
||||||
|
* @brief Reception window 1 (up to RX2 window)
|
||||||
|
*/
|
||||||
|
TTN_WINDOW_RX1 = 2,
|
||||||
|
/**
|
||||||
|
* @brief Reception window 2
|
||||||
|
*/
|
||||||
|
TTN_WINDOW_RX2 = 3
|
||||||
|
} ttn_rx_tx_window_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Spreading Factor
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief Unused / undefined spreading factor
|
||||||
|
*/
|
||||||
|
TTN_SF_NONE = 0,
|
||||||
|
/**
|
||||||
|
* @brief Frequency Shift Keying (FSK)
|
||||||
|
*/
|
||||||
|
TTN_FSK = 1,
|
||||||
|
/**
|
||||||
|
* @brief Spreading Factor 7 (SF7)
|
||||||
|
*/
|
||||||
|
TTN_SF7 = 2,
|
||||||
|
/**
|
||||||
|
* @brief Spreading Factor 8 (SF8)
|
||||||
|
*/
|
||||||
|
TTN_SF8 = 3,
|
||||||
|
/**
|
||||||
|
* @brief Spreading Factor 9 (SF9)
|
||||||
|
*/
|
||||||
|
TTN_SF9 = 4,
|
||||||
|
/**
|
||||||
|
* @brief Spreading Factor 10 (SF10)
|
||||||
|
*/
|
||||||
|
TTN_SF10 = 5,
|
||||||
|
/**
|
||||||
|
* @brief Spreading Factor 11 (SF11)
|
||||||
|
*/
|
||||||
|
TTN_SF11 = 6,
|
||||||
|
/**
|
||||||
|
* @brief Spreading Factor 12 (SF12)
|
||||||
|
*/
|
||||||
|
TTN_SF12 = 7
|
||||||
|
} ttn_spreading_factor_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bandwidth
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief Undefined/unused bandwidth
|
||||||
|
*/
|
||||||
|
TTN_BW_NONE = 0,
|
||||||
|
/**
|
||||||
|
* @brief Bandwidth of 125 kHz
|
||||||
|
*/
|
||||||
|
TTN_BW_125 = 1,
|
||||||
|
/**
|
||||||
|
* @brief Bandwidth of 250 kHz
|
||||||
|
*/
|
||||||
|
TTN_BW_250 = 2,
|
||||||
|
/**
|
||||||
|
* @brief Bandwidth of 500 kHz
|
||||||
|
*/
|
||||||
|
TTN_BW_500 = 3
|
||||||
|
} ttn_bandwidth_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Data Rate
|
||||||
|
*
|
||||||
|
* Note that the spreading factor, bandwidth, bit rate and maximum message
|
||||||
|
* size associated with each data rate depends on the region.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AS923 using SF12 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AS923_SF12 = 0,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AS923 using SF11 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AS923_SF11 = 1,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AS923 using SF10 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AS923_SF10 = 2,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AS923 using SF9 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AS923_SF9 = 3,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AS923 using SF8 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AS923_SF8 = 4,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AS923 using SF7 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AS923_SF7_BW125 = 5,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AS923 using SF7 and 250 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AS923_SF7_BW250 = 6,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AS923 using FSK and 50 kpbs.
|
||||||
|
*/
|
||||||
|
TTN_DR_AS923_FSK = 7,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF12 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF12 = 0,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF11 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF11 = 1,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF10 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF10 = 2,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF9 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF9 = 3,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF8 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF8 = 4,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF7 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF7 = 5,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF8 and 500 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF8_BW500 = 6,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF12 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF12_BW500 = 8,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF11 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF11_BW500 = 9,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF10 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF10_BW500 = 10,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF9 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF9_BW500 = 11,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF8 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF8_BW500_DR12 = 12,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region AU915 using SF7 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_AU915_SF7_BW500 = 13,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region EU868 using SF12 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_EU868_SF12 = 0,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region EU868 using SF11 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_EU868_SF11 = 1,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region EU868 using SF10 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_EU868_SF10 = 2,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region EU868 using SF9 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_EU868_SF9 = 3,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region EU868 using SF8 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_EU868_SF8 = 4,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region EU868 using SF7 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_EU868_SF7_BW125 = 5,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region EU868 using SF7 and 250 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_EU868_SF7_BW250 = 6,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region EU868 using FSK and 50 kpbs.
|
||||||
|
*/
|
||||||
|
TTN_DR_EU868_FSK = 7,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region IN866 using SF12 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_IN866_SF12 = 0,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region IN866 using SF11 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_IN866_SF11 = 1,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region IN866 using SF10 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_IN866_SF10 = 2,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region IN866 using SF9 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_IN866_SF9 = 3,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region IN866 using SF8 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_IN866_SF8 = 4,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region IN866 using SF7 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_IN866_SF7 = 5,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region IN866 using FSK and 50 kpbs.
|
||||||
|
*/
|
||||||
|
TTN_DR_IN866_FSK = 7,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region KR920 using SF12 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_KR920_SF12 = 0,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region KR920 using SF11 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_KR920_SF11 = 1,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region KR920 using SF10 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_KR920_SF10 = 2,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region KR920 using SF9 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_KR920_SF9 = 3,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region KR920 using SF8 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_KR920_SF8 = 4,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region KR920 using SF7 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_KR920_SF7 = 5,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF10 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF10 = 0,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF9 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF9 = 1,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF8 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF8 = 2,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF7 and 125 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF7 = 3,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF8 and 500 kHz bandwidth.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF8_BW500 = 4,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF12 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF12_BW500 = 8,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF11 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF11_BW500 = 9,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF10 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF10_BW500 = 10,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF9 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF9_BW500 = 11,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF8 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF8_BW500_DR12 = 12,
|
||||||
|
/**
|
||||||
|
* @brief Data rate for region US915 using SF7 and 500 kHz bandwidth.
|
||||||
|
*
|
||||||
|
* Reserved for future applications.
|
||||||
|
*/
|
||||||
|
TTN_DR_US915_SF7_BW500 = 13,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Default data rate for joining.
|
||||||
|
*/
|
||||||
|
TTN_DR_JOIN_DEFAULT = 255
|
||||||
|
} ttn_data_rate_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RF settings for TX or RX
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief Spreading Factor (SF)
|
||||||
|
*/
|
||||||
|
ttn_spreading_factor_t spreading_factor;
|
||||||
|
/**
|
||||||
|
* @brief Bandwidth (BW)
|
||||||
|
*/
|
||||||
|
ttn_bandwidth_t bandwidth;
|
||||||
|
/**
|
||||||
|
* @brief Frequency, in Hz
|
||||||
|
*/
|
||||||
|
uint32_t frequency;
|
||||||
|
} ttn_rf_settings_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Callback for recieved messages
|
||||||
|
*
|
||||||
|
* @param payload pointer to the received bytes
|
||||||
|
* @param length number of received bytes
|
||||||
|
* @param port port the message was received on
|
||||||
|
*/
|
||||||
|
typedef void (*ttn_message_cb)(const uint8_t *payload, size_t length, ttn_port_t port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes The Things Network device instance.
|
||||||
|
*
|
||||||
|
* Call this function once at the start of the program and before all other TTN functions.
|
||||||
|
*/
|
||||||
|
void ttn_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configures the pins used to communicate with the LoRaWAN radio chip.
|
||||||
|
*
|
||||||
|
* Before calling this member function, the SPI bus needs to be configured using `spi_bus_initialize()`.
|
||||||
|
* Additionally, `gpio_install_isr_service()` must have been called to initialize the GPIO ISR handler service.
|
||||||
|
*
|
||||||
|
* Call this function after @ref ttn_init() and before all other TTN functions.
|
||||||
|
*
|
||||||
|
* @param spi_host The SPI bus/peripherial to use (`SPI1_HOST`, `SPI2_HOST`, `SPI3_HOST`, `FSPI_HOST`, `HSPI_HOST`, or `VSPI_HOST`).
|
||||||
|
* @param nss The GPIO pin number connected to the radio chip's NSS pin (serving as the SPI chip select)
|
||||||
|
* @param rxtx The GPIO pin number connected to the radio chip's RXTX pin (@ref TTN_NOT_CONNECTED if not
|
||||||
|
* connected)
|
||||||
|
* @param rst The GPIO pin number connected to the radio chip's RST pin (@ref TTN_NOT_CONNECTED if not
|
||||||
|
* connected)
|
||||||
|
* @param dio0 The GPIO pin number connected to the radio chip's DIO0 pin
|
||||||
|
* @param dio1 The GPIO pin number connected to the radio chip's DIO1 pin
|
||||||
|
*/
|
||||||
|
void ttn_configure_pins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0,
|
||||||
|
uint8_t dio1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the frequency sub-band to be used.
|
||||||
|
*
|
||||||
|
* For regions with sub-bands (USA, Australia), sets the sub-band to be used for uplink communication.
|
||||||
|
* For other regions, this function has no effect.
|
||||||
|
*
|
||||||
|
* The sub-band must be set before joining or sending the first message.
|
||||||
|
*
|
||||||
|
* If not set, it defaults to sub-band 2 as defined by TTN.
|
||||||
|
*
|
||||||
|
* @param band band (0 for all bands, or value between 1 and 8)
|
||||||
|
*/
|
||||||
|
void ttn_set_subband(int band);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the keys needed to activate the device via OTAA, without activating it.
|
||||||
|
*
|
||||||
|
* The provided DevEUI, AppEUI/JoinEUI and AppKey are saved in non-volatile memory. Before
|
||||||
|
* this function is called, `nvs_flash_init()` must have been called once.
|
||||||
|
*
|
||||||
|
* In order to reduce flash wear, this function detects if the keys have not changed
|
||||||
|
* and will not write them again.
|
||||||
|
*
|
||||||
|
* Call @ref ttn_join() to activate the device.
|
||||||
|
*
|
||||||
|
* @param dev_eui DevEUI (16 character string with hexadecimal data)
|
||||||
|
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
|
||||||
|
* @param app_key AppKey of the device (32 character string with hexadecimal data)
|
||||||
|
* @return `true` if the provisioning was successful, `false` if the provisioning failed
|
||||||
|
*/
|
||||||
|
bool ttn_provision(const char *dev_eui, const char *app_eui, const char *app_key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the keys needed to activate the device via OTAA, without activating it.
|
||||||
|
*
|
||||||
|
* The provided DevEUI, AppEUI/JoinEUI and AppKey are only stored in RAM and will be lost
|
||||||
|
* when the device is powered off or put in deep sleep.
|
||||||
|
*
|
||||||
|
* Call @ref ttn_join() to activate the device.
|
||||||
|
*
|
||||||
|
* @param dev_eui DevEUI (16 character string with hexadecimal data)
|
||||||
|
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
|
||||||
|
* @param app_key AppKey of the device (32 character string with hexadecimal data)
|
||||||
|
* @return `true` if the provisioning was successful, `false` if the provisioning failed
|
||||||
|
*/
|
||||||
|
bool ttn_provision_transiently(const char *dev_eui, const char *app_eui, const char *app_key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the information needed to activate the device via OTAA, using the MAC to generate the DevEUI
|
||||||
|
* and without activating it.
|
||||||
|
*
|
||||||
|
* The generated DevEUI and the provided AppEUI/JoinEUI and AppKey are saved in non-volatile memory. Before
|
||||||
|
* this function is called, `nvs_flash_init` must have been called once.
|
||||||
|
*
|
||||||
|
* In order to reduce flash wear, this function detects if the keys have not changed
|
||||||
|
* and will not write them again.
|
||||||
|
*
|
||||||
|
* The DevEUI is generated by retrieving the ESP32's WiFi MAC address and expanding it into a DevEUI
|
||||||
|
* by adding FFFE in the middle. So the MAC address A0:B1:C2:01:02:03 becomes the EUI A0B1C2FFFE010203.
|
||||||
|
* This hexadecimal data can be entered into the DevEUI field in the TTN console.
|
||||||
|
*
|
||||||
|
* Generating the DevEUI from the MAC address allows to flash the same AppEUI/JoinEUI and AppKey to a batch of
|
||||||
|
* devices. However, using the same AppKey for multiple devices is insecure. Only use this approach if
|
||||||
|
* it is okay for that the LoRa communication of your application can easily be intercepted and that
|
||||||
|
* forged data can be injected.
|
||||||
|
*
|
||||||
|
* Call @ref ttn_join() to activate.
|
||||||
|
*
|
||||||
|
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
|
||||||
|
* @param app_key AppKey of the device (32 character string with hexadecimal data)
|
||||||
|
* @return `true` if the provisioning was successful, `false` if the provisioning failed
|
||||||
|
*/
|
||||||
|
bool ttn_provision_with_mac(const char *app_eui, const char *app_key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Starts task listening on configured UART for AT commands.
|
||||||
|
*
|
||||||
|
* Run `make menuconfig` to configure it.
|
||||||
|
*/
|
||||||
|
void ttn_start_provisioning_task(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Waits until the DevEUI, AppEUI/JoinEUI and AppKey have been provisioned
|
||||||
|
* by the provisioning task.
|
||||||
|
*
|
||||||
|
* If the device has already been provisioned (stored data in NVS, call of provision()
|
||||||
|
* or call of @ref ttn_join_with_keys(), this function immediately returns.
|
||||||
|
*/
|
||||||
|
void ttn_wait_for_provisioning(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activates the device via OTAA using previously provisioned keys.
|
||||||
|
*
|
||||||
|
* The DevEUI, AppEUI/JoinEUI and AppKey must have already been provisioned by a call
|
||||||
|
* to @ref ttn_provision() or @ref ttn_provision_with_mac().
|
||||||
|
* Before this function is called, `nvs_flash_init()` must have been called once.
|
||||||
|
*
|
||||||
|
* The RF module is initialized and the TTN background task is started.
|
||||||
|
*
|
||||||
|
* The function blocks until the activation has completed or failed.
|
||||||
|
*
|
||||||
|
* @return `true` if the activation was succesful, `false` if the activation failed
|
||||||
|
*/
|
||||||
|
bool ttn_join(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activates the device via OTAA using the provided keys.
|
||||||
|
*
|
||||||
|
* For the activation, the provided DevEUI, AppEUI/JoinEUI and AppKey are used.
|
||||||
|
* They are NOT saved in non-volatile memory.
|
||||||
|
*
|
||||||
|
* The RF module is initialized and the TTN background task is started.
|
||||||
|
*
|
||||||
|
* The function blocks until the activation has completed or failed.
|
||||||
|
*
|
||||||
|
* @param dev_eui DevEUI (16 character string with hexadecimal data)
|
||||||
|
* @param app_eui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
|
||||||
|
* @param app_key AppKey of the device (32 character string with hexadecimal data)
|
||||||
|
* @return `true` if the activation was succesful, `false` if the activation failed
|
||||||
|
*/
|
||||||
|
bool ttn_join_with_keys(const char *dev_eui, const char *app_eui, const char *app_key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resumes TTN communication after deep sleep.
|
||||||
|
*
|
||||||
|
* The communcation state is restored from data previously saved in RTC memory.
|
||||||
|
* The RF module and the TTN background task are started.
|
||||||
|
*
|
||||||
|
* This function is called instead of @ref ttn_join_with_keys() or @ref ttn_join()
|
||||||
|
* to continue with the established communication and to avoid a further join procedure.
|
||||||
|
*
|
||||||
|
* @return `true` if the device was able to resume, `false` otherwise.
|
||||||
|
*/
|
||||||
|
bool ttn_resume_after_deep_sleep(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resumes TTN communication after power off.
|
||||||
|
*
|
||||||
|
* The communcation state is restored from data previously saved in NVS (non-volatile storage).
|
||||||
|
* The RF module and the TTN background task are started.
|
||||||
|
*
|
||||||
|
* This function is called instead of @ref ttn_join_with_keys() or @ref ttn_join()
|
||||||
|
* to continue with the established communication and to avoid a further join procedure.
|
||||||
|
*
|
||||||
|
* In order to advance the clock, the estimated duration the device was powered off has to
|
||||||
|
* be specified. As the exact duration is probably not known, an estimation of the shortest
|
||||||
|
* duration between power-off and next power-on can be used instead.
|
||||||
|
*
|
||||||
|
* If the device has access to the real time, set the system time (using `settimeofday()`)
|
||||||
|
* before calling this function (and before @ref ttn_join()) and pass 0 for `off_duration`.
|
||||||
|
*
|
||||||
|
* Before this function is called, `nvs_flash_init()` must have been called once.
|
||||||
|
*
|
||||||
|
* @param off_duration duration the device was powered off (in minutes)
|
||||||
|
* @return `true` if the device was able to resume, `false` otherwise.
|
||||||
|
*/
|
||||||
|
bool ttn_resume_after_power_off(int off_duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stops all activies and prepares for deep sleep.
|
||||||
|
*
|
||||||
|
* This function is called before entering deep sleep. It saves the current
|
||||||
|
* communication state in RTC memory and shuts down the RF module and the
|
||||||
|
* TTN background task.
|
||||||
|
*
|
||||||
|
* It neither clears the provisioned keys nor the configured pins
|
||||||
|
* but they will be lost if the device goes into deep sleep.
|
||||||
|
*
|
||||||
|
* Before calling this function, use @ref ttn_busy_duration() to check
|
||||||
|
* that the TTN device is idle and ready to go to deep sleep.
|
||||||
|
*
|
||||||
|
* To restart communication, @ref ttn_resume_after_deep_sleep() must be called.
|
||||||
|
*/
|
||||||
|
void ttn_prepare_for_deep_sleep(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stops all activies and prepares for power off.
|
||||||
|
*
|
||||||
|
* This function is called before powering off the device. It saves the current
|
||||||
|
* communication state in NVS (non-volatile storage) and shuts down the RF module
|
||||||
|
* and the TTN background task.
|
||||||
|
*
|
||||||
|
* It neither clears the provisioned keys nor the configured pins
|
||||||
|
* but they will be lost if the device is powered off.
|
||||||
|
*
|
||||||
|
* Before calling this function, use @ref ttn_busy_duration() to check
|
||||||
|
* that the TTN device is idle and ready to be powered off.
|
||||||
|
*
|
||||||
|
* To restart communication, @ref ttn_resume_after_power_off(int) must be called.
|
||||||
|
*
|
||||||
|
* Before this function is called, `nvs_flash_init()` must have been called once.
|
||||||
|
*/
|
||||||
|
void ttn_prepare_for_power_off(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Waits until the TTN device is idle.
|
||||||
|
*
|
||||||
|
* If the TTN device is idle, the ESP32 can go into deep sleep mode
|
||||||
|
* or be powered off without disrupting an on-going communication.
|
||||||
|
*/
|
||||||
|
void ttn_wait_for_idle(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the minimum duration the TTN device will be busy.
|
||||||
|
*
|
||||||
|
* This function can be called to check whether the TTN device is
|
||||||
|
* still involved in communication or ready to go to deep sleep or
|
||||||
|
* to be powered off.
|
||||||
|
*
|
||||||
|
* If it returns 0, the TTN communication is idle and the device can go
|
||||||
|
* to deep sleep or can be powered off.
|
||||||
|
*
|
||||||
|
* If it returns a value different from 0, the value indicates the duration
|
||||||
|
* the device will be certainly busy. After that time, this function must be
|
||||||
|
* called again. It might still return a value different from 0.
|
||||||
|
*
|
||||||
|
* @return busy duration (in FreeRTOS ticks)
|
||||||
|
*/
|
||||||
|
TickType_t ttn_busy_duration(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stops all activies.
|
||||||
|
*
|
||||||
|
* This function shuts down the RF module and the TTN background task. It neither clears the
|
||||||
|
* provisioned keys nor the configured pins. The currentat device state (and activation)
|
||||||
|
* are lost.
|
||||||
|
*
|
||||||
|
* To restart communication, @ref ttn_join_with_keys() and @ref ttn_join() must be called.
|
||||||
|
*/
|
||||||
|
void ttn_shutdown(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transmits a message
|
||||||
|
*
|
||||||
|
* The function blocks until the message could be transmitted and a message has been received
|
||||||
|
* in the subsequent receive window (or the window expires). Additionally, the function will
|
||||||
|
* first wait until the duty cycle allows a transmission (enforcing the duty cycle limits).
|
||||||
|
*
|
||||||
|
* @param payload bytes to be transmitted
|
||||||
|
* @param length number of bytes to be transmitted
|
||||||
|
* @param port port (use 1 as default)
|
||||||
|
* @param confirm flag indicating if a confirmation should be requested (use `false` as default)
|
||||||
|
* @return @ref TTN_SUCCESSFUL_TRANSMISSION for successful transmission, @ref TTN_ERROR_TRANSMISSION_FAILED for
|
||||||
|
* failed transmission, @ref TTN_ERROR_UNEXPECTED for unexpected error
|
||||||
|
*/
|
||||||
|
ttn_response_code_t ttn_transmit_message(const uint8_t *payload, size_t length, ttn_port_t port, bool confirm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the function to be called when a message is received
|
||||||
|
*
|
||||||
|
* When a message is received, the specified function is called. The
|
||||||
|
* message, its length and the port number are provided as
|
||||||
|
* parameters. The values are only valid during the duration of the
|
||||||
|
* callback. So they must be immediately processed or copied.
|
||||||
|
*
|
||||||
|
* Messages are received as a result of @ref ttn_transmit_message(). The callback is called
|
||||||
|
* in the task that called this function and it occurs before this function
|
||||||
|
* returns control to the caller.
|
||||||
|
*
|
||||||
|
* @param callback the callback function
|
||||||
|
*/
|
||||||
|
void ttn_on_message(ttn_message_cb callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if DevEUI, AppEUI/JoinEUI and AppKey have been stored in non-volatile storage
|
||||||
|
* or have been provided by a call to @ref ttn_join_with_keys() or to @ref ttn_provision_transiently().
|
||||||
|
*
|
||||||
|
* @return `true` if they are stored, complete and of the correct size, `false` otherwise
|
||||||
|
*/
|
||||||
|
bool ttn_is_provisioned(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the RSSI calibration value for LBT (Listen Before Talk).
|
||||||
|
*
|
||||||
|
* This value is added to RSSI measured prior to decision. It must include the guardband.
|
||||||
|
* Ignored in US, EU, IN and other countries where LBT is not required.
|
||||||
|
* Defaults to 10 dB.
|
||||||
|
*
|
||||||
|
* @param rssi_cal RSSI calibration value, in dB
|
||||||
|
*/
|
||||||
|
void ttn_set_rssi_cal(int8_t rssi_cal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether Adaptive Data Rate (ADR) is enabled.
|
||||||
|
*
|
||||||
|
* @return `true` if enabled, `false` if disabled
|
||||||
|
*/
|
||||||
|
bool ttn_adr_enabled(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enables or disabled Adaptive Data Rate (ADR).
|
||||||
|
*
|
||||||
|
* ADR is enabled by default. It optimizes data rate, airtime and energy consumption
|
||||||
|
* for devices with stable RF conditions. It should be turned off for mobile devices.
|
||||||
|
*
|
||||||
|
* @param enabled `true` to enable, `false` to disable
|
||||||
|
*/
|
||||||
|
void ttn_set_adr_enabled(bool enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the transmission data rate (i.e. the data rate for uplink messages).
|
||||||
|
*
|
||||||
|
* If ADR is enabled, it's is used as the initial data rate and later adjusted depending
|
||||||
|
* on the RF conditions. If ADR is disabled, it is used for all uplink messages.
|
||||||
|
*
|
||||||
|
* @param data_rate data rate (use constants of enum @ref ttn_data_rate_t)
|
||||||
|
*/
|
||||||
|
void ttn_set_data_rate(ttn_data_rate_t data_rate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the maximum power for transmission
|
||||||
|
*
|
||||||
|
* The power is specified in dBm and sets the power emitted by the radio.
|
||||||
|
* If the antenna has a gain, it must be substracted from the specified value to
|
||||||
|
* achieve the correct transmission power.
|
||||||
|
*
|
||||||
|
* @param tx_pow power, in dBm
|
||||||
|
*/
|
||||||
|
void ttn_set_max_tx_pow(int tx_pow);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets current RX/TX window
|
||||||
|
* @return window
|
||||||
|
*/
|
||||||
|
ttn_rx_tx_window_t ttn_rx_tx_window(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the RF settings for the specified window
|
||||||
|
* @param window RX/TX window (valid values are @ref TTN_WINDOW_TX, @ref TTN_WINDOW_RX1 and @ref TTN_WINDOW_RX2
|
||||||
|
*/
|
||||||
|
ttn_rf_settings_t ttn_get_rf_settings(ttn_rx_tx_window_t window);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the RF settings of the last (or ongoing) transmission.
|
||||||
|
* @return RF settings
|
||||||
|
*/
|
||||||
|
ttn_rf_settings_t ttn_tx_settings(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the RF settings of the last (or ongoing) reception of RX window 1.
|
||||||
|
* @return RF settings
|
||||||
|
*/
|
||||||
|
ttn_rf_settings_t ttn_rx1_settings(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the RF settings of the last (or ongoing) reception of RX window 2.
|
||||||
|
* @return RF settings
|
||||||
|
*/
|
||||||
|
ttn_rf_settings_t ttn_rx2_settings(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the received signal strength indicator (RSSI).
|
||||||
|
*
|
||||||
|
* RSSI is the measured signal strength of the last recevied message (incl. join responses).
|
||||||
|
*
|
||||||
|
* @return RSSI, in dBm
|
||||||
|
*/
|
||||||
|
int ttn_rssi();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -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",
|
||||||
|
@ -1,390 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
*
|
|
||||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
|
||||||
*
|
|
||||||
* Copyright (c) 2018-2019 Manuel Bleichenbacher
|
|
||||||
*
|
|
||||||
* Licensed under MIT License
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*
|
|
||||||
* Circular buffer for detailed logging without affecting LMIC timing.
|
|
||||||
*******************************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
#if LMIC_ENABLE_event_logging
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <esp_log.h>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
#include "lmic/lmic.h"
|
|
||||||
#include "TTNLogging.h"
|
|
||||||
|
|
||||||
|
|
||||||
#define NUM_RINGBUF_MSG 50
|
|
||||||
static const char* const TAG = "lmic";
|
|
||||||
static TTNLogging ttnLog;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Message structure used in ring buffer
|
|
||||||
*
|
|
||||||
* The structure is sent from the LMIC task to the logging task.
|
|
||||||
*/
|
|
||||||
struct TTNLogMessage {
|
|
||||||
const char* message;
|
|
||||||
uint32_t datum;
|
|
||||||
ev_t event;
|
|
||||||
ostime_t time;
|
|
||||||
ostime_t txend;
|
|
||||||
ostime_t globalDutyAvail;
|
|
||||||
u4_t freq;
|
|
||||||
u2_t opmode;
|
|
||||||
u2_t fcntDn;
|
|
||||||
u2_t fcntUp;
|
|
||||||
u2_t rxsyms;
|
|
||||||
rps_t rps;
|
|
||||||
u1_t txChnl;
|
|
||||||
u1_t datarate;
|
|
||||||
u1_t txrxFlags;
|
|
||||||
u1_t saveIrqFlags;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Constants for formatting LORA values
|
|
||||||
static const char* const SF_NAMES[] = { "FSK", "SF7", "SF8", "SF9", "SF10", "SF11", "SF12", "SFrfu" };
|
|
||||||
static const char* const BW_NAMES[] = { "BW125", "BW250", "BW500", "BWrfu" };
|
|
||||||
static const char* const CR_NAMES[] = { "CR 4/5", "CR 4/6", "CR 4/7", "CR 4/8" };
|
|
||||||
static const char* const CRC_NAMES[] = { "NoCrc", "Crc" };
|
|
||||||
|
|
||||||
static void printMessage(TTNLogMessage* log);
|
|
||||||
static void printFatalError(TTNLogMessage* log);
|
|
||||||
static void printEvent(TTNLogMessage* log);
|
|
||||||
static void printEvtJoined(TTNLogMessage* log);
|
|
||||||
static void printEvtJoinFailed(TTNLogMessage* log);
|
|
||||||
static void printEvtTxComplete(TTNLogMessage* log);
|
|
||||||
static void printEvtTxStart(TTNLogMessage* log);
|
|
||||||
static void printEvtRxStart(TTNLogMessage* log);
|
|
||||||
static void printEvtJoinTxComplete(TTNLogMessage* log);
|
|
||||||
static void bin2hex(const uint8_t* bin, unsigned len, char* buf, char sep = 0);
|
|
||||||
|
|
||||||
|
|
||||||
// Create singleton instance
|
|
||||||
TTNLogging* TTNLogging::initInstance()
|
|
||||||
{
|
|
||||||
ttnLog.init();
|
|
||||||
return &ttnLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize logging
|
|
||||||
void TTNLogging::init()
|
|
||||||
{
|
|
||||||
ringBuffer = xRingbufferCreate(NUM_RINGBUF_MSG * sizeof(TTNLogMessage), RINGBUF_TYPE_NOSPLIT);
|
|
||||||
if (ringBuffer == nullptr) {
|
|
||||||
ESP_LOGE(TAG, "Failed to create ring buffer");
|
|
||||||
ASSERT(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
xTaskCreate(loggingTask, "ttn_log", 1024 * 4, ringBuffer, 4, nullptr);
|
|
||||||
hal_set_failure_handler(logFatal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record a logging event for later output
|
|
||||||
void TTNLogging::logEvent(int event, const char* message, uint32_t datum)
|
|
||||||
{
|
|
||||||
if (ringBuffer == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
TTNLogMessage log;
|
|
||||||
log.message = message;
|
|
||||||
log.datum = datum;
|
|
||||||
|
|
||||||
// capture state
|
|
||||||
log.time = os_getTime();
|
|
||||||
log.txend = LMIC.txend;
|
|
||||||
log.globalDutyAvail = LMIC.globalDutyAvail;
|
|
||||||
log.event = (ev_t)event;
|
|
||||||
log.freq = LMIC.freq;
|
|
||||||
log.opmode = LMIC.opmode;
|
|
||||||
log.fcntDn = (u2_t) LMIC.seqnoDn;
|
|
||||||
log.fcntUp = (u2_t) LMIC.seqnoUp;
|
|
||||||
log.rxsyms = LMIC.rxsyms;
|
|
||||||
log.rps = LMIC.rps;
|
|
||||||
log.txChnl = LMIC.txChnl;
|
|
||||||
log.datarate = LMIC.datarate;
|
|
||||||
log.txrxFlags = LMIC.txrxFlags;
|
|
||||||
log.saveIrqFlags = LMIC.saveIrqFlags;
|
|
||||||
|
|
||||||
xRingbufferSend(ringBuffer, &log, sizeof(log), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// record a fatal event (failed assert) for later output
|
|
||||||
void TTNLogging::logFatal(const char* file, uint16_t line)
|
|
||||||
{
|
|
||||||
ttnLog.logEvent(-3, file, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record an informational message for later output
|
|
||||||
// The message must not be freed.
|
|
||||||
extern "C" void LMICOS_logEvent(const char *pMessage)
|
|
||||||
{
|
|
||||||
ttnLog.logEvent(-1, pMessage, 0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record an information message with an integer value for later output
|
|
||||||
// The message must not be freed.
|
|
||||||
extern "C" void LMICOS_logEventUint32(const char *pMessage, uint32_t datum)
|
|
||||||
{
|
|
||||||
ttnLog.logEvent(-2, pMessage, datum);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Log output
|
|
||||||
|
|
||||||
// Tasks that receiveds the recorded messages, formats and outputs them.
|
|
||||||
void TTNLogging::loggingTask(void* param)
|
|
||||||
{
|
|
||||||
RingbufHandle_t ringBuffer = (RingbufHandle_t)param;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
size_t size;
|
|
||||||
TTNLogMessage* log = (TTNLogMessage*) xRingbufferReceive(ringBuffer, &size, portMAX_DELAY);
|
|
||||||
if (log == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
printMessage(log);
|
|
||||||
|
|
||||||
vRingbufferReturnItem(ringBuffer, log);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Format and output a log message
|
|
||||||
void printMessage(TTNLogMessage* log)
|
|
||||||
{
|
|
||||||
switch((int)log->event)
|
|
||||||
{
|
|
||||||
case -1:
|
|
||||||
ESP_LOGI(TAG, "%u (%d ms) - %s: opmode=%x",
|
|
||||||
log->time, osticks2ms(log->time),
|
|
||||||
log->message, log->opmode
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case -2:
|
|
||||||
ESP_LOGI(TAG, "%u (%d ms) - %s: datum=0x%x, opmode=%x)",
|
|
||||||
log->time, osticks2ms(log->time),
|
|
||||||
log->message, log->datum, log->opmode
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case -3:
|
|
||||||
printFatalError(log);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
printEvent(log);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void printFatalError(TTNLogMessage* log)
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "%u (%d ms) - %s, %d",
|
|
||||||
log->time, osticks2ms(log->time),
|
|
||||||
log->message, log->datum
|
|
||||||
);
|
|
||||||
ESP_LOGE(TAG, "- freq=%d.%d, txend=%u, avail=%u, ch=%u",
|
|
||||||
log->freq / 1000000, (log->freq % 1000000) / 100000,
|
|
||||||
log->txend, log->globalDutyAvail,
|
|
||||||
(unsigned)log->txChnl
|
|
||||||
);
|
|
||||||
rps_t rps = log->rps;
|
|
||||||
ESP_LOGE(TAG, "- rps=0x%02x (%s, %s, %s, %s, IH=%d)",
|
|
||||||
rps,
|
|
||||||
SF_NAMES[getSf(rps)],
|
|
||||||
BW_NAMES[getBw(rps)],
|
|
||||||
CR_NAMES[getCr(rps)],
|
|
||||||
CRC_NAMES[getNocrc(rps)],
|
|
||||||
getIh(rps)
|
|
||||||
);
|
|
||||||
ESP_LOGE(TAG, "- opmode=%x, txrxFlags=0x%02x%s, saveIrqFlags=0x%02x",
|
|
||||||
log->opmode,
|
|
||||||
log->txrxFlags,
|
|
||||||
(log->txrxFlags & TXRX_ACK) != 0 ? "; received ack" : "",
|
|
||||||
log->saveIrqFlags
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void printEvent(TTNLogMessage* log)
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "%u (%d ms) - %s",
|
|
||||||
log->time, osticks2ms(log->time),
|
|
||||||
log->message
|
|
||||||
);
|
|
||||||
|
|
||||||
switch((int)log->event)
|
|
||||||
{
|
|
||||||
case EV_JOINED:
|
|
||||||
printEvtJoined(log);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EV_JOIN_FAILED:
|
|
||||||
printEvtJoinFailed(log);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EV_TXCOMPLETE:
|
|
||||||
printEvtTxComplete(log);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EV_TXSTART:
|
|
||||||
printEvtTxStart(log);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EV_RXSTART:
|
|
||||||
printEvtRxStart(log);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EV_JOIN_TXCOMPLETE:
|
|
||||||
printEvtJoinTxComplete(log);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Format and output the detail of a successful network join
|
|
||||||
void printEvtJoined(TTNLogMessage* log)
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "- ch=%d", (unsigned)log->txChnl);
|
|
||||||
|
|
||||||
u4_t netid = 0;
|
|
||||||
devaddr_t devaddr = 0;
|
|
||||||
u1_t nwkKey[16];
|
|
||||||
u1_t artKey[16];
|
|
||||||
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "- netid: %d", netid);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "- devaddr: %08x", devaddr);
|
|
||||||
|
|
||||||
char hexBuf[48];
|
|
||||||
bin2hex((uint8_t*)&artKey, sizeof(artKey), hexBuf, '-');
|
|
||||||
ESP_LOGI(TAG, "- artKey: %s", hexBuf);
|
|
||||||
|
|
||||||
bin2hex((uint8_t*)&nwkKey, sizeof(nwkKey), hexBuf, '-');
|
|
||||||
ESP_LOGI(TAG, "- nwkKey: %s", hexBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Format and output the detail of a failed network join
|
|
||||||
void printEvtJoinFailed(TTNLogMessage* log)
|
|
||||||
{
|
|
||||||
rps_t rps = log->rps;
|
|
||||||
ESP_LOGE(TAG, "- freq=%d.%d, opmode=%x, rps=0x%02x (%s, %s, %s, %s, IH=%d)",
|
|
||||||
log->freq / 1000000, (log->freq % 1000000) / 100000,
|
|
||||||
log->opmode,
|
|
||||||
rps,
|
|
||||||
SF_NAMES[getSf(rps)],
|
|
||||||
BW_NAMES[getBw(rps)],
|
|
||||||
CR_NAMES[getCr(rps)],
|
|
||||||
CRC_NAMES[getNocrc(rps)],
|
|
||||||
getIh(rps)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void printEvtTxComplete(TTNLogMessage* log)
|
|
||||||
{
|
|
||||||
rps_t rps = log->rps;
|
|
||||||
ESP_LOGI(TAG, "- ch=%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)",
|
|
||||||
(unsigned)log->txChnl,
|
|
||||||
rps,
|
|
||||||
SF_NAMES[getSf(rps)],
|
|
||||||
BW_NAMES[getBw(rps)],
|
|
||||||
CR_NAMES[getCr(rps)],
|
|
||||||
CRC_NAMES[getNocrc(rps)],
|
|
||||||
getIh(rps)
|
|
||||||
);
|
|
||||||
ESP_LOGI(TAG, "- txrxFlags=0x%02x%s, FcntUp=%04x, FcntDn=%04x, txend=%u",
|
|
||||||
log->txrxFlags,
|
|
||||||
(log->txrxFlags & TXRX_ACK) != 0 ? "; received ack" : "",
|
|
||||||
log->fcntUp, log->fcntDn,
|
|
||||||
log->txend
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void printEvtTxStart(TTNLogMessage* log)
|
|
||||||
{
|
|
||||||
rps_t rps = log->rps;
|
|
||||||
ESP_LOGI(TAG, "- ch=%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)",
|
|
||||||
(unsigned)log->txChnl,
|
|
||||||
rps,
|
|
||||||
SF_NAMES[getSf(rps)],
|
|
||||||
BW_NAMES[getBw(rps)],
|
|
||||||
CR_NAMES[getCr(rps)],
|
|
||||||
CRC_NAMES[getNocrc(rps)],
|
|
||||||
getIh(rps)
|
|
||||||
);
|
|
||||||
ESP_LOGI(TAG, "- datarate=%u, opmode=%x, txend=%u",
|
|
||||||
log->datarate,
|
|
||||||
log->opmode,
|
|
||||||
log->txend
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void printEvtRxStart(TTNLogMessage* log)
|
|
||||||
{
|
|
||||||
rps_t rps = log->rps;
|
|
||||||
ESP_LOGI(TAG, "- freq=%d.%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)",
|
|
||||||
log->freq / 1000000, (log->freq % 1000000) / 100000,
|
|
||||||
rps,
|
|
||||||
SF_NAMES[getSf(rps)],
|
|
||||||
BW_NAMES[getBw(rps)],
|
|
||||||
CR_NAMES[getCr(rps)],
|
|
||||||
CRC_NAMES[getNocrc(rps)],
|
|
||||||
getIh(rps)
|
|
||||||
);
|
|
||||||
ESP_LOGI(TAG, "- delta=%dms, rxsysm=%u",
|
|
||||||
osticks2ms(log->time - log->txend),
|
|
||||||
log->rxsyms
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void printEvtJoinTxComplete(TTNLogMessage* log)
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "- saveIrqFlags=0x%02x",
|
|
||||||
log->saveIrqFlags
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static const char* HEX_DIGITS = "0123456789ABCDEF";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Convert binary data to hexadecimal representation.
|
|
||||||
*
|
|
||||||
* @param bin start of binary data
|
|
||||||
* @param len length of binary data (in bytes)
|
|
||||||
* @param buf buffer for hexadecimal result
|
|
||||||
* @param sep separator used between bytes (or 0 for none)
|
|
||||||
*/
|
|
||||||
void bin2hex(const uint8_t* bin, unsigned len, char* buf, char sep)
|
|
||||||
{
|
|
||||||
int tgt = 0;
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
if (sep != 0 && i != 0)
|
|
||||||
buf[tgt++] = sep;
|
|
||||||
buf[tgt++] = HEX_DIGITS[bin[i] >> 4];
|
|
||||||
buf[tgt++] = HEX_DIGITS[bin[i] & 0xf];
|
|
||||||
}
|
|
||||||
buf[tgt] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,55 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
*
|
|
||||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
|
||||||
*
|
|
||||||
* Copyright (c) 2018-2019 Manuel Bleichenbacher
|
|
||||||
*
|
|
||||||
* Licensed under MIT License
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*
|
|
||||||
* Circular buffer for detailed logging without affecting LMIC timing.
|
|
||||||
*******************************************************************************/
|
|
||||||
|
|
||||||
#ifndef _ttnlogging_h_
|
|
||||||
#define _ttnlogging_h_
|
|
||||||
|
|
||||||
|
|
||||||
#if LMIC_ENABLE_event_logging
|
|
||||||
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/ringbuf.h>
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Logging class.
|
|
||||||
*
|
|
||||||
* Logs internal information from LMIC in an asynchrnous fashion in order
|
|
||||||
* not to distrub the sensitive LORA timing.
|
|
||||||
*
|
|
||||||
* A ring buffer and a separate logging task is ued. The LMIC core records
|
|
||||||
* relevant values from the current LORA settings and writes them to a ring
|
|
||||||
* buffer. The logging tasks receives the message and the values, formats
|
|
||||||
* them and outputs them via the regular ESP-IDF logging mechanism.
|
|
||||||
*
|
|
||||||
* In order to activate the detailed logging, set the macro
|
|
||||||
* `LMIC_ENABLE_event_logging` to 1.
|
|
||||||
*
|
|
||||||
* This class is not to be used directly.
|
|
||||||
*/
|
|
||||||
class TTNLogging {
|
|
||||||
public:
|
|
||||||
static TTNLogging* initInstance();
|
|
||||||
|
|
||||||
void init();
|
|
||||||
void logEvent(int event, const char* message, uint32_t datum);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void loggingTask(void* param);
|
|
||||||
static void logFatal(const char* file, uint16_t line);
|
|
||||||
|
|
||||||
RingbufHandle_t ringBuffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,73 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
*
|
|
||||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
|
||||||
*
|
|
||||||
* Copyright (c) 2018-2019 Manuel Bleichenbacher
|
|
||||||
*
|
|
||||||
* Licensed under MIT License
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*
|
|
||||||
* Task listening on a UART port for provisioning commands.
|
|
||||||
*******************************************************************************/
|
|
||||||
|
|
||||||
#ifndef _ttnprovisioning_h_
|
|
||||||
#define _ttnprovisioning_h_
|
|
||||||
|
|
||||||
#include "lmic/oslmic.h"
|
|
||||||
#include "nvs_flash.h"
|
|
||||||
|
|
||||||
|
|
||||||
class TTNProvisioning
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TTNProvisioning();
|
|
||||||
|
|
||||||
bool haveKeys();
|
|
||||||
bool decodeKeys(const char *dev_eui, const char *app_eui, const char *app_key);
|
|
||||||
bool fromMAC(const char *app_eui, const char *app_key);
|
|
||||||
bool saveKeys();
|
|
||||||
bool restoreKeys(bool silent);
|
|
||||||
|
|
||||||
#if defined(TTN_HAS_AT_COMMANDS)
|
|
||||||
void startTask();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool decode(bool incl_dev_eui, const char *dev_eui, const char *app_eui, const char *app_key);
|
|
||||||
bool readNvsValue(nvs_handle handle, const char* key, uint8_t* data, size_t expected_length, bool silent);
|
|
||||||
bool writeNvsValue(nvs_handle handle, const char* key, const uint8_t* data, size_t len);
|
|
||||||
|
|
||||||
#if defined(TTN_HAS_AT_COMMANDS)
|
|
||||||
void provisioningTask();
|
|
||||||
void addLineData(int numBytes);
|
|
||||||
void detectLineEnd(int start_at);
|
|
||||||
void processLine();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TTN_CONFIG_UART)
|
|
||||||
void configUART();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static bool hexStrToBin(const char *hex, uint8_t *buf, int len);
|
|
||||||
static int hexTupleToByte(const char *hex);
|
|
||||||
static int hexDigitToVal(char ch);
|
|
||||||
static void binToHexStr(const uint8_t* buf, int len, char* hex);
|
|
||||||
static char valToHexDigit(int val);
|
|
||||||
static void swapBytes(uint8_t* buf, int len);
|
|
||||||
static bool isAllZeros(const uint8_t* buf, int len);
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool have_keys = false;
|
|
||||||
|
|
||||||
#if defined(TTN_HAS_AT_COMMANDS)
|
|
||||||
QueueHandle_t uart_queue;
|
|
||||||
char* line_buf;
|
|
||||||
int line_length;
|
|
||||||
uint8_t last_line_end_char;
|
|
||||||
bool quit_task;
|
|
||||||
|
|
||||||
friend void ttn_provisioning_task_caller(void* pvParameter);
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
611
src/hal/hal_esp32.c
Executable file
@ -0,0 +1,611 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2019 Manuel Bleichenbacher
|
||||||
|
*
|
||||||
|
* Licensed under MIT License
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*
|
||||||
|
* Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#include "hal_esp32.h"
|
||||||
|
#include "../lmic/lmic.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#define LMIC_UNUSED_PIN 0xff
|
||||||
|
|
||||||
|
#define NOTIFY_BIT_DIO 1
|
||||||
|
#define NOTIFY_BIT_TIMER 2
|
||||||
|
#define NOTIFY_BIT_WAKEUP 4
|
||||||
|
#define NOTIFY_BIT_STOP 8
|
||||||
|
|
||||||
|
|
||||||
|
#define TAG "ttn_hal"
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
WAIT_KIND_NONE = 0,
|
||||||
|
WAIT_KIND_CHECK_IO,
|
||||||
|
WAIT_KIND_WAIT_FOR_ANY_EVENT,
|
||||||
|
WAIT_KIND_WAIT_FOR_TIMER
|
||||||
|
} wait_kind_e;
|
||||||
|
|
||||||
|
|
||||||
|
static void lmic_background_task(void* pvParameter);
|
||||||
|
static void qio_irq_handler(void* arg);
|
||||||
|
static void timer_callback(void *arg);
|
||||||
|
static int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time);
|
||||||
|
static int64_t get_current_time();
|
||||||
|
static void init_io(void);
|
||||||
|
static void init_spi(void);
|
||||||
|
static void assert_nss(spi_transaction_t* trans);
|
||||||
|
static void deassert_nss(spi_transaction_t* trans);
|
||||||
|
static void init_timer(void);
|
||||||
|
|
||||||
|
static void set_next_alarm(int64_t time);
|
||||||
|
static void arm_timer(int64_t esp_now);
|
||||||
|
static void disarm_timer(void);
|
||||||
|
static bool wait(wait_kind_e wait_kind);
|
||||||
|
|
||||||
|
static spi_host_device_t spi_host;
|
||||||
|
static gpio_num_t pin_nss;
|
||||||
|
static gpio_num_t pin_rx_tx;
|
||||||
|
static gpio_num_t pin_rst;
|
||||||
|
static gpio_num_t pin_dio0;
|
||||||
|
static gpio_num_t pin_dio1;
|
||||||
|
static int8_t rssi_cal = 10;
|
||||||
|
|
||||||
|
static TaskHandle_t lmic_task;
|
||||||
|
static uint32_t dio_interrupt_time;
|
||||||
|
static uint8_t dio_num;
|
||||||
|
|
||||||
|
static spi_device_handle_t spi_handle;
|
||||||
|
static spi_transaction_t spi_transaction;
|
||||||
|
static SemaphoreHandle_t mutex;
|
||||||
|
static esp_timer_handle_t timer;
|
||||||
|
static int64_t time_offset;
|
||||||
|
static int32_t initial_time_offset;
|
||||||
|
static int64_t next_alarm;
|
||||||
|
static volatile bool run_background_task;
|
||||||
|
static volatile wait_kind_e current_wait_kind;
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// I/O
|
||||||
|
|
||||||
|
void hal_esp32_configure_pins(spi_host_device_t host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1)
|
||||||
|
{
|
||||||
|
spi_host = host;
|
||||||
|
pin_nss = (gpio_num_t)nss;
|
||||||
|
pin_rx_tx = (gpio_num_t)rxtx;
|
||||||
|
pin_rst = (gpio_num_t)rst;
|
||||||
|
pin_dio0 = (gpio_num_t)dio0;
|
||||||
|
pin_dio1 = (gpio_num_t)dio1;
|
||||||
|
|
||||||
|
// Until the background process has been started, use the current task
|
||||||
|
// for supporting calls like `hal_waitUntil()`.
|
||||||
|
lmic_task = xTaskGetCurrentTaskHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void IRAM_ATTR qio_irq_handler(void *arg)
|
||||||
|
{
|
||||||
|
dio_interrupt_time = hal_ticks();
|
||||||
|
dio_num = (u1_t)(long)arg;
|
||||||
|
BaseType_t higher_prio_task_woken = pdFALSE;
|
||||||
|
xTaskNotifyFromISR(lmic_task, NOTIFY_BIT_DIO, eSetBits, &higher_prio_task_woken);
|
||||||
|
if (higher_prio_task_woken)
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_io(void)
|
||||||
|
{
|
||||||
|
// pin_nss, pin_dio0 and pin_dio1 are required
|
||||||
|
ASSERT(pin_nss != LMIC_UNUSED_PIN);
|
||||||
|
ASSERT(pin_dio0 != LMIC_UNUSED_PIN);
|
||||||
|
ASSERT(pin_dio1 != LMIC_UNUSED_PIN);
|
||||||
|
|
||||||
|
gpio_config_t output_pin_config = {
|
||||||
|
.pin_bit_mask = BIT64(pin_nss),
|
||||||
|
.mode = GPIO_MODE_OUTPUT,
|
||||||
|
.pull_up_en = false,
|
||||||
|
.pull_down_en = false,
|
||||||
|
.intr_type = GPIO_INTR_DISABLE
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pin_rx_tx != LMIC_UNUSED_PIN)
|
||||||
|
output_pin_config.pin_bit_mask |= BIT64(pin_rx_tx);
|
||||||
|
|
||||||
|
if (pin_rst != LMIC_UNUSED_PIN)
|
||||||
|
output_pin_config.pin_bit_mask |= BIT64(pin_rst);
|
||||||
|
|
||||||
|
gpio_config(&output_pin_config);
|
||||||
|
|
||||||
|
gpio_set_level(pin_nss, 1);
|
||||||
|
if (pin_rx_tx != LMIC_UNUSED_PIN)
|
||||||
|
gpio_set_level(pin_rx_tx, 0);
|
||||||
|
if (pin_rst != LMIC_UNUSED_PIN)
|
||||||
|
gpio_set_level(pin_rst, 0);
|
||||||
|
|
||||||
|
// DIO pins with interrupt handlers
|
||||||
|
gpio_config_t input_pin_config = {
|
||||||
|
.pin_bit_mask = BIT64(pin_dio0) | BIT64(pin_dio1),
|
||||||
|
.mode = GPIO_MODE_INPUT,
|
||||||
|
.pull_up_en = false,
|
||||||
|
.pull_down_en = true,
|
||||||
|
.intr_type = GPIO_INTR_POSEDGE,
|
||||||
|
};
|
||||||
|
gpio_config(&input_pin_config);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "IO initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) // duplicate this symbol if your pin is controlled by e.g. I2C
|
||||||
|
void hal_pin_rxtx(u1_t val)
|
||||||
|
{
|
||||||
|
if (pin_rx_tx == LMIC_UNUSED_PIN)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gpio_set_level(pin_rx_tx, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) // duplicate this symbol if your pin is controlled by e.g. I2C
|
||||||
|
void hal_pin_rst(u1_t val)
|
||||||
|
{
|
||||||
|
if (pin_rst == LMIC_UNUSED_PIN)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (val == 0 || val == 1)
|
||||||
|
{
|
||||||
|
// drive pin
|
||||||
|
gpio_set_level(pin_rst, val);
|
||||||
|
gpio_set_direction(pin_rst, GPIO_MODE_OUTPUT);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if defined(CONFIG_TTN_RESET_STATES_ASSERTED)
|
||||||
|
// drive up the pin because the hardware is nonstandard
|
||||||
|
gpio_set_level(pin_rst, 1);
|
||||||
|
gpio_set_direction(pin_rst, GPIO_MODE_OUTPUT);
|
||||||
|
#else
|
||||||
|
// keep pin floating
|
||||||
|
gpio_set_direction(pin_rst, GPIO_MODE_INPUT);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s1_t hal_getRssiCal(void)
|
||||||
|
{
|
||||||
|
return rssi_cal;
|
||||||
|
}
|
||||||
|
|
||||||
|
ostime_t hal_setModuleActive(bit_t val)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bit_t hal_queryUsingTcxo(void)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t hal_getTxPowerPolicy(u1_t inputPolicy, s1_t requestedPower, u4_t frequency)
|
||||||
|
{
|
||||||
|
return LMICHAL_radio_tx_power_policy_paboost;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// SPI
|
||||||
|
|
||||||
|
void init_spi(void)
|
||||||
|
{
|
||||||
|
// init device
|
||||||
|
spi_device_interface_config_t spi_config = {
|
||||||
|
.mode = 0,
|
||||||
|
.clock_speed_hz = CONFIG_TTN_SPI_FREQ,
|
||||||
|
.command_bits = 0,
|
||||||
|
.address_bits = 8,
|
||||||
|
.spics_io_num = -1,
|
||||||
|
.queue_size = 1,
|
||||||
|
.pre_cb = assert_nss,
|
||||||
|
.post_cb = deassert_nss,
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t ret = spi_bus_add_device(spi_host, &spi_config, &spi_handle);
|
||||||
|
ESP_ERROR_CHECK(ret);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "SPI initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_spi_write(u1_t cmd, const u1_t *buf, size_t len)
|
||||||
|
{
|
||||||
|
memset(&spi_transaction, 0, sizeof(spi_transaction));
|
||||||
|
spi_transaction.addr = cmd;
|
||||||
|
spi_transaction.length = 8 * len;
|
||||||
|
spi_transaction.tx_buffer = buf;
|
||||||
|
esp_err_t err = spi_device_transmit(spi_handle, &spi_transaction);
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_spi_read(u1_t cmd, u1_t *buf, size_t len)
|
||||||
|
{
|
||||||
|
memset(buf, 0, len);
|
||||||
|
memset(&spi_transaction, 0, sizeof(spi_transaction));
|
||||||
|
spi_transaction.addr = cmd;
|
||||||
|
spi_transaction.length = 8 * len;
|
||||||
|
spi_transaction.rxlength = 8 * len;
|
||||||
|
spi_transaction.tx_buffer = buf;
|
||||||
|
spi_transaction.rx_buffer = buf;
|
||||||
|
esp_err_t err = spi_device_transmit(spi_handle, &spi_transaction);
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void IRAM_ATTR assert_nss(spi_transaction_t* trans)
|
||||||
|
{
|
||||||
|
gpio_set_level(pin_nss, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR deassert_nss(spi_transaction_t* trans)
|
||||||
|
{
|
||||||
|
gpio_set_level(pin_nss, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// TIME
|
||||||
|
|
||||||
|
/*
|
||||||
|
* LIMIC uses a 32 bit time system (ostime_t) counting ticks. In this
|
||||||
|
* implementation, each tick is 16µs. It will wrap arounnd every 19 hours.
|
||||||
|
*
|
||||||
|
* The ESP32 has a 64 bit timer counting microseconds. It will wrap around
|
||||||
|
* every 584,000 years. So we don't need to bother.
|
||||||
|
*
|
||||||
|
* The time includes an offset initialized from `gettimeofday()`
|
||||||
|
* to ensure the time correctly advances during deep sleep.
|
||||||
|
*
|
||||||
|
* Based on this timer, future callbacks can be scheduled. This is used to
|
||||||
|
* schedule the next LMIC job.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Convert LMIC tick time (ostime_t) to ESP absolute time.
|
||||||
|
// `os_time` is assumed to be somewhere between one hour in the past and
|
||||||
|
// 18 hours into the future.
|
||||||
|
int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time)
|
||||||
|
{
|
||||||
|
int64_t esp_time;
|
||||||
|
uint32_t os_now = (uint32_t)(esp_now >> 4);
|
||||||
|
|
||||||
|
// unsigned difference:
|
||||||
|
// 0x00000000 - 0xefffffff: future (0 to about 18 hours)
|
||||||
|
// 0xf0000000 - 0xffffffff: past (about 1 to 0 hours)
|
||||||
|
uint32_t os_diff = os_time - os_now;
|
||||||
|
if (os_diff < 0xf0000000)
|
||||||
|
{
|
||||||
|
esp_time = esp_now + (((int64_t)os_diff) << 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
os_diff = -os_diff;
|
||||||
|
esp_time = esp_now - (((int64_t)os_diff) << 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return esp_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t get_current_time()
|
||||||
|
{
|
||||||
|
return esp_timer_get_time() + time_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_timer(void)
|
||||||
|
{
|
||||||
|
esp_timer_create_args_t timer_config = {
|
||||||
|
.callback = &timer_callback,
|
||||||
|
.arg = NULL,
|
||||||
|
.dispatch_method = ESP_TIMER_TASK,
|
||||||
|
.name = "lmic_job"
|
||||||
|
};
|
||||||
|
esp_err_t err = esp_timer_create(&timer_config, &timer);
|
||||||
|
ESP_ERROR_CHECK(err);
|
||||||
|
|
||||||
|
struct timeval now;
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
time_offset += (int64_t)now.tv_sec * 1000000;
|
||||||
|
initial_time_offset = 0;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Timer initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t hal_esp32_get_time(void)
|
||||||
|
{
|
||||||
|
struct timeval now;
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
return now.tv_sec + initial_time_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_esp32_set_time(uint32_t time_val)
|
||||||
|
{
|
||||||
|
initial_time_offset = time_val;
|
||||||
|
time_offset = (int64_t)time_val * 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_next_alarm(int64_t time)
|
||||||
|
{
|
||||||
|
next_alarm = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arm_timer(int64_t esp_now)
|
||||||
|
{
|
||||||
|
if (next_alarm == 0)
|
||||||
|
return;
|
||||||
|
int64_t timeout = next_alarm - get_current_time();
|
||||||
|
if (timeout < 0)
|
||||||
|
timeout = 10;
|
||||||
|
esp_timer_start_once(timer, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void disarm_timer(void)
|
||||||
|
{
|
||||||
|
esp_timer_stop(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void timer_callback(void *arg)
|
||||||
|
{
|
||||||
|
xTaskNotify(lmic_task, NOTIFY_BIT_TIMER, eSetBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the next external event. Either:
|
||||||
|
// - scheduled timer due to scheduled job or waiting for a given time
|
||||||
|
// - wake up event from the client code
|
||||||
|
// - I/O interrupt (DIO0 or DIO1 pin)
|
||||||
|
bool wait(wait_kind_e wait_kind)
|
||||||
|
{
|
||||||
|
TickType_t ticks_to_wait = wait_kind == WAIT_KIND_CHECK_IO ? 0 : portMAX_DELAY;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
current_wait_kind = wait_kind;
|
||||||
|
uint32_t bits = ulTaskNotifyTake(pdTRUE, ticks_to_wait);
|
||||||
|
current_wait_kind = WAIT_KIND_NONE;
|
||||||
|
if (bits == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((bits & NOTIFY_BIT_STOP) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((bits & NOTIFY_BIT_WAKEUP) != 0)
|
||||||
|
{
|
||||||
|
if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER)
|
||||||
|
{
|
||||||
|
disarm_timer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((bits & NOTIFY_BIT_TIMER) != 0)
|
||||||
|
{
|
||||||
|
disarm_timer();
|
||||||
|
set_next_alarm(0);
|
||||||
|
if (wait_kind != WAIT_KIND_CHECK_IO)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else // IO interrupt
|
||||||
|
{
|
||||||
|
if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER)
|
||||||
|
disarm_timer();
|
||||||
|
hal_esp32_enter_critical_section();
|
||||||
|
radio_irq_handler_v2(dio_num, dio_interrupt_time);
|
||||||
|
hal_esp32_leave_critical_section();
|
||||||
|
if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TickType_t hal_esp32_get_timer_duration(void)
|
||||||
|
{
|
||||||
|
wait_kind_e wait_kind = current_wait_kind;
|
||||||
|
int64_t alarm_time = next_alarm;
|
||||||
|
|
||||||
|
if (wait_kind == WAIT_KIND_NONE || wait_kind == WAIT_KIND_CHECK_IO)
|
||||||
|
return 1; // busy, not waiting
|
||||||
|
|
||||||
|
if (alarm_time != 0)
|
||||||
|
{
|
||||||
|
TickType_t dur = pdMS_TO_TICKS((alarm_time - get_current_time() + 999) / 1000);
|
||||||
|
if (dur > pdMS_TO_TICKS(30000))
|
||||||
|
dur = pdMS_TO_TICKS(200);
|
||||||
|
return dur;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return 0; // waiting indefinitely
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Gets current time in LMIC ticks
|
||||||
|
u4_t IRAM_ATTR hal_ticks(void)
|
||||||
|
{
|
||||||
|
// LMIC tick unit: 16µs
|
||||||
|
// esp_timer unit: 1µs
|
||||||
|
return (u4_t)(get_current_time() >> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the specified time.
|
||||||
|
// Called if the LMIC code needs to wait for a precise time.
|
||||||
|
// All other events are ignored and will be served later.
|
||||||
|
u4_t hal_waitUntil(u4_t time)
|
||||||
|
{
|
||||||
|
int64_t esp_now = get_current_time();
|
||||||
|
int64_t esp_time = os_time_to_esp_time(esp_now, time);
|
||||||
|
set_next_alarm(esp_time);
|
||||||
|
arm_timer(esp_now);
|
||||||
|
wait(WAIT_KIND_WAIT_FOR_TIMER);
|
||||||
|
|
||||||
|
u4_t os_now = hal_ticks();
|
||||||
|
u4_t diff = os_now - time;
|
||||||
|
return diff < 0x80000000U ? diff : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by client code to wake up LMIC to do something,
|
||||||
|
// e.g. send a submitted messages.
|
||||||
|
void hal_esp32_wake_up(void)
|
||||||
|
{
|
||||||
|
xTaskNotify(lmic_task, NOTIFY_BIT_WAKEUP, eSetBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the specified time has been reached or almost reached.
|
||||||
|
// Otherwise, save it as alarm time.
|
||||||
|
// LMIC calls this function with the scheduled time of the next job
|
||||||
|
// in the queue. If the job is not due yet, LMIC will go to sleep.
|
||||||
|
u1_t hal_checkTimer(uint32_t time)
|
||||||
|
{
|
||||||
|
int64_t esp_now = get_current_time();
|
||||||
|
int64_t esp_time = os_time_to_esp_time(esp_now, time);
|
||||||
|
int64_t diff = esp_time - esp_now;
|
||||||
|
if (diff < 100)
|
||||||
|
return 1; // timer has expired or will expire very soon
|
||||||
|
|
||||||
|
set_next_alarm(esp_time);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to sleep until next event.
|
||||||
|
// Called when LMIC is not busy and not job is due to be executed.
|
||||||
|
void hal_sleep(void)
|
||||||
|
{
|
||||||
|
if (wait(WAIT_KIND_CHECK_IO))
|
||||||
|
return;
|
||||||
|
|
||||||
|
arm_timer(get_current_time());
|
||||||
|
wait(WAIT_KIND_WAIT_FOR_ANY_EVENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// IRQ
|
||||||
|
|
||||||
|
void hal_disableIRQs(void)
|
||||||
|
{
|
||||||
|
// nothing to do as interrupt handlers post message to queue
|
||||||
|
// and don't access any shared data structures
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_enableIRQs(void)
|
||||||
|
{
|
||||||
|
// nothing to do as interrupt handlers post message to queue
|
||||||
|
// and don't access any shared data structures
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_processPendingIRQs(void)
|
||||||
|
{
|
||||||
|
// nothing to do as interrupt handlers post message to queue
|
||||||
|
// and don't access any shared data structures
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Synchronization between application code and background task
|
||||||
|
|
||||||
|
void hal_esp32_init_critical_section(void)
|
||||||
|
{
|
||||||
|
mutex = xSemaphoreCreateRecursiveMutex();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_esp32_enter_critical_section(void)
|
||||||
|
{
|
||||||
|
xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_esp32_leave_critical_section(void)
|
||||||
|
{
|
||||||
|
xSemaphoreGiveRecursive(mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void lmic_background_task(void* pvParameter)
|
||||||
|
{
|
||||||
|
while (run_background_task)
|
||||||
|
os_runloop_once();
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_init_ex(const void *pContext)
|
||||||
|
{
|
||||||
|
// configure radio I/O and interrupt handler
|
||||||
|
init_io();
|
||||||
|
// configure radio SPI
|
||||||
|
init_spi();
|
||||||
|
// configure timer and alarm callback
|
||||||
|
init_timer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_esp32_start_lmic_task(void)
|
||||||
|
{
|
||||||
|
run_background_task = true;
|
||||||
|
xTaskCreate(lmic_background_task, "ttn_lmic", 1024 * 4, NULL, CONFIG_TTN_BG_TASK_PRIO, &lmic_task);
|
||||||
|
|
||||||
|
// enable interrupts
|
||||||
|
gpio_isr_handler_add(pin_dio0, qio_irq_handler, (void *)0);
|
||||||
|
gpio_isr_handler_add(pin_dio1, qio_irq_handler, (void *)1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_esp32_stop_lmic_task(void)
|
||||||
|
{
|
||||||
|
run_background_task = false;
|
||||||
|
gpio_isr_handler_remove(pin_dio0);
|
||||||
|
gpio_isr_handler_remove(pin_dio1);
|
||||||
|
disarm_timer();
|
||||||
|
set_next_alarm(0);
|
||||||
|
xTaskNotify(lmic_task, NOTIFY_BIT_STOP, eSetBits);
|
||||||
|
lmic_task = xTaskGetCurrentTaskHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Fatal failure
|
||||||
|
|
||||||
|
static hal_failure_handler_t* custom_hal_failure_handler = NULL;
|
||||||
|
|
||||||
|
void hal_set_failure_handler(hal_failure_handler_t* const handler)
|
||||||
|
{
|
||||||
|
custom_hal_failure_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hal_failed(const char *file, u2_t line)
|
||||||
|
{
|
||||||
|
if (custom_hal_failure_handler != NULL)
|
||||||
|
(*custom_hal_failure_handler)(file, line);
|
||||||
|
|
||||||
|
ESP_LOGE(TAG, "LMIC failed and stopped: %s:%d", file, line);
|
||||||
|
|
||||||
|
// go to sleep forever
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
vTaskDelay(portMAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// RSSI
|
||||||
|
|
||||||
|
void hal_esp32_set_rssi_cal(int8_t cal)
|
||||||
|
{
|
||||||
|
rssi_cal = cal;
|
||||||
|
}
|
@ -1,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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
@ -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_
|
||||||
|
@ -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
|
||||||
|
350
src/lmic/lmic.c
350
src/lmic/lmic.c
@ -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;
|
||||||
|
}
|
||||||
|
141
src/lmic/lmic.h
141
src/lmic/lmic.h
@ -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_
|
||||||
|
@ -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
|
||||||
|
@ -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
|
@ -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_
|
||||||
|
@ -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_
|
||||||
|
@ -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_
|
@ -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_
|
||||||
|
@ -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_
|
||||||
|
@ -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_
|
||||||
|
@ -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_
|
||||||
|
217
src/lmic/lmic_channelshuffle.c
Normal file
217
src/lmic/lmic_channelshuffle.c
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Module: lmic_channelshuffle.c
|
||||||
|
|
||||||
|
Function:
|
||||||
|
Channel scheduling without replacement.
|
||||||
|
|
||||||
|
Copyright and License:
|
||||||
|
This file copyright (C) 2021 by
|
||||||
|
|
||||||
|
MCCI Corporation
|
||||||
|
3520 Krums Corners Road
|
||||||
|
Ithaca, NY 14850
|
||||||
|
|
||||||
|
See accompanying LICENSE file for copyright and license information.
|
||||||
|
|
||||||
|
Author:
|
||||||
|
Terry Moore, MCCI Corporation April 2021
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "lmic.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/****************************************************************************\
|
||||||
|
|
|
||||||
|
| Manifest constants and local declarations.
|
||||||
|
|
|
||||||
|
\****************************************************************************/
|
||||||
|
|
||||||
|
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries);
|
||||||
|
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum);
|
||||||
|
|
||||||
|
/****************************************************************************\
|
||||||
|
|
|
||||||
|
| Read-only data.
|
||||||
|
|
|
||||||
|
\****************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************************************\
|
||||||
|
|
|
||||||
|
| Variables.
|
||||||
|
|
|
||||||
|
\****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Name: LMIC_findNextChannel()
|
||||||
|
|
||||||
|
Function:
|
||||||
|
Scan a shuffle mask, and select a channel (without replacement).
|
||||||
|
|
||||||
|
Definition:
|
||||||
|
int LMIC_findNextChannel(
|
||||||
|
uint16_t *pShuffleMask,
|
||||||
|
const uint16_t *pEnableMask,
|
||||||
|
uint16_t nEntries,
|
||||||
|
int lastChannel
|
||||||
|
);
|
||||||
|
|
||||||
|
Description:
|
||||||
|
pShuffleMask and pEnableMask are bit vectors. Channels correspond to
|
||||||
|
bits in little-endian order; entry [0] has channels 0 through 15, entry
|
||||||
|
[1] channels 16 through 31, and so forth. nEntries specifies the number
|
||||||
|
of entries in the mask vectors. The enable mask is 1 for a given channel
|
||||||
|
if that channel is eligible for selection, 0 otherwise.
|
||||||
|
|
||||||
|
This routine selects channels from the shuffle mask until all entries
|
||||||
|
are exhausted; it then refreshes the shuffle mask from the enable mask.
|
||||||
|
|
||||||
|
If it refreshes the channel mask, lastChannel is taken as a channel number
|
||||||
|
that is to be avoided in the next selection. (This is to avoid back-to-back
|
||||||
|
use of a channel across a refresh boundary.) Otherwise lastChannel is
|
||||||
|
ignored. This avoidance can be suppresed by setting lastChannel to -1.
|
||||||
|
If only one channel is enabled, lastChannel is also ignored. If lastChannel
|
||||||
|
is actually disabled, lastChannel is also ignored.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A channel number, in 0 .. nEntries-1, or -1 if the enable mask is
|
||||||
|
identically zero.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
This routine is somewhat optimized for AVR processors, which don't have
|
||||||
|
multi-bit shifts.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
int LMIC_findNextChannel(
|
||||||
|
uint16_t *pShuffleMask,
|
||||||
|
const uint16_t *pEnableMask,
|
||||||
|
uint16_t nEntries,
|
||||||
|
int lastChannel
|
||||||
|
) {
|
||||||
|
unsigned nSet16;
|
||||||
|
uint16_t saveLastChannelVal;
|
||||||
|
|
||||||
|
// in case someone has changed the enable mask, update
|
||||||
|
// the shuffle mask so there are no disable bits set.
|
||||||
|
for (unsigned i = 0; i < nEntries; ++i) {
|
||||||
|
pShuffleMask[i] &= pEnableMask[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// count the set bits in the shuffle mask (with a factor of 16 for speed)
|
||||||
|
nSet16 = sidewaysSum16(pShuffleMask, nEntries);
|
||||||
|
|
||||||
|
// if zero, copy the enable mask to the shuffle mask, and recount
|
||||||
|
if (nSet16 == 0) {
|
||||||
|
memcpy(pShuffleMask, pEnableMask, nEntries * sizeof(*pShuffleMask));
|
||||||
|
nSet16 = sidewaysSum16(pShuffleMask, nEntries);
|
||||||
|
} else {
|
||||||
|
// don't try to skip the last channel because it can't be chosen.
|
||||||
|
lastChannel = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if still zero, return -1.
|
||||||
|
if (nSet16 == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have to skip a channel, and we have more than one choice, turn off
|
||||||
|
// the last channel bit. Post condition: if we really clered a bit,
|
||||||
|
// saveLastChannelVal will be non-zero.
|
||||||
|
saveLastChannelVal = 0;
|
||||||
|
if (nSet16 > 16 && lastChannel >= 0 && lastChannel <= (int)(nEntries * 16)) {
|
||||||
|
uint16_t const saveLastChannelMask = (1 << (lastChannel & 0xF));
|
||||||
|
|
||||||
|
saveLastChannelVal = pShuffleMask[lastChannel >> 4] & saveLastChannelMask;
|
||||||
|
pShuffleMask[lastChannel >> 4] &= ~saveLastChannelMask;
|
||||||
|
|
||||||
|
// if we cleared a bit, reduce the count.
|
||||||
|
if (saveLastChannelVal > 0)
|
||||||
|
nSet16 -= 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saveLastChannelVal == 0) {
|
||||||
|
// We didn't eliminate a channel, so we don't have to worry.
|
||||||
|
lastChannel = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a random number
|
||||||
|
unsigned choice = os_getRndU2() % ((uint16_t)nSet16 >> 4);
|
||||||
|
|
||||||
|
// choose a bit based on set bit
|
||||||
|
unsigned channel = findNthSetBit(pShuffleMask, choice);
|
||||||
|
pShuffleMask[channel / 16] ^= (1 << (channel & 0xF));
|
||||||
|
|
||||||
|
// handle channel skip
|
||||||
|
if (lastChannel >= 0) {
|
||||||
|
pShuffleMask[lastChannel >> 4] |= saveLastChannelVal;
|
||||||
|
}
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries) {
|
||||||
|
unsigned result;
|
||||||
|
|
||||||
|
result = 0;
|
||||||
|
for (; nEntries > 0; --nEntries, ++pMask)
|
||||||
|
{
|
||||||
|
uint16_t v = *pMask;
|
||||||
|
|
||||||
|
// the following is an adaptation of Knuth 7.1.3 (62). To avoid
|
||||||
|
// lots of shifts (slow on AVR, and code intensive) and table lookups,
|
||||||
|
// we sum popc * 16, then divide by 16.
|
||||||
|
|
||||||
|
// sum adjacent bits, making a series of 2-bit sums
|
||||||
|
v = v - ((v >> 1) & 0x5555u);
|
||||||
|
v = (v & 0x3333u) + ((v >> 2) & 0x3333u);
|
||||||
|
// this assumes multiplies are essentialy free;
|
||||||
|
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u);
|
||||||
|
// Accumulate result, but note it's times 16.
|
||||||
|
// AVR compiler should optimize the x8 shift.
|
||||||
|
result += (v & 0xFF) + (v >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum) {
|
||||||
|
unsigned result;
|
||||||
|
result = 0;
|
||||||
|
bitnum = bitnum * 16;
|
||||||
|
for (;; result += 16) {
|
||||||
|
uint16_t m = *pMask++;
|
||||||
|
if (m == 0)
|
||||||
|
continue;
|
||||||
|
uint16_t v = m - ((m >> 1) & 0x5555u);
|
||||||
|
v = (v & 0x3333u) + ((v >> 2) & 0x3333u);
|
||||||
|
// this assumes multiplies are essentialy free;
|
||||||
|
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u);
|
||||||
|
// Accumulate result, but note it's times 16.
|
||||||
|
// AVR compiler should optimize the x8 shift.
|
||||||
|
v = (v & 0xFF) + (v >> 8);
|
||||||
|
if (v <= bitnum)
|
||||||
|
bitnum -= v;
|
||||||
|
else {
|
||||||
|
// the selected bit is in this word. We need to count.
|
||||||
|
while (bitnum > 0) {
|
||||||
|
m &= m - 1;
|
||||||
|
bitnum -= 16;
|
||||||
|
}
|
||||||
|
// now the lsb of m is our choice.
|
||||||
|
// get a mask, then use Knuth 7.1.3 (59) to find the
|
||||||
|
// bit number.
|
||||||
|
m &= -m;
|
||||||
|
result += ((m & 0x5555u) ? 0 : 1)
|
||||||
|
+ ((m & 0x3333u) ? 0 : 2)
|
||||||
|
+ ((m & 0x0F0Fu) ? 0 : 4)
|
||||||
|
+ ((m & 0x00FFu) ? 0 : 8)
|
||||||
|
;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
72
src/lmic/lmic_compat.h
Normal file
72
src/lmic/lmic_compat.h
Normal 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_ */
|
@ -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();
|
||||||
|
@ -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_ */
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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_ */
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
216
src/lmic/radio.c
216
src/lmic/radio.c
@ -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
562
src/ttn.c
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2021 Manuel Bleichenbacher
|
||||||
|
*
|
||||||
|
* Licensed under MIT License
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*
|
||||||
|
* High-level C API for ttn-esp32.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#include "ttn.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "hal/hal_esp32.h"
|
||||||
|
#include "lmic/lmic.h"
|
||||||
|
#include "ttn_logging.h"
|
||||||
|
#include "ttn_provisioning.h"
|
||||||
|
#include "ttn_nvs.h"
|
||||||
|
#include "ttn_rtc.h"
|
||||||
|
|
||||||
|
#define TAG "ttn"
|
||||||
|
|
||||||
|
#define DEFAULT_MAX_TX_POWER -1000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reason the user code is waiting
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
TTN_WAITING_NONE,
|
||||||
|
TTN_WAITING_FOR_JOIN,
|
||||||
|
TTN_WAITING_FOR_TRANSMISSION
|
||||||
|
} ttn_waiting_reason_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event type
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
TTN_EVENT_NONE,
|
||||||
|
TTN_EVNT_JOIN_COMPLETED,
|
||||||
|
TTN_EVENT_JOIN_FAILED,
|
||||||
|
TTN_EVENT_MESSAGE_RECEIVED,
|
||||||
|
TTN_EVENT_TRANSMISSION_COMPLETED,
|
||||||
|
TTN_EVENT_TRANSMISSION_FAILED
|
||||||
|
} ttn_event_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event message sent from LMIC task to waiting client task
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ttn_event_t event;
|
||||||
|
uint8_t port;
|
||||||
|
const uint8_t *message;
|
||||||
|
size_t message_size;
|
||||||
|
} ttn_lmic_event_t;
|
||||||
|
|
||||||
|
static bool is_started;
|
||||||
|
static bool has_joined;
|
||||||
|
static QueueHandle_t lmic_event_queue;
|
||||||
|
static ttn_message_cb message_callback;
|
||||||
|
static ttn_waiting_reason_t waiting_reason;
|
||||||
|
static ttn_rf_settings_t last_rf_settings[4];
|
||||||
|
static ttn_rx_tx_window_t current_rx_tx_window;
|
||||||
|
static int subband = 2;
|
||||||
|
static ttn_data_rate_t join_data_rate = TTN_DR_JOIN_DEFAULT;
|
||||||
|
static int max_tx_power = DEFAULT_MAX_TX_POWER;
|
||||||
|
|
||||||
|
static void start(void);
|
||||||
|
static void stop(void);
|
||||||
|
static bool join_core(void);
|
||||||
|
static void config_rf_params(void);
|
||||||
|
static void event_callback(void *user_data, ev_t event);
|
||||||
|
static void message_received_callback(void *user_data, uint8_t port, const uint8_t *message, size_t message_size);
|
||||||
|
static void message_transmitted_callback(void *user_data, int success);
|
||||||
|
static void save_rf_settings(ttn_rf_settings_t *rf_settings);
|
||||||
|
static void clear_rf_settings(ttn_rf_settings_t *rf_settings);
|
||||||
|
|
||||||
|
void ttn_init(void)
|
||||||
|
{
|
||||||
|
#if defined(TTN_IS_DISABLED)
|
||||||
|
ESP_LOGE(TAG, "TTN is disabled. Configure a frequency plan using 'make menuconfig'");
|
||||||
|
ASSERT(0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
message_callback = NULL;
|
||||||
|
hal_esp32_init_critical_section();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ttn_configure_pins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1)
|
||||||
|
{
|
||||||
|
hal_esp32_configure_pins(spi_host, nss, rxtx, rst, dio0, dio1);
|
||||||
|
|
||||||
|
#if LMIC_ENABLE_event_logging
|
||||||
|
ttn_log_init();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ttn_set_subband(int band)
|
||||||
|
{
|
||||||
|
subband = band;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(void)
|
||||||
|
{
|
||||||
|
if (is_started)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LMIC_registerEventCb(event_callback, NULL);
|
||||||
|
LMIC_registerRxMessageCb(message_received_callback, NULL);
|
||||||
|
|
||||||
|
os_init_ex(NULL);
|
||||||
|
hal_esp32_enter_critical_section();
|
||||||
|
LMIC_reset();
|
||||||
|
LMIC_setClockError(MAX_CLOCK_ERROR * 4 / 100);
|
||||||
|
waiting_reason = TTN_WAITING_NONE;
|
||||||
|
hal_esp32_leave_critical_section();
|
||||||
|
|
||||||
|
lmic_event_queue = xQueueCreate(4, sizeof(ttn_lmic_event_t));
|
||||||
|
ASSERT(lmic_event_queue != NULL);
|
||||||
|
hal_esp32_start_lmic_task();
|
||||||
|
is_started = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop(void)
|
||||||
|
{
|
||||||
|
if (!is_started)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hal_esp32_enter_critical_section();
|
||||||
|
LMIC_shutdown();
|
||||||
|
hal_esp32_stop_lmic_task();
|
||||||
|
waiting_reason = TTN_WAITING_NONE;
|
||||||
|
hal_esp32_leave_critical_section();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ttn_shutdown(void)
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ttn_provision(const char *dev_eui, const char *app_eui, const char *app_key)
|
||||||
|
{
|
||||||
|
if (!ttn_provisioning_decode_keys(dev_eui, app_eui, app_key))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return ttn_provisioning_save_keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ttn_provision_transiently(const char *dev_eui, const char *app_eui, const char *app_key)
|
||||||
|
{
|
||||||
|
return ttn_provisioning_decode_keys(dev_eui, app_eui, app_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ttn_provision_with_mac(const char *app_eui, const char *app_key)
|
||||||
|
{
|
||||||
|
if (!ttn_provisioning_from_mac(app_eui, app_key))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return ttn_provisioning_save_keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ttn_start_provisioning_task(void)
|
||||||
|
{
|
||||||
|
#if defined(TTN_HAS_AT_COMMANDS)
|
||||||
|
ttn_provisioning_start_task();
|
||||||
|
#else
|
||||||
|
ESP_LOGE(TAG, "AT commands are disabled. Change the configuration using 'make menuconfig'");
|
||||||
|
ASSERT(0);
|
||||||
|
esp_restart();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ttn_wait_for_provisioning(void)
|
||||||
|
{
|
||||||
|
#if defined(TTN_HAS_AT_COMMANDS)
|
||||||
|
if (ttn_is_provisioned())
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Device is already provisioned");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!ttn_provisioning_have_keys())
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Device successfully provisioned");
|
||||||
|
#else
|
||||||
|
ESP_LOGE(TAG, "AT commands are disabled. Change the configuration using 'make menuconfig'");
|
||||||
|
ASSERT(0);
|
||||||
|
esp_restart();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ttn_join_with_keys(const char *dev_eui, const char *app_eui, const char *app_key)
|
||||||
|
{
|
||||||
|
if (!ttn_provisioning_decode_keys(dev_eui, app_eui, app_key))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return join_core();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ttn_join(void)
|
||||||
|
{
|
||||||
|
if (!ttn_provisioning_have_keys())
|
||||||
|
{
|
||||||
|
if (!ttn_provisioning_restore_keys(false))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return join_core();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ttn_resume_after_deep_sleep(void)
|
||||||
|
{
|
||||||
|
if (!ttn_provisioning_have_keys())
|
||||||
|
{
|
||||||
|
if (!ttn_provisioning_restore_keys(false))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ttn_provisioning_have_keys())
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
|
|
||||||
|
if (!ttn_rtc_restore())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
has_joined = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ttn_resume_after_power_off(int off_duration)
|
||||||
|
{
|
||||||
|
if (!ttn_provisioning_have_keys())
|
||||||
|
{
|
||||||
|
if (!ttn_provisioning_restore_keys(false))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ttn_provisioning_have_keys())
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
|
|
||||||
|
if (!ttn_nvs_restore(off_duration))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
has_joined = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called immediately before sending join request message
|
||||||
|
void config_rf_params(void)
|
||||||
|
{
|
||||||
|
#if defined(CFG_us915) || defined(CFG_au915)
|
||||||
|
if (subband != 0)
|
||||||
|
LMIC_selectSubBand(subband - 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (join_data_rate != TTN_DR_JOIN_DEFAULT || max_tx_power != DEFAULT_MAX_TX_POWER)
|
||||||
|
{
|
||||||
|
dr_t dr = join_data_rate == TTN_DR_JOIN_DEFAULT ? LMIC.datarate : (dr_t)join_data_rate;
|
||||||
|
s1_t txpow = max_tx_power == DEFAULT_MAX_TX_POWER ? LMIC.adrTxPow : max_tx_power;
|
||||||
|
LMIC_setDrTxpow(dr, txpow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool join_core(void)
|
||||||
|
{
|
||||||
|
if (!ttn_provisioning_have_keys())
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "DevEUI, AppEUI/JoinEUI and/or AppKey have not been provided");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
|
|
||||||
|
has_joined = true;
|
||||||
|
hal_esp32_enter_critical_section();
|
||||||
|
xQueueReset(lmic_event_queue);
|
||||||
|
waiting_reason = TTN_WAITING_FOR_JOIN;
|
||||||
|
|
||||||
|
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
313
src/ttn_logging.c
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2021 Manuel Bleichenbacher
|
||||||
|
*
|
||||||
|
* Licensed under MIT License
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*
|
||||||
|
* Circular buffer for detailed logging without affecting LMIC timing.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#if LMIC_ENABLE_event_logging
|
||||||
|
|
||||||
|
#include "ttn_logging.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "lmic/lmic.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define NUM_RINGBUF_MSG 50
|
||||||
|
#define TAG "lmic"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Message structure used in ring buffer
|
||||||
|
*
|
||||||
|
* The structure is sent from the LMIC task to the logging task.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
const char *message;
|
||||||
|
uint32_t datum;
|
||||||
|
ev_t event;
|
||||||
|
ostime_t time;
|
||||||
|
ostime_t txend;
|
||||||
|
ostime_t globalDutyAvail;
|
||||||
|
u4_t freq;
|
||||||
|
u2_t opmode;
|
||||||
|
u2_t fcntDn;
|
||||||
|
u2_t fcntUp;
|
||||||
|
u2_t rxsyms;
|
||||||
|
rps_t rps;
|
||||||
|
u1_t txChnl;
|
||||||
|
u1_t datarate;
|
||||||
|
u1_t txrxFlags;
|
||||||
|
u1_t saveIrqFlags;
|
||||||
|
} TTNLogMessage;
|
||||||
|
|
||||||
|
static void loggingTask(void *param);
|
||||||
|
static void logFatal(const char *const file, const uint16_t line);
|
||||||
|
|
||||||
|
static void printMessage(TTNLogMessage *log);
|
||||||
|
static void printFatalError(TTNLogMessage *log);
|
||||||
|
static void printEvent(TTNLogMessage *log);
|
||||||
|
static void printEvtJoined(TTNLogMessage *log);
|
||||||
|
static void printEvtJoinFailed(TTNLogMessage *log);
|
||||||
|
static void printEvtTxComplete(TTNLogMessage *log);
|
||||||
|
static void printEvtTxStart(TTNLogMessage *log);
|
||||||
|
static void printEvtRxStart(TTNLogMessage *log);
|
||||||
|
static void printEvtJoinTxComplete(TTNLogMessage *log);
|
||||||
|
static void bin2hex(const uint8_t *bin, unsigned len, char *buf, char sep);
|
||||||
|
|
||||||
|
// Constants for formatting LORA values
|
||||||
|
static const char *const SF_NAMES[] = {"FSK", "SF7", "SF8", "SF9", "SF10", "SF11", "SF12", "SFrfu"};
|
||||||
|
static const char *const BW_NAMES[] = {"BW125", "BW250", "BW500", "BWrfu"};
|
||||||
|
static const char *const CR_NAMES[] = {"CR 4/5", "CR 4/6", "CR 4/7", "CR 4/8"};
|
||||||
|
static const char *const CRC_NAMES[] = {"NoCrc", "Crc"};
|
||||||
|
|
||||||
|
static RingbufHandle_t ringBuffer;
|
||||||
|
|
||||||
|
// Initialize logging
|
||||||
|
void ttn_log_init(void)
|
||||||
|
{
|
||||||
|
ringBuffer = xRingbufferCreate(NUM_RINGBUF_MSG * sizeof(TTNLogMessage), RINGBUF_TYPE_NOSPLIT);
|
||||||
|
if (ringBuffer == NULL)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to create ring buffer");
|
||||||
|
ASSERT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
xTaskCreate(loggingTask, "ttn_log", 1024 * 4, ringBuffer, 4, NULL);
|
||||||
|
hal_set_failure_handler(logFatal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record a logging event for later output
|
||||||
|
void ttn_log_event(int event, const char *message, uint32_t datum)
|
||||||
|
{
|
||||||
|
if (ringBuffer == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// capture state
|
||||||
|
TTNLogMessage log = {
|
||||||
|
.message = message,
|
||||||
|
.datum = datum,
|
||||||
|
.time = os_getTime(),
|
||||||
|
.txend = LMIC.txend,
|
||||||
|
.globalDutyAvail = LMIC.globalDutyAvail,
|
||||||
|
.event = (ev_t)event,
|
||||||
|
.freq = LMIC.freq,
|
||||||
|
.opmode = LMIC.opmode,
|
||||||
|
.fcntDn = (u2_t)LMIC.seqnoDn,
|
||||||
|
.fcntUp = (u2_t)LMIC.seqnoUp,
|
||||||
|
.rxsyms = LMIC.rxsyms,
|
||||||
|
.rps = LMIC.rps,
|
||||||
|
.txChnl = LMIC.txChnl,
|
||||||
|
.datarate = LMIC.datarate,
|
||||||
|
.txrxFlags = LMIC.txrxFlags,
|
||||||
|
.saveIrqFlags = LMIC.saveIrqFlags,
|
||||||
|
};
|
||||||
|
|
||||||
|
xRingbufferSend(ringBuffer, &log, sizeof(log), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// record a fatal event (failed assert) for later output
|
||||||
|
void logFatal(const char *const file, const uint16_t line)
|
||||||
|
{
|
||||||
|
ttn_log_event(-3, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record an informational message for later output
|
||||||
|
// The message must not be freed.
|
||||||
|
void LMICOS_logEvent(const char *pMessage)
|
||||||
|
{
|
||||||
|
ttn_log_event(-1, pMessage, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record an information message with an integer value for later output
|
||||||
|
// The message must not be freed.
|
||||||
|
void LMICOS_logEventUint32(const char *pMessage, uint32_t datum)
|
||||||
|
{
|
||||||
|
ttn_log_event(-2, pMessage, datum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Log output
|
||||||
|
|
||||||
|
// Tasks that receiveds the recorded messages, formats and outputs them.
|
||||||
|
void loggingTask(void *param)
|
||||||
|
{
|
||||||
|
RingbufHandle_t ringBuffer = (RingbufHandle_t)param;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
TTNLogMessage *log = (TTNLogMessage *)xRingbufferReceive(ringBuffer, &size, portMAX_DELAY);
|
||||||
|
if (log == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
printMessage(log);
|
||||||
|
|
||||||
|
vRingbufferReturnItem(ringBuffer, log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format and output a log message
|
||||||
|
void printMessage(TTNLogMessage *log)
|
||||||
|
{
|
||||||
|
switch ((int)log->event)
|
||||||
|
{
|
||||||
|
case -1:
|
||||||
|
ESP_LOGI(TAG, "%u (%d ms) - %s: opmode=%x", log->time, osticks2ms(log->time), log->message, log->opmode);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case -2:
|
||||||
|
ESP_LOGI(TAG, "%u (%d ms) - %s: datum=0x%x, opmode=%x)", log->time, osticks2ms(log->time), log->message,
|
||||||
|
log->datum, log->opmode);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case -3:
|
||||||
|
printFatalError(log);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
printEvent(log);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printFatalError(TTNLogMessage *log)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "%u (%d ms) - %s, %d", log->time, osticks2ms(log->time), log->message, log->datum);
|
||||||
|
ESP_LOGE(TAG, "- freq=%d.%d, txend=%u, avail=%u, ch=%u", log->freq / 1000000, (log->freq % 1000000) / 100000,
|
||||||
|
log->txend, log->globalDutyAvail, (unsigned)log->txChnl);
|
||||||
|
rps_t rps = log->rps;
|
||||||
|
ESP_LOGE(TAG, "- rps=0x%02x (%s, %s, %s, %s, IH=%d)", rps, SF_NAMES[getSf(rps)], BW_NAMES[getBw(rps)],
|
||||||
|
CR_NAMES[getCr(rps)], CRC_NAMES[getNocrc(rps)], getIh(rps));
|
||||||
|
ESP_LOGE(TAG, "- opmode=%x, txrxFlags=0x%02x%s, saveIrqFlags=0x%02x", log->opmode, log->txrxFlags,
|
||||||
|
(log->txrxFlags & TXRX_ACK) != 0 ? "; received ack" : "", log->saveIrqFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printEvent(TTNLogMessage *log)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "%u (%d ms) - %s", log->time, osticks2ms(log->time), log->message);
|
||||||
|
|
||||||
|
switch ((int)log->event)
|
||||||
|
{
|
||||||
|
case EV_JOINED:
|
||||||
|
printEvtJoined(log);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EV_JOIN_FAILED:
|
||||||
|
printEvtJoinFailed(log);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EV_TXCOMPLETE:
|
||||||
|
printEvtTxComplete(log);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EV_TXSTART:
|
||||||
|
printEvtTxStart(log);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EV_RXSTART:
|
||||||
|
printEvtRxStart(log);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EV_JOIN_TXCOMPLETE:
|
||||||
|
printEvtJoinTxComplete(log);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format and output the detail of a successful network join
|
||||||
|
void printEvtJoined(TTNLogMessage *log)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "- ch=%d", (unsigned)log->txChnl);
|
||||||
|
|
||||||
|
u4_t netid = 0;
|
||||||
|
devaddr_t devaddr = 0;
|
||||||
|
u1_t nwkKey[16];
|
||||||
|
u1_t artKey[16];
|
||||||
|
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "- netid: %d", netid);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "- devaddr: %08x", devaddr);
|
||||||
|
|
||||||
|
char hexBuf[48];
|
||||||
|
bin2hex((uint8_t *)&artKey, sizeof(artKey), hexBuf, '-');
|
||||||
|
ESP_LOGI(TAG, "- artKey: %s", hexBuf);
|
||||||
|
|
||||||
|
bin2hex((uint8_t *)&nwkKey, sizeof(nwkKey), hexBuf, '-');
|
||||||
|
ESP_LOGI(TAG, "- nwkKey: %s", hexBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format and output the detail of a failed network join
|
||||||
|
void printEvtJoinFailed(TTNLogMessage *log)
|
||||||
|
{
|
||||||
|
rps_t rps = log->rps;
|
||||||
|
ESP_LOGE(TAG, "- freq=%d.%d, opmode=%x, rps=0x%02x (%s, %s, %s, %s, IH=%d)", log->freq / 1000000,
|
||||||
|
(log->freq % 1000000) / 100000, log->opmode, rps, SF_NAMES[getSf(rps)], BW_NAMES[getBw(rps)],
|
||||||
|
CR_NAMES[getCr(rps)], CRC_NAMES[getNocrc(rps)], getIh(rps));
|
||||||
|
}
|
||||||
|
|
||||||
|
void printEvtTxComplete(TTNLogMessage *log)
|
||||||
|
{
|
||||||
|
rps_t rps = log->rps;
|
||||||
|
ESP_LOGI(TAG, "- ch=%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)", (unsigned)log->txChnl, rps, SF_NAMES[getSf(rps)],
|
||||||
|
BW_NAMES[getBw(rps)], CR_NAMES[getCr(rps)], CRC_NAMES[getNocrc(rps)], getIh(rps));
|
||||||
|
ESP_LOGI(TAG, "- txrxFlags=0x%02x%s, FcntUp=%04x, FcntDn=%04x, txend=%u", log->txrxFlags,
|
||||||
|
(log->txrxFlags & TXRX_ACK) != 0 ? "; received ack" : "", log->fcntUp, log->fcntDn, log->txend);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printEvtTxStart(TTNLogMessage *log)
|
||||||
|
{
|
||||||
|
rps_t rps = log->rps;
|
||||||
|
ESP_LOGI(TAG, "- ch=%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)", (unsigned)log->txChnl, rps, SF_NAMES[getSf(rps)],
|
||||||
|
BW_NAMES[getBw(rps)], CR_NAMES[getCr(rps)], CRC_NAMES[getNocrc(rps)], getIh(rps));
|
||||||
|
ESP_LOGI(TAG, "- datarate=%u, opmode=%x, txend=%u", log->datarate, log->opmode, log->txend);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printEvtRxStart(TTNLogMessage *log)
|
||||||
|
{
|
||||||
|
rps_t rps = log->rps;
|
||||||
|
ESP_LOGI(TAG, "- freq=%d.%d, rps=0x%02x (%s, %s, %s, %s, IH=%d)", log->freq / 1000000,
|
||||||
|
(log->freq % 1000000) / 100000, rps, SF_NAMES[getSf(rps)], BW_NAMES[getBw(rps)], CR_NAMES[getCr(rps)],
|
||||||
|
CRC_NAMES[getNocrc(rps)], getIh(rps));
|
||||||
|
ESP_LOGI(TAG, "- delta=%dms, rxsysm=%u", osticks2ms(log->time - log->txend), log->rxsyms);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printEvtJoinTxComplete(TTNLogMessage *log)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "- saveIrqFlags=0x%02x", log->saveIrqFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *HEX_DIGITS = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert binary data to hexadecimal representation.
|
||||||
|
*
|
||||||
|
* @param bin start of binary data
|
||||||
|
* @param len length of binary data (in bytes)
|
||||||
|
* @param buf buffer for hexadecimal result
|
||||||
|
* @param sep separator used between bytes (or 0 for none)
|
||||||
|
*/
|
||||||
|
void bin2hex(const uint8_t *bin, unsigned len, char *buf, char sep)
|
||||||
|
{
|
||||||
|
int tgt = 0;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
if (sep != 0 && i != 0)
|
||||||
|
buf[tgt++] = sep;
|
||||||
|
buf[tgt++] = HEX_DIGITS[bin[i] >> 4];
|
||||||
|
buf[tgt++] = HEX_DIGITS[bin[i] & 0xf];
|
||||||
|
}
|
||||||
|
buf[tgt] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
50
src/ttn_logging.h
Normal file
50
src/ttn_logging.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2021 Manuel Bleichenbacher
|
||||||
|
*
|
||||||
|
* Licensed under MIT License
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*
|
||||||
|
* Circular buffer for detailed logging without affecting LMIC timing.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TTN_LOGGING_H
|
||||||
|
#define TTN_LOGGING_H
|
||||||
|
|
||||||
|
#if LMIC_ENABLE_event_logging
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/ringbuf.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Logging functions.
|
||||||
|
*
|
||||||
|
* Logs internal information from LMIC in an asynchrnous fashion in order
|
||||||
|
* not to distrub the sensitive LORA timing.
|
||||||
|
*
|
||||||
|
* A ring buffer and a separate logging task is ued. The LMIC core records
|
||||||
|
* relevant values from the current LORA settings and writes them to a ring
|
||||||
|
* buffer. The logging tasks receives the message and the values, formats
|
||||||
|
* them and outputs them via the regular ESP-IDF logging mechanism.
|
||||||
|
*
|
||||||
|
* In order to activate the detailed logging, set the macro
|
||||||
|
* `LMIC_ENABLE_event_logging` to 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ttn_log_init(void);
|
||||||
|
void ttn_log_event(int event, const char *message, uint32_t datum);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
122
src/ttn_nvs.c
Normal file
122
src/ttn_nvs.c
Normal 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
30
src/ttn_nvs.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
*
|
||||||
|
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2021 Manuel Bleichenbacher
|
||||||
|
*
|
||||||
|
* Licensed under MIT License
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*
|
||||||
|
* Functions for storing and retrieving TTN communication state from NVS.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TTN_NVS_H
|
||||||
|
#define TTN_NVS_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ttn_nvs_save();
|
||||||
|
bool ttn_nvs_restore(int off_duration);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user