mirror of
https://github.com/manuelbl/ttn-esp32.git
synced 2025-06-15 12:24:27 +02:00
Compare commits
88 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1057aa09de | ||
|
61771c0bfa | ||
|
89ad39d463 | ||
|
c32c6b4cd6 | ||
|
569afe84e9 | ||
|
4e0587bb8e | ||
|
877784640d | ||
|
79727fdae2 | ||
|
50d45e7fb7 | ||
|
bbdb44cdd6 | ||
|
e7d9caefe1 | ||
|
68d5669833 | ||
|
46ad09736b | ||
|
1b7279518b | ||
|
c1517d3c82 | ||
|
ffcdd12d48 | ||
|
0a7353c146 | ||
|
54a91a27db | ||
|
ee8eead337 | ||
|
f06be4c069 | ||
|
5a7b3b0fcb | ||
|
9fd24c65ec | ||
|
31b00f05a8 | ||
|
ba481ceac5 | ||
|
b626ccb61a | ||
|
f433e826a7 | ||
|
0130928601 | ||
|
e71d584fca | ||
|
de4297a8f4 | ||
|
ee91ccc613 | ||
|
7df10bd6bc | ||
|
bd728887cf | ||
|
1df2c50f6f | ||
|
8fa345a5d4 | ||
|
e34dbcb467 | ||
|
72b878f1d9 | ||
|
17e432d714 | ||
|
5c1db030f2 | ||
|
eb5792fbb9 | ||
|
37d7f8d517 | ||
|
62e829b0d3 | ||
|
6df4b40122 | ||
|
7fa43dbbdb | ||
|
ba908c0b93 | ||
|
281ba52155 | ||
|
36edf92944 | ||
|
f421db44d7 | ||
|
8e2886db27 | ||
|
973a7c41c8 | ||
|
99bab17d4b | ||
|
1913840679 | ||
|
5c695fd223 | ||
|
3086b1bb58 | ||
|
f57c505d7e | ||
|
ba22c5a745 | ||
|
5b22f228da | ||
|
84b2f88f0c | ||
|
fffe7bd8b3 | ||
|
bb41c4960f | ||
|
f70ad2a996 | ||
|
fdacae73f8 | ||
|
9489f34542 | ||
|
2a31ef0096 | ||
|
e15d936bb4 | ||
|
a8cd58214d | ||
|
b3e0b567b3 | ||
|
0853fe05ec | ||
|
625968cd99 | ||
|
8d7d157445 | ||
|
b73ed23a97 | ||
|
513ac44268 | ||
|
45778d2186 | ||
|
e29477fa7e | ||
|
3036d16e0d | ||
|
2d09703fc6 | ||
|
4df057042c | ||
|
48668b3295 | ||
|
baa6c93d5f | ||
|
43080636bc | ||
|
a8fd68ca5e | ||
|
3ac63d4146 | ||
|
dc0bfefe0e | ||
|
6adf69ea0c | ||
|
c2a5bd8374 | ||
|
a601c2b2bf | ||
|
d7d4a40c4f | ||
|
1e1cadf400 | ||
|
cf94bba4af |
3
.clang-format
Normal file
3
.clang-format
Normal file
@ -0,0 +1,3 @@
|
||||
BasedOnStyle: Microsoft
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 120
|
3
.vscode/c_cpp_properties.json
vendored
3
.vscode/c_cpp_properties.json
vendored
@ -29,7 +29,8 @@
|
||||
"compilerPath": "/usr/bin/clang",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++11",
|
||||
"intelliSenseMode": "clang-x64"
|
||||
"intelliSenseMode": "clang-x64",
|
||||
"compileCommands": "${workspaceFolder}/examples/hello_world/build/compile_commands.json"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
|
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -1,11 +1,11 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"provisioning.h": "c",
|
||||
"config.h": "c",
|
||||
"sdkconfig.h": "c",
|
||||
"oslmic.h": "c",
|
||||
"hal_esp32.h": "c",
|
||||
"esp_log.h": "c",
|
||||
"nvs_flash.h": "c"
|
||||
"lmic.h": "c",
|
||||
"ttn_provisioning.h": "c",
|
||||
"lorabase.h": "c"
|
||||
}
|
||||
}
|
@ -9,6 +9,14 @@ set(COMPONENT_ADD_INCLUDEDIRS
|
||||
)
|
||||
set(COMPONENT_REQUIRES
|
||||
nvs_flash
|
||||
mbedtls
|
||||
driver
|
||||
esp_event
|
||||
esp_timer
|
||||
)
|
||||
|
||||
register_component()
|
||||
|
||||
if (IDF_VER STRGREATER_EQUAL "v4.0")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-expansion-to-defined)
|
||||
endif()
|
||||
|
46
Kconfig
46
Kconfig
@ -14,10 +14,10 @@ config TTN_LORA_FREQ_EU_868
|
||||
bool "Europe (868 MHz)"
|
||||
|
||||
config TTN_LORA_FREQ_US_915
|
||||
bool "North America (915 MHz)"
|
||||
bool "North and South America (915 MHz)"
|
||||
|
||||
config TTN_LORA_FREQ_AU_921
|
||||
bool "Australia (921 MHz)"
|
||||
config TTN_LORA_FREQ_AU_915
|
||||
bool "Australia (915 MHz)"
|
||||
|
||||
config TTN_LORA_FREQ_AS_923
|
||||
bool "Asia (923 MHz)"
|
||||
@ -25,6 +25,9 @@ config TTN_LORA_FREQ_AS_923
|
||||
config TTN_LORA_FREQ_AS_923_JP
|
||||
bool "Asia, region Japan (923 MHz)"
|
||||
|
||||
config TTN_LORA_FREQ_KR_920
|
||||
bool "South Korea (920 MHz)"
|
||||
|
||||
config TTN_LORA_FREQ_IN_866
|
||||
bool "India (866 MHz)"
|
||||
|
||||
@ -46,32 +49,27 @@ config TTN_RADIO_SX1276_77_78_79
|
||||
|
||||
endchoice
|
||||
|
||||
choice TTN_TIMER
|
||||
prompt "Timer"
|
||||
default TTN_TIMER_1_GROUP_0
|
||||
help
|
||||
A timer is used to implement the LoRaWAN protocol. This selects the ESP32's timer.
|
||||
|
||||
config TTN_TIMER_0_GROUP_0
|
||||
bool "Timer 0 of group 0"
|
||||
|
||||
config TTN_TIMER_1_GROUP_0
|
||||
bool "Timer 1 of group 0"
|
||||
|
||||
config TTN_TIMER_0_GROUP_1
|
||||
bool "Timer 0 of group 1"
|
||||
|
||||
config TTN_TIMER_1_GROUP_1
|
||||
bool "Timer 1 of group 1"
|
||||
|
||||
endchoice
|
||||
|
||||
config TTN_SPI_FREQ
|
||||
int "SPI frequency (in Hz)"
|
||||
default 10000000
|
||||
default 2000000
|
||||
help
|
||||
SPI frequency to communicate between ESP32 and SX127x radio chip
|
||||
|
||||
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
|
||||
int "Background task priority"
|
||||
default 10
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Manuel Bleichenbacher
|
||||
Copyright (c) 2018-2021 Manuel Bleichenbacher
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
27
README.md
27
README.md
@ -2,15 +2,30 @@
|
||||
|
||||
**The Things Network device library for ESP-IDF (ESP32) supporting devices with Semtech SX127x chips**
|
||||
|
||||
This ESP32 component provides LoRaWAN communication with [The Things Network](https://www.thethingsnetwork.org/). It supports
|
||||
This ESP32 component provides LoRaWAN communication with
|
||||
[The Things Network](https://www.thethingsnetwork.org/). It supports:
|
||||
|
||||
- OTAA (over-the-air activation)
|
||||
- uplink and downlink messages
|
||||
- saving the EUIs and key in non-volatile memory
|
||||
- [AT commands](https://github.com/manuelbl/ttn-esp32/wiki/AT-Commands) for provisioning EUIs and key (so the same code can be flashed to several devices)
|
||||
- support for regions Eurpe, North America, Australia, Asia and India
|
||||
- deep sleep and power off without the need for rejoining
|
||||
- [AT commands](https://github.com/manuelbl/ttn-esp32/wiki/AT-Commands) for provisioning EUIs and key
|
||||
(so the same code can be flashed to several devices)
|
||||
- support for regions Europe, North and South America, Australia, Korea, Asia and India
|
||||
- C and C++ API
|
||||
|
||||
The library is based on the LMIC library from IBM (specifically the well-maintained version by MCCI
|
||||
– see their [GitHub repository](https://github.com/mcci-catena/arduino-lmic)) and provides a high-level API specifically targeted at The Things Network.
|
||||
|
||||
## New in version 4.x
|
||||
|
||||
- Support for deep sleep and power off (see [Deep Sleep and Power Off](https://github.com/manuelbl/ttn-esp32/wiki/Deep-Sleep-and-Power-Off))
|
||||
- Verified compatibility with ESP-IDF v4.3 and 5.0
|
||||
- Upgraded underlying library mcci-catena/arduino-lmic to v4.2.0-1
|
||||
- C API
|
||||
- Support for sub-bands
|
||||
- Dropped support for *Makefile* builds
|
||||
|
||||
The library is based on the LMIC library from IBM (specifically the version maintained by MCCI – see their [GitHub repository](https://github.com/mcci-catena/arduino-lmic)) and provides a high-level API specifically targeted at The Things Network.
|
||||
|
||||
## Get Started
|
||||
|
||||
@ -18,11 +33,11 @@ Follow the detailed [Get Started Guide](https://github.com/manuelbl/ttn-esp32/wi
|
||||
|
||||
## Supported Boards
|
||||
|
||||
All boards with Semtech SX127x chips, RFM9x and compatibles are supported. It includes boards from ttgo, Heltec and HopeRF.
|
||||
All boards with Semtech SX127x chips, RFM9x and compatibles are supported. It includes boards from ttgo, Heltec and HopeRF. For several of them, the [Pin Configuration](https://github.com/manuelbl/ttn-esp32/wiki/Boards-and-Pins) is described in detail.
|
||||
|
||||
## API Documentation
|
||||
|
||||
See the Wiki page: [API Documentation](https://github.com/manuelbl/ttn-esp32/wiki/API-Documentation)
|
||||
See [API Documentation](https://codecrete.net/ttn-esp32/) for both the C ad C++ API
|
||||
|
||||
## More information
|
||||
|
||||
|
@ -1,2 +0,0 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
COMPONENT_SRCDIRS := src src/aes src/hal src/lmic
|
5
doc/.gitignore
vendored
Normal file
5
doc/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
xml/
|
||||
html/
|
||||
api.md
|
||||
node_modules/
|
||||
ttn-esp32.zip
|
2619
doc/Doxyfile
Normal file
2619
doc/Doxyfile
Normal file
File diff suppressed because it is too large
Load Diff
21
doc/doc.dox
Normal file
21
doc/doc.dox
Normal file
@ -0,0 +1,21 @@
|
||||
/*!
|
||||
\mainpage ttn-esp32 API Documentation
|
||||
|
||||
ttn-esp32 is a C and C++ library for the ESP-IDF (ESP32) framework
|
||||
for The Things Network library end devices using Semtech SX127x chips.
|
||||
|
||||
|
||||
\section cplusplus C++ API
|
||||
|
||||
\ref TheThingsNetwork class
|
||||
|
||||
\ref TTNRFSettings struct
|
||||
|
||||
|
||||
\section c C API
|
||||
|
||||
\ref c_api
|
||||
|
||||
\ref ttn_rf_settings_t struct
|
||||
|
||||
*/
|
@ -0,0 +1,40 @@
|
||||
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
|
||||
#MSearchBox {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px);
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height));
|
||||
}
|
||||
}
|
107
doc/doxygen-awesome-css/doxygen-awesome-sidebar-only.css
Normal file
107
doc/doxygen-awesome-css/doxygen-awesome-sidebar-only.css
Normal file
@ -0,0 +1,107 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
html {
|
||||
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
|
||||
* Make sure it is wide enought to contain the page title (logo + title + version)
|
||||
*/
|
||||
--side-nav-fixed-width: 340px;
|
||||
--menu-display: none;
|
||||
|
||||
--top-height: 120px;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
html {
|
||||
--searchbar-background: var(--page-background-color);
|
||||
}
|
||||
|
||||
#side-nav {
|
||||
min-width: var(--side-nav-fixed-width);
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
top: var(--top-height);
|
||||
}
|
||||
|
||||
#nav-tree, #side-nav {
|
||||
height: calc(100vh - var(--top-height)) !important;
|
||||
}
|
||||
|
||||
#nav-tree {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#top {
|
||||
display: block;
|
||||
border-bottom: none;
|
||||
height: var(--top-height);
|
||||
margin-bottom: calc(0px - var(--top-height));
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
background: var(--side-nav-background);
|
||||
}
|
||||
#main-nav {
|
||||
float: left;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.ui-resizable-handle {
|
||||
cursor: default;
|
||||
width: 1px !important;
|
||||
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
|
||||
}
|
||||
|
||||
#nav-path {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: var(--side-nav-fixed-width);
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#doc-content {
|
||||
height: calc(100vh - 31px) !important;
|
||||
padding-bottom: calc(3 * var(--spacing-large));
|
||||
padding-top: calc(var(--top-height) - 80px);
|
||||
box-sizing: border-box;
|
||||
margin-left: var(--side-nav-fixed-width) !important;
|
||||
}
|
||||
|
||||
#MSearchBox {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
|
||||
}
|
||||
|
||||
#MSearchResultsWindow {
|
||||
left: var(--spacing-medium) !important;
|
||||
right: auto;
|
||||
}
|
||||
}
|
1478
doc/doxygen-awesome-css/doxygen-awesome.css
Normal file
1478
doc/doxygen-awesome-css/doxygen-awesome.css
Normal file
File diff suppressed because it is too large
Load Diff
5
doc/generate.sh
Executable file
5
doc/generate.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
rm -rf html ttn-esp32.zip
|
||||
doxygen Doxyfile
|
||||
cd html
|
||||
zip -r ../ttn-esp32.zip .
|
10
examples/deep_sleep/CMakeLists.txt
Normal file
10
examples/deep_sleep/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
# Update the below line to match the path to the ttn-esp32 library,
|
||||
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "../..")
|
||||
|
||||
#add_definitions(-DLMIC_ENABLE_event_logging=1)
|
||||
|
||||
project(deep_sleep)
|
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)
|
131
examples/deep_sleep/main/main.cpp
Normal file
131
examples/deep_sleep/main/main.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||
*
|
||||
* Copyright (c) 2021 Manuel Bleichenbacher
|
||||
*
|
||||
* Licensed under MIT License
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* Sample program sending messages and going to deep sleep in-between.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "nvs_flash.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "TheThingsNetwork.h"
|
||||
|
||||
// NOTE:
|
||||
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
|
||||
// Go to Components / The Things Network, select the appropriate values and save.
|
||||
|
||||
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
|
||||
// > Your device > Activation information)
|
||||
|
||||
// AppEUI (sometimes called JoinEUI)
|
||||
const char *appEui = "????????????????";
|
||||
// DevEUI
|
||||
const char *devEui = "????????????????";
|
||||
// AppKey
|
||||
const char *appKey = "????????????????????????????????";
|
||||
|
||||
// Pins and other resources
|
||||
#define TTN_SPI_HOST SPI2_HOST
|
||||
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
|
||||
#define TTN_PIN_SPI_SCLK 5
|
||||
#define TTN_PIN_SPI_MOSI 27
|
||||
#define TTN_PIN_SPI_MISO 19
|
||||
#define TTN_PIN_NSS 18
|
||||
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
|
||||
#define TTN_PIN_RST 14
|
||||
#define TTN_PIN_DIO0 26
|
||||
#define TTN_PIN_DIO1 35
|
||||
|
||||
static TheThingsNetwork ttn;
|
||||
|
||||
const unsigned TX_INTERVAL = 60;
|
||||
|
||||
// This counter value is retained (in RTC memory) during deep sleep
|
||||
RTC_DATA_ATTR int counter_in_rtc_mem;
|
||||
|
||||
void messageReceived(const uint8_t *message, size_t length, ttn_port_t port)
|
||||
{
|
||||
printf("Message of %d bytes received on port %d:", length, port);
|
||||
for (int i = 0; i < length; i++)
|
||||
printf(" %02x", message[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
esp_err_t err;
|
||||
// Initialize the GPIO ISR handler service
|
||||
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
|
||||
err = nvs_flash_init();
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Initialize SPI bus
|
||||
spi_bus_config_t spi_bus_config;
|
||||
memset(&spi_bus_config, 0, sizeof(spi_bus_config));
|
||||
spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO;
|
||||
spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI;
|
||||
spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK;
|
||||
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Configure the SX127x pins
|
||||
ttn.configurePins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
|
||||
|
||||
// The below line can be commented after the first run as the data is saved in NVS
|
||||
ttn.provision(devEui, appEui, appKey);
|
||||
|
||||
// Register callback for received messages
|
||||
ttn.onMessage(messageReceived);
|
||||
|
||||
// ttn.setAdrEnabled(false);
|
||||
// ttn.setDataRate(kTTNDataRate_US915_SF7);
|
||||
// ttn.setMaxTxPower(14);
|
||||
|
||||
if (ttn.resumeAfterDeepSleep())
|
||||
{
|
||||
printf("Resumed from deep sleep.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Joining...\n");
|
||||
if (ttn.join())
|
||||
{
|
||||
printf("Joined.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Join failed. Goodbye\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Sending message...\n");
|
||||
counter_in_rtc_mem++;
|
||||
char message[20];
|
||||
sprintf(message, "Hello %d", counter_in_rtc_mem);
|
||||
TTNResponseCode res = ttn.transmitMessage((uint8_t *)message, strlen(message));
|
||||
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
|
||||
|
||||
// Wait until TTN communication is idle and save state
|
||||
ttn.waitForIdle();
|
||||
ttn.prepareForDeepSleep();
|
||||
|
||||
// Schedule wake up
|
||||
esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000LL);
|
||||
|
||||
printf("Going to deep sleep...\n");
|
||||
esp_deep_sleep_start();
|
||||
}
|
10
examples/deep_sleep_in_c/CMakeLists.txt
Normal file
10
examples/deep_sleep_in_c/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
# Update the below line to match the path to the ttn-esp32 library,
|
||||
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "../..")
|
||||
|
||||
#add_definitions(-DLMIC_ENABLE_event_logging=1)
|
||||
|
||||
project(deep_sleep)
|
4
examples/deep_sleep_in_c/main/CMakeLists.txt
Normal file
4
examples/deep_sleep_in_c/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ttn-esp32)
|
135
examples/deep_sleep_in_c/main/main.c
Normal file
135
examples/deep_sleep_in_c/main/main.c
Normal file
@ -0,0 +1,135 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||
*
|
||||
* Copyright (c) 2021 Manuel Bleichenbacher
|
||||
*
|
||||
* Licensed under MIT License
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* Sample program (in C) sending messages and going to deep sleep in-between.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_event.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_sleep.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ttn.h"
|
||||
|
||||
// NOTE:
|
||||
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
|
||||
// Go to Components / The Things Network, select the appropriate values and save.
|
||||
|
||||
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
|
||||
// > Your device > Activation information)
|
||||
|
||||
// AppEUI (sometimes called JoinEUI)
|
||||
const char *appEui = "????????????????";
|
||||
// DevEUI
|
||||
const char *devEui = "????????????????";
|
||||
// AppKey
|
||||
const char *appKey = "????????????????????????????????";
|
||||
|
||||
// Pins and other resources
|
||||
#define TTN_SPI_HOST SPI2_HOST
|
||||
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
|
||||
#define TTN_PIN_SPI_SCLK 5
|
||||
#define TTN_PIN_SPI_MOSI 27
|
||||
#define TTN_PIN_SPI_MISO 19
|
||||
#define TTN_PIN_NSS 18
|
||||
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
|
||||
#define TTN_PIN_RST 14
|
||||
#define TTN_PIN_DIO0 26
|
||||
#define TTN_PIN_DIO1 35
|
||||
|
||||
#define TX_INTERVAL 60 // in sec
|
||||
|
||||
// This counter value is retained (in RTC memory) during deep sleep
|
||||
RTC_DATA_ATTR int counter_in_rtc_mem;
|
||||
|
||||
|
||||
void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
|
||||
{
|
||||
printf("Message of %d bytes received on port %d:", length, port);
|
||||
for (int i = 0; i < length; i++)
|
||||
printf(" %02x", message[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t err;
|
||||
// Initialize the GPIO ISR handler service
|
||||
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
|
||||
err = nvs_flash_init();
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Initialize SPI bus
|
||||
spi_bus_config_t spi_bus_config = {
|
||||
.miso_io_num = TTN_PIN_SPI_MISO,
|
||||
.mosi_io_num = TTN_PIN_SPI_MOSI,
|
||||
.sclk_io_num = TTN_PIN_SPI_SCLK,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1
|
||||
};
|
||||
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Initialize TTN
|
||||
ttn_init();
|
||||
|
||||
// Configure the SX127x pins
|
||||
ttn_configure_pins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
|
||||
|
||||
// The below line can be commented after the first run as the data is saved in NVS
|
||||
ttn_provision(devEui, appEui, appKey);
|
||||
|
||||
// Register callback for received messages
|
||||
ttn_on_message(messageReceived);
|
||||
|
||||
// ttn_set_adr_enabled(false);
|
||||
// ttn_set_data_rate(TTN_DR_US915_SF7);
|
||||
// ttn_set_max_tx_pow(14);
|
||||
|
||||
if (ttn_resume_after_deep_sleep()) {
|
||||
printf("Resumed from deep sleep.\n");
|
||||
|
||||
} else {
|
||||
|
||||
printf("Joining...\n");
|
||||
if (ttn_join())
|
||||
{
|
||||
printf("Joined.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Join failed. Goodbye\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Sending message...\n");
|
||||
counter_in_rtc_mem++;
|
||||
char message[20];
|
||||
sprintf(message, "Hello %d", counter_in_rtc_mem);
|
||||
ttn_response_code_t res = ttn_transmit_message((uint8_t *)message, strlen(message), 1, false);
|
||||
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
|
||||
|
||||
// Wait until TTN communication is idle and save state
|
||||
ttn_wait_for_idle();
|
||||
ttn_prepare_for_deep_sleep();
|
||||
|
||||
// Schedule wake up
|
||||
esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000LL);
|
||||
|
||||
printf("Going to deep sleep...\n");
|
||||
esp_deep_sleep_start();
|
||||
}
|
@ -1,6 +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)
|
||||
|
||||
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)
|
||||
|
@ -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")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS "")
|
||||
|
||||
register_component()
|
||||
idf_component_register(
|
||||
SRCS "main.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ttn-esp32)
|
||||
|
@ -7,38 +7,42 @@
|
||||
* Licensed under MIT License
|
||||
* 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 "esp_event.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "nvs_flash.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "TheThingsNetwork.h"
|
||||
|
||||
// NOTE:
|
||||
// The LoRaWAN frequency and the radio chip must be configured by running 'make menuconfig'.
|
||||
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
|
||||
// Go to Components / The Things Network, select the appropriate values and save.
|
||||
|
||||
// Copy the below hex string from the "Device EUI" field
|
||||
// on your device's overview page in the TTN console.
|
||||
const char *devEui = "????????????????";
|
||||
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
|
||||
// > Your device > Activation information)
|
||||
|
||||
// Copy the below two lines from bottom of the same page
|
||||
// AppEUI (sometimes called JoinEUI)
|
||||
const char *appEui = "????????????????";
|
||||
// DevEUI
|
||||
const char *devEui = "????????????????";
|
||||
// AppKey
|
||||
const char *appKey = "????????????????????????????????";
|
||||
|
||||
// Pins and other resources
|
||||
#define TTN_SPI_HOST HSPI_HOST
|
||||
#define TTN_SPI_DMA_CHAN 1
|
||||
#define TTN_SPI_HOST SPI2_HOST
|
||||
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
|
||||
#define TTN_PIN_SPI_SCLK 5
|
||||
#define TTN_PIN_SPI_MOSI 27
|
||||
#define TTN_PIN_SPI_MISO 19
|
||||
#define TTN_PIN_NSS 18
|
||||
#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_DIO1 33
|
||||
#define TTN_PIN_DIO1 35
|
||||
|
||||
static TheThingsNetwork ttn;
|
||||
|
||||
@ -53,10 +57,18 @@ void sendMessages(void* pvParameter)
|
||||
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
|
||||
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)
|
||||
{
|
||||
esp_err_t err;
|
||||
@ -70,12 +82,10 @@ extern "C" void app_main(void)
|
||||
|
||||
// Initialize SPI bus
|
||||
spi_bus_config_t spi_bus_config;
|
||||
memset(&spi_bus_config, 0, sizeof(spi_bus_config));
|
||||
spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO;
|
||||
spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI;
|
||||
spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK;
|
||||
spi_bus_config.quadwp_io_num = -1;
|
||||
spi_bus_config.quadhd_io_num = -1;
|
||||
spi_bus_config.max_transfer_sz = 0;
|
||||
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
@ -85,6 +95,13 @@ extern "C" void app_main(void)
|
||||
// The below line can be commented after the first run as the data is saved in NVS
|
||||
ttn.provision(devEui, appEui, appKey);
|
||||
|
||||
// Register callback for received messages
|
||||
ttn.onMessage(messageReceived);
|
||||
|
||||
// ttn.setAdrEnabled(false);
|
||||
// ttn.setDataRate(kTTNDataRate_US915_SF7);
|
||||
// ttn.setMaxTxPower(14);
|
||||
|
||||
printf("Joining...\n");
|
||||
if (ttn.join())
|
||||
{
|
||||
|
10
examples/hello_world_in_c/CMakeLists.txt
Normal file
10
examples/hello_world_in_c/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
# Update the below line to match the path to the ttn-esp32 library,
|
||||
# e.g. list(APPEND EXTRA_COMPONENT_DIRS "/Users/me/Documents/ttn-esp32")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "../..")
|
||||
|
||||
#add_definitions(-DLMIC_ENABLE_event_logging=1)
|
||||
|
||||
project(hello_world)
|
4
examples/hello_world_in_c/main/CMakeLists.txt
Normal file
4
examples/hello_world_in_c/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ttn-esp32)
|
117
examples/hello_world_in_c/main/main.c
Normal file
117
examples/hello_world_in_c/main/main.c
Normal file
@ -0,0 +1,117 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||
*
|
||||
* Copyright (c) 2021 Manuel Bleichenbacher
|
||||
*
|
||||
* Licensed under MIT License
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* Sample program for C showing how to send and receive messages.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_event.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "ttn.h"
|
||||
|
||||
// NOTE:
|
||||
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
|
||||
// Go to Components / The Things Network, select the appropriate values and save.
|
||||
|
||||
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
|
||||
// > Your device > Activation information)
|
||||
|
||||
// AppEUI (sometimes called JoinEUI)
|
||||
const char *appEui = "????????????????";
|
||||
// DevEUI
|
||||
const char *devEui = "????????????????";
|
||||
// AppKey
|
||||
const char *appKey = "????????????????????????????????";
|
||||
|
||||
// Pins and other resources
|
||||
#define TTN_SPI_HOST SPI2_HOST
|
||||
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
|
||||
#define TTN_PIN_SPI_SCLK 5
|
||||
#define TTN_PIN_SPI_MOSI 27
|
||||
#define TTN_PIN_SPI_MISO 19
|
||||
#define TTN_PIN_NSS 18
|
||||
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
|
||||
#define TTN_PIN_RST 14
|
||||
#define TTN_PIN_DIO0 26
|
||||
#define TTN_PIN_DIO1 35
|
||||
|
||||
#define TX_INTERVAL 30
|
||||
static uint8_t msgData[] = "Hello, world";
|
||||
|
||||
|
||||
void sendMessages(void* pvParameter)
|
||||
{
|
||||
while (1) {
|
||||
printf("Sending message...\n");
|
||||
ttn_response_code_t res = ttn_transmit_message(msgData, sizeof(msgData) - 1, 1, false);
|
||||
printf(res == TTN_SUCCESSFUL_TRANSMISSION ? "Message sent.\n" : "Transmission failed.\n");
|
||||
|
||||
vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
|
||||
{
|
||||
printf("Message of %d bytes received on port %d:", length, port);
|
||||
for (int i = 0; i < length; i++)
|
||||
printf(" %02x", message[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t err;
|
||||
// Initialize the GPIO ISR handler service
|
||||
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Initialize the NVS (non-volatile storage) for saving and restoring the keys
|
||||
err = nvs_flash_init();
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Initialize SPI bus
|
||||
spi_bus_config_t spi_bus_config = {
|
||||
.miso_io_num = TTN_PIN_SPI_MISO,
|
||||
.mosi_io_num = TTN_PIN_SPI_MOSI,
|
||||
.sclk_io_num = TTN_PIN_SPI_SCLK,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1
|
||||
};
|
||||
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Initialize TTN
|
||||
ttn_init();
|
||||
|
||||
// Configure the SX127x pins
|
||||
ttn_configure_pins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
|
||||
|
||||
// The below line can be commented after the first run as the data is saved in NVS
|
||||
ttn_provision(devEui, appEui, appKey);
|
||||
|
||||
// Register callback for received messages
|
||||
ttn_on_message(messageReceived);
|
||||
|
||||
// ttn_set_adr_enabled(false);
|
||||
// ttn_set_data_rate(TTN_DR_US915_SF7);
|
||||
// ttn_set_max_tx_pow(14);
|
||||
|
||||
printf("Joining...\n");
|
||||
if (ttn_join())
|
||||
{
|
||||
printf("Joined.\n");
|
||||
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Join failed. Goodbye\n");
|
||||
}
|
||||
}
|
@ -1,6 +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)
|
||||
|
||||
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)
|
||||
|
@ -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")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS "")
|
||||
|
||||
register_component()
|
||||
idf_component_register(
|
||||
SRCS "main.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ttn-esp32)
|
||||
|
@ -11,23 +11,31 @@
|
||||
*******************************************************************************/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_event.h"
|
||||
#include "nvs_flash.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "TheThingsNetwork.h"
|
||||
|
||||
|
||||
// NOTE:
|
||||
// The LoRaWAN frequency and the radio chip must be configured by running 'make menuconfig'.
|
||||
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
|
||||
// Go to Components / The Things Network, select the appropriate values and save.
|
||||
|
||||
// Copy the below two lines from the bottom
|
||||
// of your device's overview page in the TTN console.
|
||||
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
|
||||
// > Your device > Activation information)
|
||||
|
||||
// AppEUI (sometimes called JoinEUI)
|
||||
const char *appEui = "????????????????";
|
||||
|
||||
// AppKey
|
||||
const char *appKey = "????????????????????????????????";
|
||||
|
||||
|
||||
// Pins and other resources
|
||||
#define TTN_SPI_HOST HSPI_HOST
|
||||
#define TTN_SPI_DMA_CHAN 1
|
||||
#define TTN_SPI_HOST SPI2_HOST
|
||||
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
|
||||
#define TTN_PIN_SPI_SCLK 5
|
||||
#define TTN_PIN_SPI_MOSI 27
|
||||
#define TTN_PIN_SPI_MISO 19
|
||||
@ -50,7 +58,7 @@ void sendMessages(void* pvParameter)
|
||||
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
|
||||
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
|
||||
|
||||
vTaskDelay(TX_INTERVAL * 1000 / portTICK_PERIOD_MS);
|
||||
vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,12 +75,10 @@ extern "C" void app_main(void)
|
||||
|
||||
// Initialize SPI bus
|
||||
spi_bus_config_t spi_bus_config;
|
||||
memset(&spi_bus_config, 0, sizeof(spi_bus_config));
|
||||
spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO;
|
||||
spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI;
|
||||
spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK;
|
||||
spi_bus_config.quadwp_io_num = -1;
|
||||
spi_bus_config.quadhd_io_num = -1;
|
||||
spi_bus_config.max_transfer_sz = 0;
|
||||
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
|
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)
|
@ -12,25 +12,31 @@
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_event.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "nvs_flash.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "TheThingsNetwork.h"
|
||||
|
||||
|
||||
// NOTE:
|
||||
// The LoRaWAN frequency and the radio chip must be configured by running 'make menuconfig'.
|
||||
// The LoRaWAN frequency and the radio chip must be configured by running 'idf.py menuconfig'.
|
||||
// Go to Components / The Things Network, select the appropriate values and save.
|
||||
|
||||
// Copy the below hex string from the "Device EUI" field
|
||||
// on your device's overview page in the TTN console.
|
||||
const char *devEui = "????????????????";
|
||||
// Copy the below hex strings from the TTN console (Applications > Your application > End devices
|
||||
// > Your device > Activation information)
|
||||
|
||||
// Copy the below two lines from bottom of the same page
|
||||
// AppEUI (sometimes called JoinEUI)
|
||||
const char *appEui = "????????????????";
|
||||
// DevEUI
|
||||
const char *devEui = "????????????????";
|
||||
// AppKey
|
||||
const char *appKey = "????????????????????????????????";
|
||||
|
||||
|
||||
// Pins and other resources
|
||||
#define TTN_SPI_HOST HSPI_HOST
|
||||
#define TTN_SPI_DMA_CHAN 1
|
||||
#define TTN_SPI_HOST SPI2_HOST
|
||||
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
|
||||
#define TTN_PIN_SPI_SCLK 5
|
||||
#define TTN_PIN_SPI_MOSI 27
|
||||
#define TTN_PIN_SPI_MISO 19
|
||||
@ -38,7 +44,7 @@ const char *appKey = "????????????????????????????????";
|
||||
#define TTN_PIN_RXTX TTN_NOT_CONNECTED
|
||||
#define TTN_PIN_RST 14
|
||||
#define TTN_PIN_DIO0 26
|
||||
#define TTN_PIN_DIO1 33
|
||||
#define TTN_PIN_DIO1 35
|
||||
|
||||
static TheThingsNetwork ttn;
|
||||
|
||||
@ -46,23 +52,53 @@ const unsigned TX_INTERVAL = 30;
|
||||
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, %ld.%ld MHz\n",
|
||||
window, bw, settings.frequency / 1000000, (settings.frequency % 1000000 + 50000) / 100000);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("%s: SF%d, BW %dkHz, %ld.%ld MHz\n",
|
||||
window, sf, bw, settings.frequency / 1000000, (settings.frequency % 1000000 + 50000) / 100000);
|
||||
}
|
||||
}
|
||||
|
||||
void printAllRFSettings()
|
||||
{
|
||||
printRFSettings("TX ", ttn.txSettings());
|
||||
printRFSettings("RX1", ttn.rx1Settings());
|
||||
printRFSettings("RX2", ttn.rx2Settings());
|
||||
}
|
||||
|
||||
void sendMessages(void* pvParameter)
|
||||
{
|
||||
while (1) {
|
||||
printf("Sending message...\n");
|
||||
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
|
||||
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
|
||||
printAllRFSettings();
|
||||
|
||||
vTaskDelay(TX_INTERVAL * 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);
|
||||
for (int i = 0; i < length; i++)
|
||||
printf(" %02x", message[i]);
|
||||
printf("\n");
|
||||
printf("RSSI: %d dBm\n", ttn.rssi());
|
||||
}
|
||||
|
||||
extern "C" void app_main(void)
|
||||
@ -78,12 +114,10 @@ extern "C" void app_main(void)
|
||||
|
||||
// Initialize SPI bus
|
||||
spi_bus_config_t spi_bus_config;
|
||||
memset(&spi_bus_config, 0, sizeof(spi_bus_config));
|
||||
spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO;
|
||||
spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI;
|
||||
spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK;
|
||||
spi_bus_config.quadwp_io_num = -1;
|
||||
spi_bus_config.quadhd_io_num = -1;
|
||||
spi_bus_config.max_transfer_sz = 0;
|
||||
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
@ -93,12 +127,15 @@ extern "C" void app_main(void)
|
||||
// The below line can be commented after the first run as the data is saved in NVS
|
||||
ttn.provision(devEui, appEui, appKey);
|
||||
|
||||
// Register callback for received messages
|
||||
ttn.onMessage(messageReceived);
|
||||
|
||||
printf("Joining...\n");
|
||||
if (ttn.join())
|
||||
{
|
||||
printf("Joined.\n");
|
||||
printAllRFSettings();
|
||||
printf("RSSI: %d dBm\n", ttn.rssi());
|
||||
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, nullptr);
|
||||
}
|
||||
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)
|
125
examples/power_off/main/main.cpp
Normal file
125
examples/power_off/main/main.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* 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 <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;
|
||||
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;
|
||||
memset(&spi_bus_config, 0, sizeof(spi_bus_config));
|
||||
spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO;
|
||||
spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI;
|
||||
spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK;
|
||||
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Configure the SX127x pins
|
||||
ttn.configurePins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
|
||||
|
||||
// The below line can be commented after the first run as the data is saved in NVS
|
||||
ttn.provision(devEui, appEui, appKey);
|
||||
|
||||
// Register callback for received messages
|
||||
ttn.onMessage(messageReceived);
|
||||
|
||||
// ttn.setAdrEnabled(false);
|
||||
// ttn.setDataRate(kTTNDataRate_US915_SF7);
|
||||
// ttn.setMaxTxPower(14);
|
||||
|
||||
if (ttn.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,6 +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)
|
||||
|
||||
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)
|
||||
|
@ -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")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS "")
|
||||
|
||||
register_component()
|
||||
idf_component_register(
|
||||
SRCS "main.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ttn-esp32)
|
||||
|
@ -11,18 +11,22 @@
|
||||
*******************************************************************************/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_event.h"
|
||||
#include "nvs_flash.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "TheThingsNetwork.h"
|
||||
|
||||
|
||||
// NOTE:
|
||||
// The LoRaWAN frequency and the radio chip must be configured by running 'make menuconfig'.
|
||||
// Go to Components / The Things Network, select the appropriate values and save.
|
||||
|
||||
|
||||
// Pins and other resources
|
||||
#define TTN_SPI_HOST HSPI_HOST
|
||||
#define TTN_SPI_DMA_CHAN 1
|
||||
#define TTN_SPI_HOST SPI2_HOST
|
||||
#define TTN_SPI_DMA_CHAN SPI_DMA_DISABLED
|
||||
#define TTN_PIN_SPI_SCLK 5
|
||||
#define TTN_PIN_SPI_MOSI 27
|
||||
#define TTN_PIN_SPI_MISO 19
|
||||
@ -45,7 +49,7 @@ void sendMessages(void* pvParameter)
|
||||
TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
|
||||
printf(res == kTTNSuccessfulTransmission ? "Message sent.\n" : "Transmission failed.\n");
|
||||
|
||||
vTaskDelay(TX_INTERVAL * 1000 / portTICK_PERIOD_MS);
|
||||
vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,12 +66,11 @@ extern "C" void app_main(void)
|
||||
|
||||
// Initialize SPI bus
|
||||
spi_bus_config_t spi_bus_config;
|
||||
memset(&spi_bus_config, 0, sizeof(spi_bus_config));
|
||||
spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO;
|
||||
spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI;
|
||||
spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK;
|
||||
spi_bus_config.quadwp_io_num = -1;
|
||||
spi_bus_config.quadhd_io_num = -1;
|
||||
spi_bus_config.max_transfer_sz = 0;
|
||||
|
||||
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
|
@ -1,6 +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)
|
||||
|
||||
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": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/manuelbl/ttn-esp32.git",
|
||||
"branch": "dev"
|
||||
"branch": "master"
|
||||
},
|
||||
"version": "2.3.2",
|
||||
"version": "4.2.0-1",
|
||||
"license": "MIT License",
|
||||
"export": {
|
||||
"include": [
|
||||
"include",
|
||||
"src",
|
||||
"esp_idf_lmic_config.h"
|
||||
"src"
|
||||
]
|
||||
},
|
||||
"frameworks": "espidf",
|
||||
|
@ -1,73 +0,0 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||
*
|
||||
* Copyright (c) 2018 Manuel Bleichenbacher
|
||||
*
|
||||
* Licensed under MIT License
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* 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,261 +2,22 @@
|
||||
*
|
||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||
*
|
||||
* Copyright (c) 2018 Manuel Bleichenbacher
|
||||
* Copyright (c) 2018-2021 Manuel Bleichenbacher
|
||||
*
|
||||
* Licensed under MIT License
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* High-level API for ttn-esp32.
|
||||
* High-level C++ API for ttn-esp32.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "TheThingsNetwork.h"
|
||||
#include "hal/hal_esp32.h"
|
||||
#include "lmic/lmic.h"
|
||||
#include "TTNProvisioning.h"
|
||||
|
||||
|
||||
enum ClientAction
|
||||
TTNRFSettings TheThingsNetwork::getRFSettings(TTNRxTxWindow window)
|
||||
{
|
||||
eActionUnrelated,
|
||||
eActionJoining,
|
||||
eActionSending
|
||||
};
|
||||
|
||||
static const char *TAG = "ttn";
|
||||
|
||||
static TheThingsNetwork* ttnInstance;
|
||||
static QueueHandle_t resultQueue;
|
||||
static ClientAction clientAction = eActionUnrelated;
|
||||
static TTNProvisioning provisioning;
|
||||
|
||||
|
||||
TheThingsNetwork::TheThingsNetwork()
|
||||
: messageCallback(nullptr)
|
||||
{
|
||||
#if defined(TTN_IS_DISABLED)
|
||||
ESP_LOGE(TAG, "TTN is disabled. Configure a frequency plan using 'make menuconfig'");
|
||||
ASSERT(0);
|
||||
esp_restart();
|
||||
#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)
|
||||
{
|
||||
lmic_pins.spi_host = spi_host;
|
||||
lmic_pins.nss = nss;
|
||||
lmic_pins.rxtx = rxtx;
|
||||
lmic_pins.rst = rst;
|
||||
lmic_pins.dio0 = dio0;
|
||||
lmic_pins.dio1 = dio1;
|
||||
|
||||
os_init();
|
||||
reset();
|
||||
|
||||
resultQueue = xQueueCreate(12, sizeof(int));
|
||||
ASSERT(resultQueue != nullptr);
|
||||
ttn_hal.startBackgroundTask();
|
||||
}
|
||||
|
||||
void TheThingsNetwork::reset()
|
||||
{
|
||||
ttn_hal.enterCriticalSection();
|
||||
LMIC_reset();
|
||||
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();
|
||||
clientAction = eActionJoining;
|
||||
LMIC_startJoining();
|
||||
ttn_hal.wakeUp();
|
||||
ttn_hal.leaveCriticalSection();
|
||||
|
||||
int result = 0;
|
||||
xQueueReceive(resultQueue, &result, portMAX_DELAY);
|
||||
return result == EV_JOINED;
|
||||
}
|
||||
|
||||
TTNResponseCode TheThingsNetwork::transmitMessage(const uint8_t *payload, size_t length, port_t port, bool confirm)
|
||||
{
|
||||
ttn_hal.enterCriticalSection();
|
||||
if (LMIC.opmode & OP_TXRXPEND)
|
||||
{
|
||||
ttn_hal.leaveCriticalSection();
|
||||
return kTTNErrorTransmissionFailed;
|
||||
}
|
||||
|
||||
clientAction = eActionSending;
|
||||
LMIC_setTxData2(port, (xref2u1_t)payload, length, confirm);
|
||||
ttn_hal.wakeUp();
|
||||
ttn_hal.leaveCriticalSection();
|
||||
|
||||
int result = 0;
|
||||
xQueueReceive(resultQueue, &result, portMAX_DELAY);
|
||||
|
||||
if (result == EV_TXCOMPLETE)
|
||||
{
|
||||
bool hasRecevied = (LMIC.txrxFlags & (TXRX_DNW1 | TXRX_DNW2)) != 0;
|
||||
if (hasRecevied && messageCallback != nullptr)
|
||||
{
|
||||
port_t port = 0;
|
||||
if ((LMIC.txrxFlags & TXRX_PORT))
|
||||
port = LMIC.frame[LMIC.dataBeg - 1];
|
||||
const uint8_t* msg = nullptr;
|
||||
if (LMIC.dataLen > 0)
|
||||
msg = LMIC.frame + LMIC.dataBeg;
|
||||
messageCallback(msg, LMIC.dataLen, port);
|
||||
}
|
||||
|
||||
return kTTNSuccessfulTransmission;
|
||||
}
|
||||
|
||||
return kTTNErrorTransmissionFailed;
|
||||
}
|
||||
|
||||
void TheThingsNetwork::onMessage(TTNMessageCallback callback)
|
||||
{
|
||||
messageCallback = callback;
|
||||
}
|
||||
|
||||
|
||||
bool TheThingsNetwork::isProvisioned()
|
||||
{
|
||||
if (provisioning.haveKeys())
|
||||
return true;
|
||||
|
||||
provisioning.restoreKeys(true);
|
||||
|
||||
return provisioning.haveKeys();
|
||||
}
|
||||
|
||||
|
||||
// --- LMIC functions ---
|
||||
|
||||
#if CONFIG_LOG_DEFAULT_LEVEL >= 3
|
||||
static const char *eventNames[] = {
|
||||
nullptr,
|
||||
"EV_SCAN_TIMEOUT", "EV_BEACON_FOUND",
|
||||
"EV_BEACON_MISSED", "EV_BEACON_TRACKED", "EV_JOINING",
|
||||
"EV_JOINED", "EV_RFU1", "EV_JOIN_FAILED", "EV_REJOIN_FAILED",
|
||||
"EV_TXCOMPLETE", "EV_LOST_TSYNC", "EV_RESET",
|
||||
"EV_RXCOMPLETE", "EV_LINK_DEAD", "EV_LINK_ALIVE", "EV_SCAN_FOUND",
|
||||
"EV_TXSTART"
|
||||
};
|
||||
#endif
|
||||
|
||||
void onEvent (ev_t ev) {
|
||||
#if CONFIG_LOG_DEFAULT_LEVEL >= 3
|
||||
ESP_LOGI(TAG, "event %s", eventNames[ev]);
|
||||
#endif
|
||||
|
||||
if (ev == EV_TXCOMPLETE) {
|
||||
if (LMIC.txrxFlags & TXRX_ACK)
|
||||
ESP_LOGI(TAG, "ACK received\n");
|
||||
}
|
||||
|
||||
if (clientAction == eActionUnrelated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (clientAction == eActionJoining)
|
||||
{
|
||||
if (ev != EV_JOINED && ev != EV_REJOIN_FAILED && ev != EV_RESET)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ev != EV_TXCOMPLETE && ev != EV_LINK_DEAD && ev != EV_RESET)
|
||||
return;
|
||||
}
|
||||
|
||||
int result = ev;
|
||||
clientAction = eActionUnrelated;
|
||||
xQueueSend(resultQueue, &result, 100 / portTICK_PERIOD_MS);
|
||||
ttn_rf_settings_t settings = ttn_get_rf_settings(static_cast<ttn_rx_tx_window_t>(window));
|
||||
TTNRFSettings result;
|
||||
result.spreadingFactor = static_cast<TTNSpreadingFactor>(settings.spreading_factor);
|
||||
result.bandwidth = static_cast<TTNBandwidth>(settings.bandwidth);
|
||||
result.frequency = settings.frequency;
|
||||
return result;
|
||||
}
|
||||
|
0
src/aes/lmic_aes.c
Executable file → Normal file
0
src/aes/lmic_aes.c
Executable file → Normal file
4
src/aes/other.c
Executable file → Normal file
4
src/aes/other.c
Executable file → Normal file
@ -34,9 +34,9 @@
|
||||
// This should be defined elsewhere
|
||||
void lmic_aes_encrypt(u1_t *data, u1_t *key);
|
||||
|
||||
// global area for passing parameters (aux, key) and for storing round keys
|
||||
// global area for passing parameters (aux, key)
|
||||
u4_t AESAUX[16/sizeof(u4_t)];
|
||||
u4_t AESKEY[11*16/sizeof(u4_t)];
|
||||
u4_t AESKEY[16/sizeof(u4_t)];
|
||||
|
||||
// Shift the given buffer left one bit
|
||||
static void shift_left(xref2u1_t buf, u1_t len) {
|
||||
|
@ -17,12 +17,18 @@
|
||||
#elif defined(CONFIG_TTN_LORA_FREQ_US_915)
|
||||
#define CFG_us915 1
|
||||
#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)
|
||||
#define CFG_as923 1
|
||||
#elif defined(CONFIG_TTN_LORA_FREQ_AS_923_JP)
|
||||
#define CFG_as923 1
|
||||
#define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP
|
||||
#elif defined(CONFIG_TTN_LORA_FREQ_KR_920)
|
||||
#define CFG_kr920 1
|
||||
#elif defined(CONFIG_TTN_LORA_FREQ_IN_866)
|
||||
#define CFG_in866 1
|
||||
#else
|
||||
@ -38,26 +44,6 @@
|
||||
#error TTN LoRa radio chip must be configured using 'make menuconfig'
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_TTN_TIMER_0_GROUP_0)
|
||||
#define TTN_TIMER TIMER_0
|
||||
#define TTN_TIMER_GROUP TIMER_GROUP_0
|
||||
#define TTN_CLEAR_TIMER_ALARM TIMERG0.int_clr_timers.t0 = 1
|
||||
#elif defined(CONFIG_TTN_TIMER_1_GROUP_0)
|
||||
#define TTN_TIMER TIMER_1
|
||||
#define TTN_TIMER_GROUP TIMER_GROUP_0
|
||||
#define TTN_CLEAR_TIMER_ALARM TIMERG0.int_clr_timers.t1 = 1
|
||||
#elif defined(CONFIG_TTN_TIMER_0_GROUP_1)
|
||||
#define TTN_TIMER TIMER_0
|
||||
#define TTN_TIMER_GROUP TIMER_GROUP_1
|
||||
#define TTN_CLEAR_TIMER_ALARM TIMERG1.int_clr_timers.t0 = 1
|
||||
#elif defined(CONFIG_TTN_TIMER_1_GROUP_1)
|
||||
#define TTN_TIMER TIMER_1
|
||||
#define TTN_TIMER_GROUP TIMER_GROUP_1
|
||||
#define TTN_CLEAR_TIMER_ALARM TIMERG1.int_clr_timers.t1 = 1
|
||||
#else
|
||||
#error TTN timer must be configured using 'make menuconfig'
|
||||
#endif
|
||||
|
||||
#if !defined(CONFIG_TTN_PROVISION_UART_NONE)
|
||||
#define TTN_HAS_AT_COMMANDS 1
|
||||
#if defined(CONFIG_TTN_PROVISION_UART_CONFIG_YES)
|
||||
@ -65,7 +51,6 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
// 16 μs per tick
|
||||
// LMIC requires ticks to be 15.5μs - 100 μs long
|
||||
#define US_PER_OSTICK 16
|
||||
@ -78,6 +63,8 @@
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#define LMIC_ENABLE_onEvent 0
|
||||
|
||||
#define DISABLE_PING
|
||||
|
||||
#define DISABLE_BEACONS
|
611
src/hal/hal_esp32.c
Executable file
611
src/hal/hal_esp32.c
Executable file
@ -0,0 +1,611 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||
*
|
||||
* Copyright (c) 2018-2019 Manuel Bleichenbacher
|
||||
*
|
||||
* Licensed under MIT License
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "hal_esp32.h"
|
||||
#include "../lmic/lmic.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_log.h"
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#define LMIC_UNUSED_PIN 0xff
|
||||
|
||||
#define NOTIFY_BIT_DIO 1
|
||||
#define NOTIFY_BIT_TIMER 2
|
||||
#define NOTIFY_BIT_WAKEUP 4
|
||||
#define NOTIFY_BIT_STOP 8
|
||||
|
||||
|
||||
#define TAG "ttn_hal"
|
||||
|
||||
|
||||
typedef enum {
|
||||
WAIT_KIND_NONE = 0,
|
||||
WAIT_KIND_CHECK_IO,
|
||||
WAIT_KIND_WAIT_FOR_ANY_EVENT,
|
||||
WAIT_KIND_WAIT_FOR_TIMER
|
||||
} wait_kind_e;
|
||||
|
||||
|
||||
static void lmic_background_task(void* pvParameter);
|
||||
static void qio_irq_handler(void* arg);
|
||||
static void timer_callback(void *arg);
|
||||
static int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time);
|
||||
static int64_t get_current_time();
|
||||
static void init_io(void);
|
||||
static void init_spi(void);
|
||||
static void assert_nss(spi_transaction_t* trans);
|
||||
static void deassert_nss(spi_transaction_t* trans);
|
||||
static void init_timer(void);
|
||||
|
||||
static void set_next_alarm(int64_t time);
|
||||
static void arm_timer(int64_t esp_now);
|
||||
static void disarm_timer(void);
|
||||
static bool wait(wait_kind_e wait_kind);
|
||||
|
||||
static spi_host_device_t spi_host;
|
||||
static gpio_num_t pin_nss;
|
||||
static gpio_num_t pin_rx_tx;
|
||||
static gpio_num_t pin_rst;
|
||||
static gpio_num_t pin_dio0;
|
||||
static gpio_num_t pin_dio1;
|
||||
static int8_t rssi_cal = 10;
|
||||
|
||||
static TaskHandle_t lmic_task;
|
||||
static uint32_t dio_interrupt_time;
|
||||
static uint8_t dio_num;
|
||||
|
||||
static spi_device_handle_t spi_handle;
|
||||
static spi_transaction_t spi_transaction;
|
||||
static SemaphoreHandle_t mutex;
|
||||
static esp_timer_handle_t timer;
|
||||
static int64_t time_offset;
|
||||
static int32_t initial_time_offset;
|
||||
static int64_t next_alarm;
|
||||
static volatile bool run_background_task;
|
||||
static volatile wait_kind_e current_wait_kind;
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// I/O
|
||||
|
||||
void hal_esp32_configure_pins(spi_host_device_t host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1)
|
||||
{
|
||||
spi_host = host;
|
||||
pin_nss = (gpio_num_t)nss;
|
||||
pin_rx_tx = (gpio_num_t)rxtx;
|
||||
pin_rst = (gpio_num_t)rst;
|
||||
pin_dio0 = (gpio_num_t)dio0;
|
||||
pin_dio1 = (gpio_num_t)dio1;
|
||||
|
||||
// Until the background process has been started, use the current task
|
||||
// for supporting calls like `hal_waitUntil()`.
|
||||
lmic_task = xTaskGetCurrentTaskHandle();
|
||||
}
|
||||
|
||||
|
||||
void IRAM_ATTR qio_irq_handler(void *arg)
|
||||
{
|
||||
dio_interrupt_time = hal_ticks();
|
||||
dio_num = (u1_t)(long)arg;
|
||||
BaseType_t higher_prio_task_woken = pdFALSE;
|
||||
xTaskNotifyFromISR(lmic_task, NOTIFY_BIT_DIO, eSetBits, &higher_prio_task_woken);
|
||||
if (higher_prio_task_woken)
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
|
||||
void init_io(void)
|
||||
{
|
||||
// pin_nss, pin_dio0 and pin_dio1 are required
|
||||
ASSERT(pin_nss != LMIC_UNUSED_PIN);
|
||||
ASSERT(pin_dio0 != LMIC_UNUSED_PIN);
|
||||
ASSERT(pin_dio1 != LMIC_UNUSED_PIN);
|
||||
|
||||
gpio_config_t output_pin_config = {
|
||||
.pin_bit_mask = BIT64(pin_nss),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = false,
|
||||
.pull_down_en = false,
|
||||
.intr_type = GPIO_INTR_DISABLE
|
||||
};
|
||||
|
||||
if (pin_rx_tx != LMIC_UNUSED_PIN)
|
||||
output_pin_config.pin_bit_mask |= BIT64(pin_rx_tx);
|
||||
|
||||
if (pin_rst != LMIC_UNUSED_PIN)
|
||||
output_pin_config.pin_bit_mask |= BIT64(pin_rst);
|
||||
|
||||
gpio_config(&output_pin_config);
|
||||
|
||||
gpio_set_level(pin_nss, 1);
|
||||
if (pin_rx_tx != LMIC_UNUSED_PIN)
|
||||
gpio_set_level(pin_rx_tx, 0);
|
||||
if (pin_rst != LMIC_UNUSED_PIN)
|
||||
gpio_set_level(pin_rst, 0);
|
||||
|
||||
// DIO pins with interrupt handlers
|
||||
gpio_config_t input_pin_config = {
|
||||
.pin_bit_mask = BIT64(pin_dio0) | BIT64(pin_dio1),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = false,
|
||||
.pull_down_en = true,
|
||||
.intr_type = GPIO_INTR_POSEDGE,
|
||||
};
|
||||
gpio_config(&input_pin_config);
|
||||
|
||||
ESP_LOGI(TAG, "IO initialized");
|
||||
}
|
||||
|
||||
__attribute__((weak)) // duplicate this symbol if your pin is controlled by e.g. I2C
|
||||
void hal_pin_rxtx(u1_t val)
|
||||
{
|
||||
if (pin_rx_tx == LMIC_UNUSED_PIN)
|
||||
return;
|
||||
|
||||
gpio_set_level(pin_rx_tx, val);
|
||||
}
|
||||
|
||||
__attribute__((weak)) // duplicate this symbol if your pin is controlled by e.g. I2C
|
||||
void hal_pin_rst(u1_t val)
|
||||
{
|
||||
if (pin_rst == LMIC_UNUSED_PIN)
|
||||
return;
|
||||
|
||||
if (val == 0 || val == 1)
|
||||
{
|
||||
// drive pin
|
||||
gpio_set_level(pin_rst, val);
|
||||
gpio_set_direction(pin_rst, GPIO_MODE_OUTPUT);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(CONFIG_TTN_RESET_STATES_ASSERTED)
|
||||
// drive up the pin because the hardware is nonstandard
|
||||
gpio_set_level(pin_rst, 1);
|
||||
gpio_set_direction(pin_rst, GPIO_MODE_OUTPUT);
|
||||
#else
|
||||
// keep pin floating
|
||||
gpio_set_direction(pin_rst, GPIO_MODE_INPUT);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
s1_t hal_getRssiCal(void)
|
||||
{
|
||||
return rssi_cal;
|
||||
}
|
||||
|
||||
ostime_t hal_setModuleActive(bit_t val)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bit_t hal_queryUsingTcxo(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t hal_getTxPowerPolicy(u1_t inputPolicy, s1_t requestedPower, u4_t frequency)
|
||||
{
|
||||
return LMICHAL_radio_tx_power_policy_paboost;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SPI
|
||||
|
||||
void init_spi(void)
|
||||
{
|
||||
// init device
|
||||
spi_device_interface_config_t spi_config = {
|
||||
.mode = 0,
|
||||
.clock_speed_hz = CONFIG_TTN_SPI_FREQ,
|
||||
.command_bits = 0,
|
||||
.address_bits = 8,
|
||||
.spics_io_num = -1,
|
||||
.queue_size = 1,
|
||||
.pre_cb = assert_nss,
|
||||
.post_cb = deassert_nss,
|
||||
};
|
||||
|
||||
esp_err_t ret = spi_bus_add_device(spi_host, &spi_config, &spi_handle);
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
ESP_LOGI(TAG, "SPI initialized");
|
||||
}
|
||||
|
||||
void hal_spi_write(u1_t cmd, const u1_t *buf, size_t len)
|
||||
{
|
||||
memset(&spi_transaction, 0, sizeof(spi_transaction));
|
||||
spi_transaction.addr = cmd;
|
||||
spi_transaction.length = 8 * len;
|
||||
spi_transaction.tx_buffer = buf;
|
||||
esp_err_t err = spi_device_transmit(spi_handle, &spi_transaction);
|
||||
ESP_ERROR_CHECK(err);
|
||||
}
|
||||
|
||||
void hal_spi_read(u1_t cmd, u1_t *buf, size_t len)
|
||||
{
|
||||
memset(buf, 0, len);
|
||||
memset(&spi_transaction, 0, sizeof(spi_transaction));
|
||||
spi_transaction.addr = cmd;
|
||||
spi_transaction.length = 8 * len;
|
||||
spi_transaction.rxlength = 8 * len;
|
||||
spi_transaction.tx_buffer = buf;
|
||||
spi_transaction.rx_buffer = buf;
|
||||
esp_err_t err = spi_device_transmit(spi_handle, &spi_transaction);
|
||||
ESP_ERROR_CHECK(err);
|
||||
}
|
||||
|
||||
|
||||
void IRAM_ATTR assert_nss(spi_transaction_t* trans)
|
||||
{
|
||||
gpio_set_level(pin_nss, 0);
|
||||
}
|
||||
|
||||
void IRAM_ATTR deassert_nss(spi_transaction_t* trans)
|
||||
{
|
||||
gpio_set_level(pin_nss, 1);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// TIME
|
||||
|
||||
/*
|
||||
* LIMIC uses a 32 bit time system (ostime_t) counting ticks. In this
|
||||
* implementation, each tick is 16µs. It will wrap arounnd every 19 hours.
|
||||
*
|
||||
* The ESP32 has a 64 bit timer counting microseconds. It will wrap around
|
||||
* every 584,000 years. So we don't need to bother.
|
||||
*
|
||||
* The time includes an offset initialized from `gettimeofday()`
|
||||
* to ensure the time correctly advances during deep sleep.
|
||||
*
|
||||
* Based on this timer, future callbacks can be scheduled. This is used to
|
||||
* schedule the next LMIC job.
|
||||
*/
|
||||
|
||||
// Convert LMIC tick time (ostime_t) to ESP absolute time.
|
||||
// `os_time` is assumed to be somewhere between one hour in the past and
|
||||
// 18 hours into the future.
|
||||
int64_t os_time_to_esp_time(int64_t esp_now, uint32_t os_time)
|
||||
{
|
||||
int64_t esp_time;
|
||||
uint32_t os_now = (uint32_t)(esp_now >> 4);
|
||||
|
||||
// unsigned difference:
|
||||
// 0x00000000 - 0xefffffff: future (0 to about 18 hours)
|
||||
// 0xf0000000 - 0xffffffff: past (about 1 to 0 hours)
|
||||
uint32_t os_diff = os_time - os_now;
|
||||
if (os_diff < 0xf0000000)
|
||||
{
|
||||
esp_time = esp_now + (((int64_t)os_diff) << 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
os_diff = -os_diff;
|
||||
esp_time = esp_now - (((int64_t)os_diff) << 4);
|
||||
}
|
||||
|
||||
return esp_time;
|
||||
}
|
||||
|
||||
int64_t IRAM_ATTR get_current_time()
|
||||
{
|
||||
return esp_timer_get_time() + time_offset;
|
||||
}
|
||||
|
||||
void init_timer(void)
|
||||
{
|
||||
esp_timer_create_args_t timer_config = {
|
||||
.callback = &timer_callback,
|
||||
.arg = NULL,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "lmic_job"
|
||||
};
|
||||
esp_err_t err = esp_timer_create(&timer_config, &timer);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
time_offset += (int64_t)now.tv_sec * 1000000;
|
||||
initial_time_offset = 0;
|
||||
|
||||
ESP_LOGI(TAG, "Timer initialized");
|
||||
}
|
||||
|
||||
uint32_t hal_esp32_get_time(void)
|
||||
{
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
return now.tv_sec + initial_time_offset;
|
||||
}
|
||||
|
||||
void hal_esp32_set_time(uint32_t time_val)
|
||||
{
|
||||
initial_time_offset = time_val;
|
||||
time_offset = (int64_t)time_val * 1000000;
|
||||
}
|
||||
|
||||
void set_next_alarm(int64_t time)
|
||||
{
|
||||
next_alarm = time;
|
||||
}
|
||||
|
||||
void arm_timer(int64_t esp_now)
|
||||
{
|
||||
if (next_alarm == 0)
|
||||
return;
|
||||
int64_t timeout = next_alarm - get_current_time();
|
||||
if (timeout < 0)
|
||||
timeout = 10;
|
||||
esp_timer_start_once(timer, timeout);
|
||||
}
|
||||
|
||||
void disarm_timer(void)
|
||||
{
|
||||
esp_timer_stop(timer);
|
||||
}
|
||||
|
||||
void timer_callback(void *arg)
|
||||
{
|
||||
xTaskNotify(lmic_task, NOTIFY_BIT_TIMER, eSetBits);
|
||||
}
|
||||
|
||||
// Wait for the next external event. Either:
|
||||
// - scheduled timer due to scheduled job or waiting for a given time
|
||||
// - wake up event from the client code
|
||||
// - I/O interrupt (DIO0 or DIO1 pin)
|
||||
bool wait(wait_kind_e wait_kind)
|
||||
{
|
||||
TickType_t ticks_to_wait = wait_kind == WAIT_KIND_CHECK_IO ? 0 : portMAX_DELAY;
|
||||
while (true)
|
||||
{
|
||||
current_wait_kind = wait_kind;
|
||||
uint32_t bits = ulTaskNotifyTake(pdTRUE, ticks_to_wait);
|
||||
current_wait_kind = WAIT_KIND_NONE;
|
||||
if (bits == 0)
|
||||
return false;
|
||||
|
||||
if ((bits & NOTIFY_BIT_STOP) != 0)
|
||||
return false;
|
||||
|
||||
if ((bits & NOTIFY_BIT_WAKEUP) != 0)
|
||||
{
|
||||
if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER)
|
||||
{
|
||||
disarm_timer();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ((bits & NOTIFY_BIT_TIMER) != 0)
|
||||
{
|
||||
disarm_timer();
|
||||
set_next_alarm(0);
|
||||
if (wait_kind != WAIT_KIND_CHECK_IO)
|
||||
return true;
|
||||
}
|
||||
else // IO interrupt
|
||||
{
|
||||
if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER)
|
||||
disarm_timer();
|
||||
hal_esp32_enter_critical_section();
|
||||
radio_irq_handler_v2(dio_num, dio_interrupt_time);
|
||||
hal_esp32_leave_critical_section();
|
||||
if (wait_kind != WAIT_KIND_WAIT_FOR_TIMER)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TickType_t hal_esp32_get_timer_duration(void)
|
||||
{
|
||||
wait_kind_e wait_kind = current_wait_kind;
|
||||
int64_t alarm_time = next_alarm;
|
||||
|
||||
if (wait_kind == WAIT_KIND_NONE || wait_kind == WAIT_KIND_CHECK_IO)
|
||||
return 1; // busy, not waiting
|
||||
|
||||
if (alarm_time != 0)
|
||||
{
|
||||
TickType_t dur = pdMS_TO_TICKS((alarm_time - get_current_time() + 999) / 1000);
|
||||
if (dur > pdMS_TO_TICKS(30000))
|
||||
dur = pdMS_TO_TICKS(200);
|
||||
return dur;
|
||||
}
|
||||
|
||||
|
||||
return 0; // waiting indefinitely
|
||||
}
|
||||
|
||||
|
||||
// Gets current time in LMIC ticks
|
||||
u4_t IRAM_ATTR hal_ticks(void)
|
||||
{
|
||||
// LMIC tick unit: 16µs
|
||||
// esp_timer unit: 1µs
|
||||
return (u4_t)(get_current_time() >> 4);
|
||||
}
|
||||
|
||||
// Wait until the specified time.
|
||||
// Called if the LMIC code needs to wait for a precise time.
|
||||
// All other events are ignored and will be served later.
|
||||
u4_t hal_waitUntil(u4_t time)
|
||||
{
|
||||
int64_t esp_now = get_current_time();
|
||||
int64_t esp_time = os_time_to_esp_time(esp_now, time);
|
||||
set_next_alarm(esp_time);
|
||||
arm_timer(esp_now);
|
||||
wait(WAIT_KIND_WAIT_FOR_TIMER);
|
||||
|
||||
u4_t os_now = hal_ticks();
|
||||
u4_t diff = os_now - time;
|
||||
return diff < 0x80000000U ? diff : 0;
|
||||
}
|
||||
|
||||
// Called by client code to wake up LMIC to do something,
|
||||
// e.g. send a submitted messages.
|
||||
void hal_esp32_wake_up(void)
|
||||
{
|
||||
xTaskNotify(lmic_task, NOTIFY_BIT_WAKEUP, eSetBits);
|
||||
}
|
||||
|
||||
// Check if the specified time has been reached or almost reached.
|
||||
// Otherwise, save it as alarm time.
|
||||
// LMIC calls this function with the scheduled time of the next job
|
||||
// in the queue. If the job is not due yet, LMIC will go to sleep.
|
||||
u1_t hal_checkTimer(uint32_t time)
|
||||
{
|
||||
int64_t esp_now = get_current_time();
|
||||
int64_t esp_time = os_time_to_esp_time(esp_now, time);
|
||||
int64_t diff = esp_time - esp_now;
|
||||
if (diff < 100)
|
||||
return 1; // timer has expired or will expire very soon
|
||||
|
||||
set_next_alarm(esp_time);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Go to sleep until next event.
|
||||
// Called when LMIC is not busy and not job is due to be executed.
|
||||
void hal_sleep(void)
|
||||
{
|
||||
if (wait(WAIT_KIND_CHECK_IO))
|
||||
return;
|
||||
|
||||
arm_timer(get_current_time());
|
||||
wait(WAIT_KIND_WAIT_FOR_ANY_EVENT);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// IRQ
|
||||
|
||||
void hal_disableIRQs(void)
|
||||
{
|
||||
// nothing to do as interrupt handlers post message to queue
|
||||
// and don't access any shared data structures
|
||||
}
|
||||
|
||||
void hal_enableIRQs(void)
|
||||
{
|
||||
// nothing to do as interrupt handlers post message to queue
|
||||
// and don't access any shared data structures
|
||||
}
|
||||
|
||||
void hal_processPendingIRQs(void)
|
||||
{
|
||||
// nothing to do as interrupt handlers post message to queue
|
||||
// and don't access any shared data structures
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Synchronization between application code and background task
|
||||
|
||||
void hal_esp32_init_critical_section(void)
|
||||
{
|
||||
mutex = xSemaphoreCreateRecursiveMutex();
|
||||
}
|
||||
|
||||
void hal_esp32_enter_critical_section(void)
|
||||
{
|
||||
xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void hal_esp32_leave_critical_section(void)
|
||||
{
|
||||
xSemaphoreGiveRecursive(mutex);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void lmic_background_task(void* pvParameter)
|
||||
{
|
||||
while (run_background_task)
|
||||
os_runloop_once();
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void hal_init_ex(const void *pContext)
|
||||
{
|
||||
// configure radio I/O and interrupt handler
|
||||
init_io();
|
||||
// configure radio SPI
|
||||
init_spi();
|
||||
// configure timer and alarm callback
|
||||
init_timer();
|
||||
}
|
||||
|
||||
void hal_esp32_start_lmic_task(void)
|
||||
{
|
||||
run_background_task = true;
|
||||
xTaskCreate(lmic_background_task, "ttn_lmic", 1024 * 4, NULL, CONFIG_TTN_BG_TASK_PRIO, &lmic_task);
|
||||
|
||||
// enable interrupts
|
||||
gpio_isr_handler_add(pin_dio0, qio_irq_handler, (void *)0);
|
||||
gpio_isr_handler_add(pin_dio1, qio_irq_handler, (void *)1);
|
||||
}
|
||||
|
||||
void hal_esp32_stop_lmic_task(void)
|
||||
{
|
||||
run_background_task = false;
|
||||
gpio_isr_handler_remove(pin_dio0);
|
||||
gpio_isr_handler_remove(pin_dio1);
|
||||
disarm_timer();
|
||||
set_next_alarm(0);
|
||||
xTaskNotify(lmic_task, NOTIFY_BIT_STOP, eSetBits);
|
||||
lmic_task = xTaskGetCurrentTaskHandle();
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Fatal failure
|
||||
|
||||
static hal_failure_handler_t* custom_hal_failure_handler = NULL;
|
||||
|
||||
void hal_set_failure_handler(hal_failure_handler_t* const handler)
|
||||
{
|
||||
custom_hal_failure_handler = handler;
|
||||
}
|
||||
|
||||
void hal_failed(const char *file, u2_t line)
|
||||
{
|
||||
if (custom_hal_failure_handler != NULL)
|
||||
(*custom_hal_failure_handler)(file, line);
|
||||
|
||||
ESP_LOGE(TAG, "LMIC failed and stopped: %s:%d", file, line);
|
||||
|
||||
// go to sleep forever
|
||||
while (true)
|
||||
{
|
||||
vTaskDelay(portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// RSSI
|
||||
|
||||
void hal_esp32_set_rssi_cal(int8_t cal)
|
||||
{
|
||||
rssi_cal = cal;
|
||||
}
|
@ -1,450 +0,0 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||
*
|
||||
* Copyright (c) 2018 Manuel Bleichenbacher
|
||||
*
|
||||
* Licensed under MIT License
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* 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
|
||||
|
||||
static const char * const TAG = "ttn_hal";
|
||||
|
||||
lmic_pinmap lmic_pins;
|
||||
HAL_ESP32 ttn_hal;
|
||||
|
||||
|
||||
struct HALQueueItem {
|
||||
ostime_t time;
|
||||
HAL_Event ev;
|
||||
|
||||
HALQueueItem() : time(0), ev(DIO0) { }
|
||||
HALQueueItem(HAL_Event e, ostime_t t = 0)
|
||||
: time(t), ev(e) { }
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Constructor
|
||||
|
||||
HAL_ESP32::HAL_ESP32()
|
||||
: nextTimerEvent(0x200000000)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// I/O
|
||||
|
||||
void IRAM_ATTR HAL_ESP32::dioIrqHandler(void *arg)
|
||||
{
|
||||
uint64_t now;
|
||||
timer_get_counter_value(TTN_TIMER_GROUP, TTN_TIMER, &now);
|
||||
BaseType_t higherPrioTaskWoken = pdFALSE;
|
||||
HALQueueItem item { (HAL_Event)(long)arg, (ostime_t)now };
|
||||
xQueueSendFromISR(ttn_hal.dioQueue, &item, &higherPrioTaskWoken);
|
||||
if (higherPrioTaskWoken)
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
|
||||
void HAL_ESP32::ioInit()
|
||||
{
|
||||
// NSS and DIO0 and DIO1 are required
|
||||
ASSERT(lmic_pins.nss != LMIC_UNUSED_PIN);
|
||||
ASSERT(lmic_pins.dio0 != LMIC_UNUSED_PIN);
|
||||
ASSERT(lmic_pins.dio1 != LMIC_UNUSED_PIN);
|
||||
|
||||
gpio_pad_select_gpio(lmic_pins.nss);
|
||||
gpio_set_level((gpio_num_t)lmic_pins.nss, 0);
|
||||
gpio_set_direction((gpio_num_t)lmic_pins.nss, GPIO_MODE_OUTPUT);
|
||||
|
||||
if (lmic_pins.rxtx != LMIC_UNUSED_PIN)
|
||||
{
|
||||
gpio_pad_select_gpio(lmic_pins.rxtx);
|
||||
gpio_set_level((gpio_num_t)lmic_pins.rxtx, 0);
|
||||
gpio_set_direction((gpio_num_t)lmic_pins.rxtx, GPIO_MODE_OUTPUT);
|
||||
}
|
||||
|
||||
if (lmic_pins.rst != LMIC_UNUSED_PIN)
|
||||
{
|
||||
gpio_pad_select_gpio((gpio_num_t)lmic_pins.rst);
|
||||
gpio_set_level((gpio_num_t)lmic_pins.rst, 0);
|
||||
gpio_set_direction((gpio_num_t)lmic_pins.rst, GPIO_MODE_OUTPUT);
|
||||
}
|
||||
|
||||
dioQueue = xQueueCreate(12, sizeof(HALQueueItem));
|
||||
ASSERT(dioQueue != NULL);
|
||||
|
||||
gpio_pad_select_gpio(lmic_pins.dio0);
|
||||
gpio_set_direction((gpio_num_t)lmic_pins.dio0, GPIO_MODE_INPUT);
|
||||
gpio_set_intr_type((gpio_num_t)lmic_pins.dio0, GPIO_INTR_POSEDGE);
|
||||
gpio_isr_handler_add((gpio_num_t)lmic_pins.dio0, dioIrqHandler, (void *)0);
|
||||
|
||||
gpio_pad_select_gpio((gpio_num_t)lmic_pins.dio1);
|
||||
gpio_set_direction((gpio_num_t)lmic_pins.dio1, GPIO_MODE_INPUT);
|
||||
gpio_set_intr_type((gpio_num_t)lmic_pins.dio1, GPIO_INTR_POSEDGE);
|
||||
gpio_isr_handler_add((gpio_num_t)lmic_pins.dio1, dioIrqHandler, (void *)1);
|
||||
|
||||
ESP_LOGI(TAG, "IO initialized");
|
||||
}
|
||||
|
||||
void hal_pin_rxtx(u1_t val)
|
||||
{
|
||||
if (lmic_pins.rxtx == LMIC_UNUSED_PIN)
|
||||
return;
|
||||
|
||||
gpio_set_level((gpio_num_t)lmic_pins.rxtx, val);
|
||||
}
|
||||
|
||||
void hal_pin_rst(u1_t val)
|
||||
{
|
||||
if (lmic_pins.rst == LMIC_UNUSED_PIN)
|
||||
return;
|
||||
|
||||
if (val == 0 || val == 1)
|
||||
{ // drive pin
|
||||
gpio_set_level((gpio_num_t)lmic_pins.rst, val);
|
||||
gpio_set_direction((gpio_num_t)lmic_pins.rst, GPIO_MODE_OUTPUT);
|
||||
}
|
||||
else
|
||||
{ // keep pin floating
|
||||
gpio_set_level((gpio_num_t)lmic_pins.rst, val);
|
||||
gpio_set_direction((gpio_num_t)lmic_pins.rst, GPIO_MODE_INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
s1_t hal_getRssiCal (void)
|
||||
{
|
||||
return lmic_pins.rssi_cal;
|
||||
}
|
||||
|
||||
ostime_t hal_setModuleActive (bit_t val)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bit_t hal_queryUsingTcxo(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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 = lmic_pins.nss;
|
||||
spiConfig.queue_size = 1;
|
||||
spiConfig.cs_ena_posttrans = 2;
|
||||
|
||||
esp_err_t ret = spi_bus_add_device(lmic_pins.spi_host, &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 (ostime_t) counting ticks. In this implementation
|
||||
* each tick is 16µs. So the timer will wrap around once every 19 hour.
|
||||
* The timer alarm should trigger when a specific value has been reached.
|
||||
* Due to the wrap around, an alarm time in the future can have a lower value
|
||||
* than the current timer value.
|
||||
*
|
||||
* ESP32 has 64 bits counters with a pecularity: the alarm does not only
|
||||
* trigger when the exact value has been reached but also when the clock is
|
||||
* higer than the alarm value. Therefore, the wrap around is more difficult to
|
||||
* handle.
|
||||
*
|
||||
* The approach here is to always use a higher value than the current timer
|
||||
* value. If it would be lower than the timer value, 0x100000000 is added.
|
||||
* The lower 32 bits still represent the desired value. After the timer has
|
||||
* triggered an alarm and is higher than 0x100000000, it's value is reduced
|
||||
* by 0x100000000.
|
||||
*/
|
||||
|
||||
static const ostime_t OVERRUN_TRESHOLD = 0x10000; // approx 10 seconds
|
||||
|
||||
void HAL_ESP32::timerInit()
|
||||
{
|
||||
timer_config_t config = {
|
||||
.alarm_en = false,
|
||||
.counter_en = false,
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.auto_reload = false,
|
||||
.divider = 1280 /* 80 MHz APB_CLK * 16µs */
|
||||
};
|
||||
timer_init(TTN_TIMER_GROUP, TTN_TIMER, &config);
|
||||
timer_set_counter_value(TTN_TIMER_GROUP, TTN_TIMER, 0x0);
|
||||
timer_isr_register(TTN_TIMER_GROUP, TTN_TIMER, timerIrqHandler, NULL, ESP_INTR_FLAG_IRAM, NULL);
|
||||
timer_start(TTN_TIMER_GROUP, TTN_TIMER);
|
||||
|
||||
ESP_LOGI(TAG, "Timer initialized");
|
||||
}
|
||||
|
||||
void HAL_ESP32::prepareNextAlarm(u4_t time)
|
||||
{
|
||||
uint64_t now;
|
||||
timer_get_counter_value(TTN_TIMER_GROUP, TTN_TIMER, &now);
|
||||
u4_t now32 = (u4_t)now;
|
||||
|
||||
if (now != now32)
|
||||
{
|
||||
// decrease timer to 32 bit value
|
||||
now = now32;
|
||||
timer_pause(TTN_TIMER_GROUP, TTN_TIMER);
|
||||
timer_set_counter_value(TTN_TIMER_GROUP, TTN_TIMER, now);
|
||||
timer_start(TTN_TIMER_GROUP, TTN_TIMER);
|
||||
}
|
||||
|
||||
nextTimerEvent = time;
|
||||
if (now32 > time && now32 - time > OVERRUN_TRESHOLD)
|
||||
nextTimerEvent += 0x100000000;
|
||||
}
|
||||
|
||||
void HAL_ESP32::armTimer()
|
||||
{
|
||||
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_DIS);
|
||||
timer_set_alarm_value(TTN_TIMER_GROUP, TTN_TIMER, nextTimerEvent);
|
||||
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_EN);
|
||||
}
|
||||
|
||||
void HAL_ESP32::disarmTimer()
|
||||
{
|
||||
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_DIS);
|
||||
nextTimerEvent = 0x200000000; // wait indefinitely (almost)
|
||||
}
|
||||
|
||||
void IRAM_ATTR HAL_ESP32::timerIrqHandler(void *arg)
|
||||
{
|
||||
TTN_CLEAR_TIMER_ALARM;
|
||||
BaseType_t higherPrioTaskWoken = pdFALSE;
|
||||
HALQueueItem item { TIMER };
|
||||
xQueueSendFromISR(ttn_hal.dioQueue, &item, &higherPrioTaskWoken);
|
||||
if (higherPrioTaskWoken)
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
|
||||
bool HAL_ESP32::wait(WaitKind waitKind)
|
||||
{
|
||||
TickType_t ticksToWait = waitKind == CHECK_IO ? 0 : portMAX_DELAY;
|
||||
while (true)
|
||||
{
|
||||
HALQueueItem item;
|
||||
if (xQueueReceive(dioQueue, &item, ticksToWait) == pdFALSE)
|
||||
return false;
|
||||
|
||||
if (item.ev == WAKEUP)
|
||||
{
|
||||
if (waitKind != WAIT_FOR_TIMER)
|
||||
{
|
||||
disarmTimer();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (item.ev == TIMER)
|
||||
{
|
||||
disarmTimer();
|
||||
if (waitKind != CHECK_IO)
|
||||
return true;
|
||||
}
|
||||
else // IO interrupt
|
||||
{
|
||||
if (waitKind != WAIT_FOR_TIMER)
|
||||
disarmTimer();
|
||||
enterCriticalSection();
|
||||
radio_irq_handler_v2(item.ev, item.time);
|
||||
leaveCriticalSection();
|
||||
if (waitKind != WAIT_FOR_TIMER)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u4_t hal_ticks()
|
||||
{
|
||||
uint64_t val;
|
||||
timer_get_counter_value(TTN_TIMER_GROUP, TTN_TIMER, &val);
|
||||
return (u4_t)val;
|
||||
}
|
||||
|
||||
void hal_waitUntil(u4_t time)
|
||||
{
|
||||
ttn_hal.waitUntil(time);
|
||||
}
|
||||
|
||||
void HAL_ESP32::waitUntil(uint32_t time)
|
||||
{
|
||||
prepareNextAlarm(time);
|
||||
armTimer();
|
||||
wait(WAIT_FOR_TIMER);
|
||||
}
|
||||
|
||||
void HAL_ESP32::wakeUp()
|
||||
{
|
||||
HALQueueItem item { WAKEUP };
|
||||
xQueueSend(dioQueue, &item, 0);
|
||||
}
|
||||
|
||||
// check and rewind for target time
|
||||
u1_t hal_checkTimer(u4_t time)
|
||||
{
|
||||
return ttn_hal.checkTimer(time);
|
||||
}
|
||||
|
||||
uint8_t HAL_ESP32::checkTimer(uint32_t time)
|
||||
{
|
||||
uint64_t now;
|
||||
timer_get_counter_value(TTN_TIMER_GROUP, TTN_TIMER, &now);
|
||||
u4_t now32 = (u4_t)now;
|
||||
|
||||
if (time >= now32)
|
||||
{
|
||||
if (time - now32 < 5)
|
||||
return 1; // timer will expire very soon
|
||||
}
|
||||
else
|
||||
{
|
||||
if (now32 - time < OVERRUN_TRESHOLD)
|
||||
return 1; // timer has expired recently
|
||||
}
|
||||
|
||||
prepareNextAlarm(time);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hal_sleep()
|
||||
{
|
||||
ttn_hal.sleep();
|
||||
}
|
||||
|
||||
void HAL_ESP32::sleep()
|
||||
{
|
||||
if (wait(CHECK_IO))
|
||||
return;
|
||||
|
||||
armTimer();
|
||||
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::backgroundTask(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 interrupt handler
|
||||
timerInit();
|
||||
}
|
||||
|
||||
void HAL_ESP32::startBackgroundTask() {
|
||||
xTaskCreate(backgroundTask, "ttn_lora_task", 1024 * 4, NULL, CONFIG_TTN_BG_TASK_PRIO, NULL);
|
||||
}
|
||||
|
||||
void hal_failed(const char *file, u2_t line)
|
||||
{
|
||||
ESP_LOGE(TAG, "%s:%d", file, line);
|
||||
ASSERT(0);
|
||||
}
|
@ -2,93 +2,63 @@
|
||||
*
|
||||
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
|
||||
*
|
||||
* Copyright (c) 2018 Manuel Bleichenbacher
|
||||
* Copyright (c) 2018-2021 Manuel Bleichenbacher
|
||||
*
|
||||
* Licensed under MIT License
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* 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_
|
||||
#define _hal_esp32_h_
|
||||
#ifndef HAL_ESP32_H
|
||||
#define HAL_ESP32_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct lmic_pinmap {
|
||||
spi_host_device_t spi_host;
|
||||
uint8_t nss;
|
||||
uint8_t rxtx;
|
||||
uint8_t rst;
|
||||
uint8_t dio0;
|
||||
uint8_t dio1;
|
||||
int8_t rssi_cal; // cal in dB -- added to RSSI measured prior to decision. Must include noise guardband!
|
||||
} lmic_pinmap;
|
||||
|
||||
extern lmic_pinmap lmic_pins;
|
||||
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);
|
||||
|
||||
void hal_esp32_wake_up(void);
|
||||
void hal_esp32_init_critical_section(void);
|
||||
void hal_esp32_enter_critical_section(void);
|
||||
void hal_esp32_leave_critical_section(void);
|
||||
|
||||
void hal_esp32_set_rssi_cal(int8_t rssi_cal);
|
||||
|
||||
TickType_t hal_esp32_get_timer_duration(void);
|
||||
|
||||
/**
|
||||
* Gets the time.
|
||||
*
|
||||
* The time is relative to boot time of the
|
||||
* run when the device joined the TTN network.
|
||||
*
|
||||
* @return time (in seconds)
|
||||
*/
|
||||
uint32_t hal_esp32_get_time(void);
|
||||
|
||||
/**
|
||||
* Sets the time.
|
||||
*
|
||||
* The time is relative to boot time of the
|
||||
* run when the device joined the TTN network.
|
||||
*
|
||||
* @param time_val time (in seconds)
|
||||
*/
|
||||
void hal_esp32_set_time(uint32_t time_val);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
enum HAL_Event {
|
||||
DIO0 = 0,
|
||||
DIO1,
|
||||
DIO2,
|
||||
TIMER,
|
||||
WAKEUP
|
||||
};
|
||||
|
||||
|
||||
enum WaitKind {
|
||||
CHECK_IO,
|
||||
WAIT_FOR_ANY_EVENT,
|
||||
WAIT_FOR_TIMER
|
||||
};
|
||||
|
||||
|
||||
|
||||
class HAL_ESP32
|
||||
{
|
||||
public:
|
||||
HAL_ESP32();
|
||||
|
||||
void init();
|
||||
void startBackgroundTask();
|
||||
void wakeUp();
|
||||
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);
|
||||
uint8_t checkTimer(uint32_t time);
|
||||
void sleep();
|
||||
void waitUntil(uint32_t time);
|
||||
|
||||
private:
|
||||
static void backgroundTask(void* pvParameter);
|
||||
static void dioIrqHandler(void* arg);
|
||||
void ioInit();
|
||||
void spiInit();
|
||||
void timerInit();
|
||||
void prepareNextAlarm(uint32_t time);
|
||||
void armTimer();
|
||||
void disarmTimer();
|
||||
static void IRAM_ATTR timerIrqHandler(void *arg);
|
||||
bool wait(WaitKind waitKind);
|
||||
|
||||
QueueHandle_t dioQueue;
|
||||
spi_device_handle_t spiHandle;
|
||||
spi_transaction_t spiTransaction;
|
||||
uint64_t nextTimerEvent;
|
||||
SemaphoreHandle_t mutex;
|
||||
};
|
||||
|
||||
extern HAL_ESP32 ttn_hal;
|
||||
|
||||
|
||||
#endif // _hal_esp32_h_
|
||||
#endif // HAL_ESP32_H
|
||||
|
72
src/lmic/config.h
Executable file → Normal file
72
src/lmic/config.h
Executable file → Normal file
@ -116,22 +116,16 @@
|
||||
|
||||
// define these in lmic_project_config.h to disable the corresponding MAC commands.
|
||||
// Class A
|
||||
//#define DISABLE_MCMD_DCAP_REQ // duty cycle cap
|
||||
//#define DISABLE_MCMD_DN2P_SET // 2nd DN window param
|
||||
//#define DISABLE_MCMD_SNCH_REQ // set new channel
|
||||
//#define DISABLE_MCMD_DutyCycleReq // duty cycle cap
|
||||
//#define DISABLE_MCMD_RXParamSetupReq // 2nd DN window param
|
||||
//#define DISABLE_MCMD_NewChannelReq // set new channel
|
||||
//#define DISABLE_MCMD_DlChannelReq // set downlink channel for RX1 for given uplink channel.
|
||||
//#define DISABLE_MCMD_RXTimingSetupReq // delay between TX and RX
|
||||
// Class B
|
||||
//#define DISABLE_MCMD_PING_SET // set ping freq, automatically disabled by DISABLE_PING
|
||||
//#define DISABLE_MCMD_BCNI_ANS // next beacon start, automatically disabled by DISABLE_BEACON
|
||||
//#define DISABLE_MCMD_PingSlotChannelReq // set ping freq, automatically disabled by DISABLE_PING
|
||||
//#define ENABLE_MCMD_BeaconTimingAns // next beacon start, DEPRECATED, normally disabled by DISABLE_BEACON
|
||||
|
||||
// In LoRaWAN, a gateway applies I/Q inversion on TX, and nodes do the
|
||||
// same on RX. This ensures that gateways can talk to nodes and vice
|
||||
// versa, but gateways will not hear other gateways and nodes will not
|
||||
// hear other nodes. By defining this macro in lmic_project_config.h,
|
||||
// this inversion is disabled and this node can hear other nodes. If
|
||||
// two nodes both have this macro set, they can talk to each other
|
||||
// (but they can no longer hear gateways). This should probably only
|
||||
// be used when debugging and/or when talking to the radio directly
|
||||
// (e.g. like in the "raw" example).
|
||||
// DEPRECATED(tmm@mcci.com); replaced by LMIC.noRXIQinversion (dynamic). Don't define this.
|
||||
//#define DISABLE_INVERT_IQ_ON_RX
|
||||
|
||||
// This allows choosing between multiple included AES implementations.
|
||||
@ -175,7 +169,55 @@
|
||||
// enable support for MCMD_DeviceTimeReq and MCMD_DeviceTimeAns
|
||||
// this is always defined, and non-zero to enable it.
|
||||
#if !defined(LMIC_ENABLE_DeviceTimeReq)
|
||||
# define LMIC_ENABLE_DeviceTimeReq 0
|
||||
# define LMIC_ENABLE_DeviceTimeReq 1
|
||||
#endif
|
||||
|
||||
// LMIC_ENABLE_user_events
|
||||
// Enable/disable support for programmable callbacks for events, rx, and tx.
|
||||
// This is always defined, and non-zero to enable. Default is enabled.
|
||||
#if !defined(LMIC_ENABLE_user_events)
|
||||
# define LMIC_ENABLE_user_events 1
|
||||
#endif
|
||||
|
||||
// LMIC_ENABLE_onEvent
|
||||
// Enable/disable support for out-call to user-supplied `onEvent()` function.
|
||||
// This is always defined, and non-zero to enable. Default is enabled.
|
||||
#if !defined(LMIC_ENABLE_onEvent)
|
||||
# define LMIC_ENABLE_onEvent 1
|
||||
#endif
|
||||
|
||||
// LMIC_ENABLE_long_messages
|
||||
// LMIC certification requires full-length 255 frames, but to save RAM,
|
||||
// a shorter maximum can be set. This controls both RX and TX buffers,
|
||||
// so reducing this by 1 saves 2 bytes of RAM.
|
||||
#if defined(LMIC_ENABLE_long_messages) && defined(LMIC_MAX_FRAME_LENGTH)
|
||||
#error "Use only one of LMIC_ENABLE_long_messages or LMIC_MAX_FRAME_LENGTH"
|
||||
#elif defined(LMIC_ENABLE_long_messages) && LMIC_ENABLE_long_messages == 0
|
||||
# define LMIC_MAX_FRAME_LENGTH 64
|
||||
#elif !defined(LMIC_MAX_FRAME_LENGTH)
|
||||
# define LMIC_MAX_FRAME_LENGTH 255
|
||||
#elif LMIC_MAX_FRAME_LENGTH > 255
|
||||
#error "LMIC_MAX_FRAME_LENGTH cannot be larger than 255"
|
||||
#endif
|
||||
|
||||
// LMIC_ENABLE_event_logging
|
||||
// LMIC debugging for certification tests requires this, because debug prints affect
|
||||
// timing too dramatically. But normal operation doesn't need this.
|
||||
#if !defined(LMIC_ENABLE_event_logging)
|
||||
# define LMIC_ENABLE_event_logging 0 /* PARAM */
|
||||
#endif
|
||||
|
||||
// LMIC_LORAWAN_SPEC_VERSION
|
||||
#if !defined(LMIC_LORAWAN_SPEC_VERSION)
|
||||
# define LMIC_LORAWAN_SPEC_VERSION LMIC_LORAWAN_SPEC_VERSION_1_0_3
|
||||
#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_
|
||||
|
56
src/lmic/hal.h
Executable file → Normal file
56
src/lmic/hal.h
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2016, 2018 MCCI Corporation.
|
||||
* Copyright (c) 2016, 2018-2019 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -33,10 +33,17 @@
|
||||
# include "oslmic_types.h"
|
||||
#endif
|
||||
|
||||
#ifndef _lmic_env_h_
|
||||
# include "lmic_env.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"{
|
||||
#endif
|
||||
|
||||
// The type of an optional user-defined failure handler routine
|
||||
typedef void LMIC_ABI_STD hal_failure_handler_t(const char* const file, const uint16_t line);
|
||||
|
||||
/*
|
||||
* initialize hardware (IO, SPI, TIMER, IRQ).
|
||||
* This API is deprecated as it uses the const global lmic_pins,
|
||||
@ -87,6 +94,11 @@ void hal_disableIRQs (void);
|
||||
*/
|
||||
void hal_enableIRQs (void);
|
||||
|
||||
/*
|
||||
* return CPU interrupt nesting count
|
||||
*/
|
||||
uint8_t hal_getIrqLevel (void);
|
||||
|
||||
/*
|
||||
* put system and CPU in low-power mode, sleep until interrupt.
|
||||
*/
|
||||
@ -98,9 +110,10 @@ void hal_sleep (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.
|
||||
@ -116,6 +129,13 @@ u1_t hal_checkTimer (u4_t targettime);
|
||||
*/
|
||||
void hal_failed (const char *file, u2_t line);
|
||||
|
||||
/*
|
||||
* set a custom hal failure handler routine. The default behaviour, defined in
|
||||
* hal_failed(), is to halt by looping infintely.
|
||||
*/
|
||||
void hal_set_failure_handler(hal_failure_handler_t* const);
|
||||
// ttn-esp32: too much const
|
||||
|
||||
/*
|
||||
* get the calibration value for radio_rssi
|
||||
*/
|
||||
@ -129,8 +149,38 @@ s1_t hal_getRssiCal (void);
|
||||
*/
|
||||
ostime_t hal_setModuleActive (bit_t val);
|
||||
|
||||
/* find out if we're using Tcxo */
|
||||
bit_t hal_queryUsingTcxo(void);
|
||||
|
||||
/* represent the various radio TX power policy */
|
||||
enum {
|
||||
LMICHAL_radio_tx_power_policy_rfo = 0,
|
||||
LMICHAL_radio_tx_power_policy_paboost = 1,
|
||||
LMICHAL_radio_tx_power_policy_20dBm = 2,
|
||||
};
|
||||
|
||||
/*
|
||||
* query the configuration as to the Tx Power Policy
|
||||
* to be used on this board, given our desires and
|
||||
* requested power.
|
||||
*/
|
||||
uint8_t hal_getTxPowerPolicy(
|
||||
u1_t inputPolicy,
|
||||
s1_t requestedPower,
|
||||
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
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
1858
src/lmic/lmic.c
Executable file → Normal file
1858
src/lmic/lmic.c
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
512
src/lmic/lmic.h
Executable file → Normal file
512
src/lmic/lmic.h
Executable file → Normal file
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2016 Matthijs Kooijman.
|
||||
* Copyright (c) 2016-2019 MCCI Corporation.
|
||||
* Copyright (c) 2016-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -96,40 +96,78 @@
|
||||
extern "C"{
|
||||
#endif
|
||||
|
||||
// LMIC version -- this is ths IBM LMIC version
|
||||
// LMIC version -- this is the IBM LMIC version
|
||||
#define LMIC_VERSION_MAJOR 1
|
||||
#define LMIC_VERSION_MINOR 6
|
||||
#define LMIC_VERSION_BUILD 1468577746
|
||||
|
||||
// Arduino LMIC version
|
||||
#define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \
|
||||
(((major) << 24u) | ((minor) << 16u) | ((patch) << 8u) | (local))
|
||||
((((major)*UINT32_C(1)) << 24) | (((minor)*UINT32_C(1)) << 16) | (((patch)*UINT32_C(1)) << 8) | (((local)*UINT32_C(1)) << 0))
|
||||
|
||||
#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(2, 3, 2, 0) /* v2.3.2 */
|
||||
#define ARDUINO_LMIC_VERSION \
|
||||
ARDUINO_LMIC_VERSION_CALC(4, 2, 0, 1) /* 4.2.0-1 */
|
||||
|
||||
#define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \
|
||||
(((v) >> 24u) & 0xFFu)
|
||||
((((v)*UINT32_C(1)) >> 24u) & 0xFFu)
|
||||
|
||||
#define ARDUINO_LMIC_VERSION_GET_MINOR(v) \
|
||||
(((v) >> 16u) & 0xFFu)
|
||||
((((v)*UINT32_C(1)) >> 16u) & 0xFFu)
|
||||
|
||||
#define ARDUINO_LMIC_VERSION_GET_PATCH(v) \
|
||||
(((v) >> 8u) & 0xFFu)
|
||||
((((v)*UINT32_C(1)) >> 8u) & 0xFFu)
|
||||
|
||||
#define ARDUINO_LMIC_VERSION_GET_LOCAL(v) \
|
||||
((v) & 0xFFu)
|
||||
|
||||
/// \brief convert a semantic version to an ordinal integer.
|
||||
#define ARDUINO_LMIC_VERSION_TO_ORDINAL(v) \
|
||||
(((v) & 0xFFFFFF00u) | (((v) - 1) & 0xFFu))
|
||||
|
||||
/// \brief compare two semantic versions
|
||||
/// \return \c true if \p a is less than \p b (as a semantic version).
|
||||
#define ARDUINO_LMIC_VERSION_COMPARE_LT(a, b) \
|
||||
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) < ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
|
||||
|
||||
/// \brief compare two semantic versions
|
||||
/// \return \c true if \p a is less than or equal to \p b (as a semantic version).
|
||||
#define ARDUINO_LMIC_VERSION_COMPARE_LE(a, b) \
|
||||
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) <= ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
|
||||
|
||||
/// \brief compare two semantic versions
|
||||
/// \return \c true if \p a is greater than \p b (as a semantic version).
|
||||
#define ARDUINO_LMIC_VERSION_COMPARE_GT(a, b) \
|
||||
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) > ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
|
||||
|
||||
/// \brief compare two semantic versions
|
||||
/// \return \c true if \p a is greater than or equal to \p b (as a semantic version).
|
||||
#define ARDUINO_LMIC_VERSION_COMPARE_GE(a, b) \
|
||||
(ARDUINO_LMIC_VERSION_TO_ORDINAL(a) >= ARDUINO_LMIC_VERSION_TO_ORDINAL(b))
|
||||
|
||||
|
||||
//! Only For Antenna Tuning Tests !
|
||||
//#define CFG_TxContinuousMode 1
|
||||
|
||||
enum { MAX_FRAME_LEN = 64 }; //!< Library cap on max frame length
|
||||
enum { TXCONF_ATTEMPTS = 8 }; //!< Transmit attempts for confirmed frames
|
||||
enum { MAX_MISSED_BCNS = 20 }; // threshold for triggering rejoin requests
|
||||
enum { MAX_RXSYMS = 100 }; // stop tracking beacon beyond this
|
||||
// since this was announced as the API variable, we keep it. But it's not used,
|
||||
// MAX_LEN_FRAME is what the code uses.
|
||||
enum { MAX_FRAME_LEN = MAX_LEN_FRAME }; //!< Library cap on max frame length
|
||||
|
||||
enum { LINK_CHECK_CONT = 12 , // continue with this after reported dead link
|
||||
LINK_CHECK_DEAD = 24 , // after this UP frames and no response from NWK assume link is dead
|
||||
LINK_CHECK_INIT = -12 , // UP frame count until we inc datarate
|
||||
enum { TXCONF_ATTEMPTS = 8 }; //!< Transmit attempts for confirmed frames
|
||||
enum { MAX_MISSED_BCNS = (2 * 60 * 60 + 127) / 128 }; //!< threshold for dropping out of class B, triggering rejoin requests
|
||||
// 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
|
||||
LINK_CHECK_DEAD = 32 , // after this UP frames and no response to ack from NWK assume link is dead (ADR_ACK_DELAY)
|
||||
LINK_CHECK_UNJOIN_MIN = LINK_CHECK_DEAD + 4, // this is the minimum value of LINK_CHECK_UNJOIN if we parameterize
|
||||
LINK_CHECK_UNJOIN = LINK_CHECK_DEAD + (3 * 240), // after this many UP frames and no response, switch to join (by default)
|
||||
LINK_CHECK_INIT = -64 , // UP frame count until we ask for ack (ADR_ACK_LIMIT)
|
||||
LINK_CHECK_OFF =-128 }; // link check disabled
|
||||
|
||||
enum { TIME_RESYNC = 6*128 }; // secs
|
||||
@ -149,28 +187,39 @@ struct band_t {
|
||||
u2_t txcap; // duty cycle limitation: 1/txcap
|
||||
s1_t txpow; // maximum TX power
|
||||
u1_t lastchnl; // last used channel
|
||||
ostime_t avail; // channel is blocked until this time
|
||||
ostime_t avail; // band is blocked until this time
|
||||
};
|
||||
TYPEDEF_xref2band_t; //!< \internal
|
||||
|
||||
struct lmic_saved_adr_state_s {
|
||||
u4_t channelFreq[MAX_CHANNELS];
|
||||
u2_t channelMap;
|
||||
};
|
||||
|
||||
#elif CFG_LMIC_US_like // US915 spectrum =================================================
|
||||
|
||||
enum { MAX_XCHANNELS = 2 }; // extra channels in RAM, channels 0-71 are immutable
|
||||
struct lmic_saved_adr_state_s {
|
||||
u2_t channelMap[(72+15)/16]; // enabled bits
|
||||
u2_t activeChannels125khz;
|
||||
u2_t activeChannels500khz;
|
||||
};
|
||||
|
||||
#endif // ==========================================================================
|
||||
|
||||
typedef struct lmic_saved_adr_state_s lmic_saved_adr_state_t;
|
||||
|
||||
// Keep in sync with evdefs.hpp::drChange
|
||||
enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD };
|
||||
enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD, DRCHG_FRAMESIZE };
|
||||
enum { KEEP_TXPOW = -128 };
|
||||
|
||||
|
||||
#if !defined(DISABLE_PING)
|
||||
//! \internal
|
||||
struct rxsched_t {
|
||||
u1_t dr;
|
||||
dr_t dr;
|
||||
u1_t intvExp; // 0..7
|
||||
u1_t slot; // runs from 0 to 128
|
||||
u1_t rxsyms;
|
||||
rxsyms_t rxsyms;
|
||||
ostime_t rxbase;
|
||||
ostime_t rxtime; // start of next spot
|
||||
u4_t freq;
|
||||
@ -189,19 +238,19 @@ enum { BCN_NONE = 0x00, //!< No beacon received
|
||||
//! Information about the last and previous beacons.
|
||||
struct bcninfo_t {
|
||||
ostime_t txtime; //!< Time when the beacon was sent
|
||||
u4_t time; //!< GPS time in seconds of last beacon (received or surrogate)
|
||||
s4_t lat; //!< Lat field of last beacon (valid only if BCN_FULL set)
|
||||
s4_t lon; //!< Lon field of last beacon (valid only if BCN_FULL set)
|
||||
s1_t rssi; //!< Adjusted RSSI value of last received beacon
|
||||
s1_t snr; //!< Scaled SNR value of last received beacon
|
||||
u1_t flags; //!< Last beacon reception and tracking states. See BCN_* values.
|
||||
u4_t time; //!< GPS time in seconds of last beacon (received or surrogate)
|
||||
//
|
||||
u1_t info; //!< Info field of last beacon (valid only if BCN_FULL set)
|
||||
s4_t lat; //!< Lat field of last beacon (valid only if BCN_FULL set)
|
||||
s4_t lon; //!< Lon field of last beacon (valid only if BCN_FULL set)
|
||||
};
|
||||
#endif // !DISABLE_BEACONS
|
||||
|
||||
// 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
|
||||
enum { NETID_NONE=(int)~0U, NETID_MASK=(int)0xFFFFFF };
|
||||
// MAC operation modes (lmic_t.opmode).
|
||||
@ -220,29 +269,115 @@ enum { OP_NONE = 0x0000,
|
||||
OP_NEXTCHNL = 0x0800, // find a new channel
|
||||
OP_LINKDEAD = 0x1000, // link was reported as dead
|
||||
OP_TESTMODE = 0x2000, // developer test mode
|
||||
OP_UNJOIN = 0x4000, // unjoin and rejoin on next engineUpdate().
|
||||
};
|
||||
// TX-RX transaction flags - report back to user
|
||||
enum { TXRX_ACK = 0x80, // confirmed UP frame was acked
|
||||
TXRX_NACK = 0x40, // confirmed UP frame was not acked
|
||||
TXRX_NOPORT = 0x20, // set if a frame with a port was RXed, clr if no frame/no port
|
||||
TXRX_PORT = 0x10, // set if a frame with a port was RXed, LMIC.frame[LMIC.dataBeg-1] => port
|
||||
TXRX_DNW1 = 0x01, // received in 1st DN slot
|
||||
TXRX_LENERR = 0x08, // set if frame was discarded due to length error.
|
||||
TXRX_PING = 0x04, // received in a scheduled RX slot
|
||||
TXRX_DNW2 = 0x02, // received in 2dn DN slot
|
||||
TXRX_PING = 0x04 }; // received in a scheduled RX slot
|
||||
TXRX_DNW1 = 0x01, // received in 1st DN slot
|
||||
};
|
||||
|
||||
// Event types for event callback
|
||||
enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND,
|
||||
EV_BEACON_MISSED, EV_BEACON_TRACKED, EV_JOINING,
|
||||
EV_JOINED, EV_RFU1, EV_JOIN_FAILED, EV_REJOIN_FAILED,
|
||||
EV_TXCOMPLETE, EV_LOST_TSYNC, EV_RESET,
|
||||
EV_RXCOMPLETE, EV_LINK_DEAD, EV_LINK_ALIVE, EV_SCAN_FOUND,
|
||||
EV_TXSTART };
|
||||
EV_TXSTART, EV_TXCANCELED, EV_RXSTART, EV_JOIN_TXCOMPLETE };
|
||||
typedef enum _ev_t ev_t;
|
||||
|
||||
// this macro can be used to initalize a normal table of event strings
|
||||
#define LMIC_EVENT_NAME_TABLE__INIT \
|
||||
"<<zero>>", \
|
||||
"EV_SCAN_TIMEOUT", "EV_BEACON_FOUND", \
|
||||
"EV_BEACON_MISSED", "EV_BEACON_TRACKED", "EV_JOINING", \
|
||||
"EV_JOINED", "EV_RFU1", "EV_JOIN_FAILED", "EV_REJOIN_FAILED", \
|
||||
"EV_TXCOMPLETE", "EV_LOST_TSYNC", "EV_RESET", \
|
||||
"EV_RXCOMPLETE", "EV_LINK_DEAD", "EV_LINK_ALIVE", "EV_SCAN_FOUND", \
|
||||
"EV_TXSTART", "EV_TXCANCELED", "EV_RXSTART", "EV_JOIN_TXCOMPLETE"
|
||||
|
||||
// if working on an AVR (or worried about it), you can use this multi-zero
|
||||
// string and put this in a single const F() string. Index through this
|
||||
// counting up from 0, until you get to the entry you want or to an
|
||||
// entry that begins with a \0.
|
||||
#define LMIC_EVENT_NAME_MULTISZ__INIT \
|
||||
"<<zero>>\0" \
|
||||
"EV_SCAN_TIMEOUT\0" "EV_BEACON_FOUND\0" \
|
||||
"EV_BEACON_MISSED\0" "EV_BEACON_TRACKED\0" "EV_JOINING\0" \
|
||||
"EV_JOINED\0" "EV_RFU1\0" "EV_JOIN_FAILED\0" "EV_REJOIN_FAILED\0" \
|
||||
"EV_TXCOMPLETE\0" "EV_LOST_TSYNC\0" "EV_RESET\0" \
|
||||
"EV_RXCOMPLETE\0" "EV_LINK_DEAD\0" "EV_LINK_ALIVE\0" "EV_SCAN_FOUND\0" \
|
||||
"EV_TXSTART\0" "EV_TXCANCELED\0" "EV_RXSTART\0" "EV_JOIN_TXCOMPLETE\0"
|
||||
|
||||
enum {
|
||||
LMIC_ERROR_SUCCESS = 0,
|
||||
LMIC_ERROR_TX_BUSY = -1,
|
||||
LMIC_ERROR_TX_TOO_LARGE = -2,
|
||||
LMIC_ERROR_TX_NOT_FEASIBLE = -3,
|
||||
LMIC_ERROR_TX_FAILED = -4,
|
||||
};
|
||||
|
||||
typedef int lmic_tx_error_t;
|
||||
|
||||
#define LMIC_ERROR_NAME__INIT \
|
||||
"LMIC_ERROR_SUCCESS", \
|
||||
"LMIC_ERROR_TX_BUSY", \
|
||||
"LMIC_ERROR_TX_TOO_LARGE", \
|
||||
"LMIC_ERROR_TX_NOT_FEASIBLE", \
|
||||
"LMIC_ERROR_TX_FAILED"
|
||||
|
||||
#define LMIC_ERROR_NAME_MULTISZ__INIT \
|
||||
"LMIC_ERROR_SUCCESS\0" \
|
||||
"LMIC_ERROR_TX_BUSY\0" \
|
||||
"LMIC_ERROR_TX_TOO_LARGE\0" \
|
||||
"LMIC_ERROR_TX_NOT_FEASIBLE\0" \
|
||||
"LMIC_ERROR_TX_FAILED"
|
||||
|
||||
enum {
|
||||
LMIC_BEACON_ERROR_INVALID = -2,
|
||||
LMIC_BEACON_ERROR_WRONG_NETWORK = -1,
|
||||
LMIC_BEACON_ERROR_SUCCESS_PARTIAL = 0,
|
||||
LMIC_BEACON_ERROR_SUCCESS_FULL = 1,
|
||||
};
|
||||
|
||||
typedef s1_t lmic_beacon_error_t;
|
||||
|
||||
static inline bit_t LMIC_BEACON_SUCCESSFUL(lmic_beacon_error_t e) {
|
||||
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 {
|
||||
// This value represents 100% error in LMIC.clockError
|
||||
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.
|
||||
// types and functions are always defined, to reduce #ifs in example code and libraries.
|
||||
typedef void LMIC_ABI_STD lmic_rxmessage_cb_t(void *pUserData, uint8_t port, const uint8_t *pMessage, size_t nMessage);
|
||||
typedef void LMIC_ABI_STD lmic_txmessage_cb_t(void *pUserData, int fSuccess);
|
||||
typedef void LMIC_ABI_STD lmic_event_cb_t(void *pUserData, ev_t e);
|
||||
|
||||
// network time request callback function
|
||||
// defined unconditionally, because APIs and types can't change based on config.
|
||||
// This is called when a time-request succeeds or when we get a downlink
|
||||
@ -272,98 +407,229 @@ enum lmic_request_time_state_e {
|
||||
|
||||
typedef u1_t lmic_request_time_state_t;
|
||||
|
||||
enum lmic_engine_update_state_e {
|
||||
lmic_EngineUpdateState_idle = 0, // engineUpdate is idle.
|
||||
lmic_EngineUpdateState_busy = 1, // engineUpdate is busy, but has not been reentered.
|
||||
lmic_EngineUpdateState_again = 2, // engineUpdate is busy, and has to be evaluated again.
|
||||
};
|
||||
|
||||
typedef u1_t lmic_engine_update_state_t;
|
||||
|
||||
/*
|
||||
|
||||
Structure: lmic_client_data_t
|
||||
|
||||
Function:
|
||||
Holds LMIC client data that must live through LMIC_reset().
|
||||
|
||||
Description:
|
||||
There are a variety of client registration linkage items that
|
||||
must live through LMIC_reset(), because LMIC_reset() is called
|
||||
at frame rollover time. We group them together into a structure
|
||||
to make copies easy.
|
||||
|
||||
*/
|
||||
|
||||
//! abstract type for collection of client data that survives LMIC_reset().
|
||||
typedef struct lmic_client_data_s lmic_client_data_t;
|
||||
|
||||
//! contents of lmic_client_data_t
|
||||
struct lmic_client_data_s {
|
||||
|
||||
/* pointer-width things come first */
|
||||
#if LMIC_ENABLE_DeviceTimeReq
|
||||
lmic_request_network_time_cb_t *pNetworkTimeCb; //! call-back routine for network time
|
||||
void *pNetworkTimeUserData; //! call-back data for network time.
|
||||
#endif
|
||||
|
||||
#if LMIC_ENABLE_user_events
|
||||
lmic_event_cb_t *eventCb; //! user-supplied callback function for events.
|
||||
void *eventUserData; //! data for eventCb
|
||||
lmic_rxmessage_cb_t *rxMessageCb; //! user-supplied message-received callback
|
||||
void *rxMessageUserData; //! data for rxMessageCb
|
||||
lmic_txmessage_cb_t *txMessageCb; //! transmit-complete message handler; reset on each tx complete.
|
||||
void *txMessageUserData; //! data for txMessageCb.
|
||||
#endif // LMIC_ENABLE_user_events
|
||||
|
||||
/* next we have things that are (u)int32_t */
|
||||
/* none at the moment */
|
||||
|
||||
/* next we have things that are (u)int16_t */
|
||||
|
||||
u2_t clockError; //! Inaccuracy in the clock. CLOCK_ERROR_MAX represents +/-100% error
|
||||
|
||||
/* finally, things that are (u)int8_t */
|
||||
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;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Structure: lmic_t
|
||||
|
||||
Function:
|
||||
Provides the instance data for the LMIC.
|
||||
|
||||
*/
|
||||
|
||||
struct lmic_t {
|
||||
// client setup data, survives LMIC_reset().
|
||||
lmic_client_data_t client;
|
||||
|
||||
// the OS job object. pointer alignment.
|
||||
osjob_t osjob;
|
||||
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
bcninfo_t bcninfo; // Last received beacon info
|
||||
#endif
|
||||
|
||||
#if !defined(DISABLE_PING)
|
||||
rxsched_t ping; // pingable setup
|
||||
#endif
|
||||
|
||||
// the radio driver portable context
|
||||
lmic_radio_data_t radio;
|
||||
|
||||
/* (u)int32_t things */
|
||||
|
||||
// Radio settings TX/RX (also accessed by HAL)
|
||||
ostime_t txend;
|
||||
ostime_t rxtime;
|
||||
|
||||
// LBT info
|
||||
ostime_t lbt_ticks; // ticks to listen
|
||||
s1_t lbt_dbmax; // max permissible dB on our channel (eg -80)
|
||||
|
||||
u4_t freq;
|
||||
s1_t rssi;
|
||||
s1_t snr; // LMIC.snr is SNR times 4
|
||||
rps_t rps;
|
||||
u1_t rxsyms;
|
||||
u1_t dndr;
|
||||
s1_t txpow; // dBm
|
||||
|
||||
osjob_t osjob;
|
||||
|
||||
// Channel scheduling
|
||||
#if CFG_LMIC_EU_like
|
||||
band_t bands[MAX_BANDS];
|
||||
u4_t channelFreq[MAX_CHANNELS];
|
||||
u2_t channelDrMap[MAX_CHANNELS];
|
||||
u2_t channelMap;
|
||||
#elif CFG_LMIC_US_like
|
||||
u4_t xchFreq[MAX_XCHANNELS]; // extra channel frequencies (if device is behind a repeater)
|
||||
u2_t xchDrMap[MAX_XCHANNELS]; // extra channel datarate ranges ---XXX: ditto
|
||||
u2_t channelMap[(72+MAX_XCHANNELS+15)/16]; // enabled bits
|
||||
u2_t activeChannels125khz;
|
||||
u2_t activeChannels500khz;
|
||||
#endif
|
||||
u1_t txChnl; // channel for next TX
|
||||
u1_t globalDutyRate; // max rate: 1/2^k
|
||||
ostime_t globalDutyAvail; // time device can send again
|
||||
|
||||
u4_t netid; // current network id (~0 - none)
|
||||
u2_t opmode;
|
||||
u1_t upRepeat; // configured up repeat
|
||||
s1_t adrTxPow; // ADR adjusted TX power
|
||||
u1_t datarate; // current data rate
|
||||
u1_t errcr; // error coding rate (used for TX only)
|
||||
u1_t rejoinCnt; // adjustment for rejoin datarate
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
s2_t drift; // last measured drift
|
||||
s2_t lastDriftDiff;
|
||||
s2_t maxDriftDiff;
|
||||
#endif
|
||||
|
||||
u2_t clockError; // Inaccuracy in the clock. CLOCK_ERROR_MAX
|
||||
// represents +/-100% error
|
||||
|
||||
u1_t pendTxPort;
|
||||
u1_t pendTxConf; // confirmed data
|
||||
u1_t pendTxLen; // +0x80 = confirmed
|
||||
u1_t pendTxData[MAX_LEN_PAYLOAD];
|
||||
|
||||
u2_t devNonce; // last generated nonce
|
||||
u1_t nwkKey[16]; // network session key
|
||||
u1_t artKey[16]; // application router session key
|
||||
devaddr_t devaddr;
|
||||
u4_t seqnoDn; // device level down stream seqno
|
||||
u4_t seqnoUp;
|
||||
u4_t dn2Freq;
|
||||
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
ostime_t bcnRxtime;
|
||||
#endif
|
||||
|
||||
#if LMIC_ENABLE_DeviceTimeReq
|
||||
// put here for alignment, to reduce RAM use.
|
||||
ostime_t localDeviceTime; // the LMIC.txend value for last DeviceTimeAns
|
||||
lmic_gpstime_t netDeviceTime; // the netDeviceTime for lastDeviceTimeAns
|
||||
// zero ==> not valid.
|
||||
lmic_request_network_time_cb_t *pNetworkTimeCb; // call-back routine
|
||||
void *pNetworkTimeUserData; // call-back data
|
||||
#endif // LMIC_ENABLE_DeviceTimeReq
|
||||
|
||||
// Channel scheduling -- very much private
|
||||
#if CFG_LMIC_EU_like
|
||||
band_t bands[MAX_BANDS];
|
||||
u4_t channelFreq[MAX_CHANNELS];
|
||||
#if !defined(DISABLE_MCMD_DlChannelReq)
|
||||
u4_t channelDlFreq[MAX_CHANNELS];
|
||||
#endif
|
||||
// bit map of enabled datarates for each channel
|
||||
u2_t channelDrMap[MAX_CHANNELS];
|
||||
u2_t channelMap;
|
||||
u2_t channelShuffleMap;
|
||||
#elif CFG_LMIC_US_like
|
||||
u2_t channelMap[(72+15)/16]; // enabled bits
|
||||
u2_t channelShuffleMap[(72+15)/16]; // enabled bits
|
||||
u2_t activeChannels125khz;
|
||||
u2_t activeChannels500khz;
|
||||
#endif
|
||||
|
||||
/* (u)int16_t things */
|
||||
rps_t rps; // radio parameter selections: SF, BW, CodingRate, NoCrc, implicit hdr
|
||||
u2_t opmode; // engineUpdate() operating mode flags
|
||||
u2_t devNonce; // last generated nonce
|
||||
|
||||
s2_t adrAckReq; // counter for link integrity tracking (LINK_CHECK_OFF=off)
|
||||
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
s2_t drift; // last measured drift
|
||||
s2_t lastDriftDiff;
|
||||
s2_t maxDriftDiff;
|
||||
rxsyms_t bcnRxsyms; //
|
||||
#endif
|
||||
|
||||
/* (u)int8_t things */
|
||||
lmic_engine_update_state_t engineUpdateState; // state of the engineUpdate() evaluator.
|
||||
s1_t rssi;
|
||||
s1_t snr; // LMIC.snr is SNR times 4
|
||||
rxsyms_t rxsyms; // symbols for receive timeout.
|
||||
u1_t dndr;
|
||||
s1_t txpow; // transmit dBm (administrative)
|
||||
s1_t radio_txpow; // the radio driver's copy of txpow, in dB limited by adrTxPow, and
|
||||
// also adjusted for EIRP/antenna gain considerations.
|
||||
// This is just the radio's idea of power. So if you are
|
||||
// controlling EIRP, and you have 3 dB antenna gain, this
|
||||
// needs to reduced by 3 dB.
|
||||
s1_t lbt_dbmax; // max permissible dB on our channel (eg -80)
|
||||
|
||||
u1_t txChnl; // channel for next TX
|
||||
u1_t globalDutyRate; // max rate: 1/2^k
|
||||
#if CFG_LMIC_US_like
|
||||
u1_t txChnl_125kHz; ///< during joins on 500 kHz, the 125 kHz channel
|
||||
/// that was last used.
|
||||
#endif
|
||||
u1_t upRepeat; // configured up repeat
|
||||
s1_t adrTxPow; // ADR adjusted TX power
|
||||
u1_t datarate; // current data rate
|
||||
u1_t errcr; // error coding rate (used for TX only)
|
||||
u1_t rejoinCnt; // adjustment for rejoin datarate
|
||||
|
||||
u1_t upRepeatCount; // current up-repeat
|
||||
bit_t initBandplanAfterReset; // cleared by LMIC_reset(), set by first join. See issue #244
|
||||
|
||||
u1_t pendTxPort;
|
||||
u1_t pendTxConf; // confirmed data
|
||||
u1_t pendTxLen; // count of bytes in pendTxData.
|
||||
u1_t pendTxData[MAX_LEN_PAYLOAD];
|
||||
|
||||
u1_t pendMacLen; // number of bytes of pending Mac response data
|
||||
bit_t pendMacPiggyback; // received on port 0 or piggyback?
|
||||
// response data if piggybacked
|
||||
u1_t pendMacData[LWAN_FCtrl_FOptsLen_MAX];
|
||||
|
||||
u1_t nwkKey[16]; // network session key
|
||||
u1_t artKey[16]; // application router session key
|
||||
|
||||
u1_t dnConf; // dn frame confirm pending: LORA::FCT_ACK or 0
|
||||
s1_t adrAckReq; // counter until we reset data rate (0=off)
|
||||
u1_t lastDnConf; // downlink with seqnoDn-1 requested confirmation
|
||||
u1_t adrChanged;
|
||||
|
||||
u1_t rxDelay; // Rx delay after TX
|
||||
|
||||
u1_t margin;
|
||||
bit_t ladrAns; // link adr adapt answer pending
|
||||
bit_t devsAns; // device status answer pending
|
||||
s1_t devAnsMargin; // SNR value between -32 and 31 (inclusive) for the last successfully received DevStatusReq command
|
||||
u1_t adrEnabled;
|
||||
u1_t moreData; // NWK has more data pending
|
||||
#if !defined(DISABLE_MCMD_DCAP_REQ)
|
||||
bit_t dutyCapAns; // have to ACK duty cycle settings
|
||||
#endif
|
||||
#if !defined(DISABLE_MCMD_SNCH_REQ)
|
||||
u1_t snchAns; // answer set new channel
|
||||
#endif
|
||||
#if LMIC_ENABLE_TxParamSetupReq
|
||||
bit_t txParamSetupAns; // transmit setup answer pending.
|
||||
u1_t txParam; // the saved TX param byte.
|
||||
#endif
|
||||
#if LMIC_ENABLE_DeviceTimeReq
|
||||
@ -376,23 +642,21 @@ struct lmic_t {
|
||||
|
||||
// 2nd RX window (after up stream)
|
||||
u1_t dn2Dr;
|
||||
u4_t dn2Freq;
|
||||
#if !defined(DISABLE_MCMD_DN2P_SET)
|
||||
#if !defined(DISABLE_MCMD_RXParamSetupReq)
|
||||
u1_t dn2Ans; // 0=no answer pend, 0x80+ACKs
|
||||
#endif
|
||||
#if !defined(DISABLE_MCMD_DlChannelReq)
|
||||
u1_t macDlChannelAns; // 0 ==> no answer pending, 0x80+ACK bits
|
||||
#endif
|
||||
#if !defined(DISABLE_MCMD_RXTimingSetupReq)
|
||||
bit_t macRxTimingSetupAns; // 0 ==> no answer pend, non-zero inserts response.
|
||||
#endif
|
||||
|
||||
// Class B state
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
u1_t missedBcns; // unable to track last N beacons
|
||||
u1_t bcninfoTries; // how often to try (scan mode only)
|
||||
#endif
|
||||
#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING)
|
||||
u1_t pingSetAns; // answer set cmd and ACK bits
|
||||
#endif
|
||||
#if !defined(DISABLE_PING)
|
||||
rxsched_t ping; // pingable setup
|
||||
#endif
|
||||
|
||||
// Public part of MAC state
|
||||
u1_t txCnt;
|
||||
u1_t txrxFlags; // transaction flags (TX-RX combo)
|
||||
@ -402,12 +666,10 @@ struct lmic_t {
|
||||
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
u1_t bcnChnl;
|
||||
u1_t bcnRxsyms; //
|
||||
ostime_t bcnRxtime;
|
||||
bcninfo_t bcninfo; // Last received beacon info
|
||||
#endif
|
||||
|
||||
u1_t noRXIQinversion;
|
||||
u1_t saveIrqFlags; // last LoRa IRQ flags
|
||||
};
|
||||
|
||||
//! \var struct lmic_t LMIC
|
||||
@ -418,16 +680,26 @@ DECLARE_LMIC; //!< \internal
|
||||
#define DR_RANGE_MAP(drlo,drhi) (((u2_t)0xFFFF<<(drlo)) & ((u2_t)0xFFFF>>(15-(drhi))))
|
||||
bit_t LMIC_setupBand (u1_t bandidx, s1_t txpow, u2_t txcap);
|
||||
bit_t LMIC_setupChannel (u1_t channel, u4_t freq, u2_t drmap, s1_t band);
|
||||
void LMIC_disableChannel (u1_t channel);
|
||||
void LMIC_enableSubBand(u1_t band);
|
||||
void LMIC_enableChannel(u1_t channel);
|
||||
void LMIC_disableSubBand(u1_t band);
|
||||
void LMIC_selectSubBand(u1_t band);
|
||||
bit_t LMIC_disableChannel (u1_t channel);
|
||||
bit_t LMIC_enableSubBand(u1_t band);
|
||||
bit_t LMIC_enableChannel(u1_t channel);
|
||||
bit_t LMIC_disableSubBand(u1_t band);
|
||||
bit_t LMIC_selectSubBand(u1_t band);
|
||||
|
||||
//! \brief get the number of (fixed) default channels before the programmable channels.
|
||||
u1_t LMIC_queryNumDefaultChannels(void);
|
||||
|
||||
//! \brief check whether the LMIC is ready for a transmit packet
|
||||
bit_t LMIC_queryTxReady(void);
|
||||
|
||||
void LMIC_setDrTxpow (dr_t dr, s1_t txpow); // set default/start DR/txpow
|
||||
void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off)
|
||||
|
||||
#if !defined(DISABLE_JOIN)
|
||||
bit_t LMIC_startJoining (void);
|
||||
void LMIC_tryRejoin (void);
|
||||
void LMIC_unjoin (void);
|
||||
void LMIC_unjoinAndRejoin (void);
|
||||
#endif
|
||||
|
||||
void LMIC_shutdown (void);
|
||||
@ -435,7 +707,11 @@ void LMIC_init (void);
|
||||
void LMIC_reset (void);
|
||||
void LMIC_clrTxData (void);
|
||||
void LMIC_setTxData (void);
|
||||
int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed);
|
||||
void LMIC_setTxData_strict(void);
|
||||
lmic_tx_error_t LMIC_setTxData2(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);
|
||||
lmic_tx_error_t LMIC_sendWithCallback(u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed, lmic_txmessage_cb_t *pCb, void *pUserData);
|
||||
lmic_tx_error_t LMIC_sendWithCallback_strict(u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed, lmic_txmessage_cb_t *pCb, void *pUserData);
|
||||
void LMIC_sendAlive (void);
|
||||
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
@ -447,9 +723,6 @@ void LMIC_disableTracking (void);
|
||||
void LMIC_stopPingable (void);
|
||||
void LMIC_setPingable (u1_t intvExp);
|
||||
#endif
|
||||
#if !defined(DISABLE_JOIN)
|
||||
void LMIC_tryRejoin (void);
|
||||
#endif
|
||||
|
||||
void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey);
|
||||
void LMIC_setLinkCheckMode (bit_t enabled);
|
||||
@ -462,11 +735,31 @@ void LMIC_getSessionKeys (u4_t *netid, devaddr_t *devaddr, xref2u1_t nwkKey, xre
|
||||
void LMIC_requestNetworkTime(lmic_request_network_time_cb_t *pCallbackfn, void *pUserData);
|
||||
int LMIC_getNetworkTimeReference(lmic_time_reference_t *pReference);
|
||||
|
||||
int LMIC_registerRxMessageCb(lmic_rxmessage_cb_t *pRxMessageCb, void *pUserData);
|
||||
int LMIC_registerEventCb(lmic_event_cb_t *pEventCb, void *pUserData);
|
||||
|
||||
int LMIC_findNextChannel(uint16_t *, const uint16_t *, uint16_t, int);
|
||||
|
||||
u1_t LMIC_getBatteryLevel(void);
|
||||
u1_t LMIC_setBatteryLevel(u1_t /* uBattLevel */);
|
||||
|
||||
// APIs for client half of compliance.
|
||||
typedef u1_t lmic_compliance_rx_action_t;
|
||||
|
||||
enum lmic_compliance_rx_action_e {
|
||||
LMIC_COMPLIANCE_RX_ACTION_PROCESS = 0, // process this message normally
|
||||
LMIC_COMPLIANCE_RX_ACTION_START, // enter compliance mode, discard this message
|
||||
LMIC_COMPLIANCE_RX_ACTION_IGNORE, // continue in compliance mode, discard this message
|
||||
LMIC_COMPLIANCE_RX_ACTION_END // exit compliance mode, discard this message
|
||||
};
|
||||
|
||||
lmic_compliance_rx_action_t LMIC_complianceRxMessage(u1_t port, const u1_t *pMessage, size_t nMessage);
|
||||
|
||||
// Declare onEvent() function, to make sure any definition will have the
|
||||
// C conventions, even when in a C++ file.
|
||||
#if LMIC_ENABLE_onEvent
|
||||
DECL_ON_LMIC_EVENT;
|
||||
|
||||
|
||||
#endif /* LMIC_ENABLE_onEvent */
|
||||
|
||||
// Special APIs - for development or testing
|
||||
// !!!See implementation for caveats!!!
|
||||
@ -475,4 +768,7 @@ DECL_ON_LMIC_EVENT;
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
// names for backward compatibility
|
||||
#include "lmic_compat.h"
|
||||
|
||||
#endif // _lmic_h_
|
||||
|
238
src/lmic/lmic_as923.c
Executable file → Normal file
238
src/lmic/lmic_as923.c
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -35,6 +35,11 @@
|
||||
//
|
||||
// BEG: AS923 related stuff
|
||||
//
|
||||
enum {
|
||||
AS923_REGION_TX_EIRP_MAX_DBM =
|
||||
(LMIC_COUNTRY_CODE == LMIC_COUNTRY_CODE_JP) ? AS923_JP_TX_EIRP_MAX_DBM
|
||||
: AS923_TX_EIRP_MAX_DBM
|
||||
};
|
||||
|
||||
// see table in section 2.7.3
|
||||
CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
@ -50,16 +55,24 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS
|
||||
};
|
||||
|
||||
bit_t
|
||||
LMICas923_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
// see table in 2.7.6 -- this assumes UplinkDwellTime = 0.
|
||||
static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
|
||||
59+5, // [0]
|
||||
59+5, // [1]
|
||||
59+5, // [2]
|
||||
123+5, // [3]
|
||||
230+5, // [4]
|
||||
230+5, // [5]
|
||||
230+5, // [6]
|
||||
230+5 // [7]
|
||||
250+5, // [4]
|
||||
250+5, // [5]
|
||||
250+5, // [6]
|
||||
250+5 // [7]
|
||||
};
|
||||
|
||||
// see table in 2.7.6 -- this assumes UplinkDwellTime = 1.
|
||||
@ -76,16 +89,18 @@ static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = {
|
||||
|
||||
static uint8_t
|
||||
LMICas923_getUplinkDwellBit(uint8_t mcmd_txparam) {
|
||||
LMIC_API_PARAMETER(mcmd_txparam);
|
||||
if (mcmd_txparam == 0xFF)
|
||||
return AS923_INITIAL_TxParam_UplinkDwellTime;
|
||||
|
||||
return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0;
|
||||
return (mcmd_txparam & MCMD_TxParam_TxDWELL_MASK) != 0;
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
LMICas923_getDownlinkDwellBit(uint8_t mcmd_txparam) {
|
||||
LMIC_API_PARAMETER(mcmd_txparam);
|
||||
if (mcmd_txparam == 0xFF)
|
||||
return AS923_INITIAL_TxParam_DownlinkDwellTime;
|
||||
|
||||
return (LMIC.txParam & MCMD_TxParam_RxDWELL_MASK) != 0;
|
||||
return (mcmd_txparam & MCMD_TxParam_RxDWELL_MASK) != 0;
|
||||
}
|
||||
|
||||
uint8_t LMICas923_maxFrameLen(uint8_t dr) {
|
||||
@ -95,7 +110,7 @@ uint8_t LMICas923_maxFrameLen(uint8_t dr) {
|
||||
else
|
||||
return TABLE_GET_U1(maxFrameLens_dwell0, dr);
|
||||
} else {
|
||||
return 0xFF;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,7 +125,6 @@ static CONST_TABLE(s1_t, TXPOWLEVELS)[] = {
|
||||
-10, // [5]: MaxEIRP - 10dB
|
||||
-12, // [6]: MaxEIRP - 12dB
|
||||
-14, // [7]: MaxEIRP - 14dB
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
// from LoRaWAN 5.8: mapping from txParam to MaxEIRP
|
||||
@ -119,8 +133,9 @@ static CONST_TABLE(s1_t, TXMAXEIRP)[16] = {
|
||||
};
|
||||
|
||||
static int8_t LMICas923_getMaxEIRP(uint8_t mcmd_txparam) {
|
||||
// if uninitialized, return default.
|
||||
if (mcmd_txparam == 0xFF)
|
||||
return AS923_TX_EIRP_MAX_DBM;
|
||||
return AS923_REGION_TX_EIRP_MAX_DBM;
|
||||
else
|
||||
return TABLE_GET_S1(
|
||||
TXMAXEIRP,
|
||||
@ -130,15 +145,20 @@ static int8_t LMICas923_getMaxEIRP(uint8_t mcmd_txparam) {
|
||||
}
|
||||
|
||||
// translate from an encoded power to an actual power using
|
||||
// the maxeirp setting.
|
||||
// the maxeirp setting; return -128 if not legal.
|
||||
int8_t LMICas923_pow2dBm(uint8_t mcmd_ladr_p1) {
|
||||
uint8_t const pindex = (mcmd_ladr_p1&MCMD_LinkADRReq_POW_MASK)>>MCMD_LinkADRReq_POW_SHIFT;
|
||||
if (pindex < LENOF_TABLE(TXPOWLEVELS)) {
|
||||
s1_t const adj =
|
||||
TABLE_GET_S1(
|
||||
TXPOWLEVELS,
|
||||
(mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT
|
||||
pindex
|
||||
);
|
||||
|
||||
return adj;
|
||||
return LMICas923_getMaxEIRP(LMIC.txParam) + adj;
|
||||
} else {
|
||||
return -128;
|
||||
}
|
||||
}
|
||||
|
||||
// only used in this module, but used by variant macro dr2hsym().
|
||||
@ -171,6 +191,9 @@ void LMICas923_initDefaultChannels(bit_t join) {
|
||||
LMIC_API_PARAMETER(join);
|
||||
|
||||
os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq));
|
||||
#if !defined(DISABLE_MCMD_DlChannelReq)
|
||||
os_clearMem(&LMIC.channelDlFreq, sizeof(LMIC.channelDlFreq));
|
||||
#endif // !DISABLE_MCMD_DlChannelReq
|
||||
os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap));
|
||||
os_clearMem(&LMIC.bands, sizeof(LMIC.bands));
|
||||
|
||||
@ -181,7 +204,7 @@ void LMICas923_initDefaultChannels(bit_t join) {
|
||||
}
|
||||
|
||||
LMIC.bands[BAND_CENTI].txcap = AS923_TX_CAP;
|
||||
LMIC.bands[BAND_CENTI].txpow = AS923_TX_EIRP_MAX_DBM;
|
||||
LMIC.bands[BAND_CENTI].txpow = AS923_REGION_TX_EIRP_MAX_DBM;
|
||||
LMIC.bands[BAND_CENTI].lastchnl = os_getRndU1() % MAX_CHANNELS;
|
||||
LMIC.bands[BAND_CENTI].avail = os_getTime();
|
||||
}
|
||||
@ -216,7 +239,31 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// \brief query number of default channels.
|
||||
///
|
||||
u1_t LMIC_queryNumDefaultChannels() {
|
||||
return NUM_DEFAULT_CHANNELS;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief LMIC_setupChannel for EU 868
|
||||
///
|
||||
/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range
|
||||
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
|
||||
/// This routine is used internally for MAC commands, so we enforce
|
||||
/// this for the extenal API as well.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
// zero the band bits in freq, just in case.
|
||||
freq &= ~3;
|
||||
|
||||
if (chidx < NUM_DEFAULT_CHANNELS) {
|
||||
// can't do anything to a default channel.
|
||||
return 0;
|
||||
}
|
||||
bit_t fEnable = (freq != 0);
|
||||
if (chidx >= MAX_CHANNELS)
|
||||
return 0;
|
||||
if (band == -1) {
|
||||
@ -229,7 +276,10 @@ bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
LMIC.channelDrMap[chidx] =
|
||||
drmap == 0 ? DR_RANGE_MAP(AS923_DR_SF12, AS923_DR_SF7B)
|
||||
: drmap;
|
||||
if (fEnable)
|
||||
LMIC.channelMap |= 1 << chidx; // enabled right away
|
||||
else
|
||||
LMIC.channelMap &= ~(1 << chidx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -260,6 +310,8 @@ void LMICas923_setRx1Params(void) {
|
||||
int effective_rx1DrOffset;
|
||||
int candidateDr;
|
||||
|
||||
LMICeulike_setRx1Freq();
|
||||
|
||||
effective_rx1DrOffset = LMIC.rx1DrOffset;
|
||||
// per section 2.7.7 of regional, lines 1101:1103:
|
||||
switch (effective_rx1DrOffset) {
|
||||
@ -288,36 +340,109 @@ void LMICas923_setRx1Params(void) {
|
||||
LMIC.rps = dndr2rps(LMIC.dndr);
|
||||
}
|
||||
|
||||
|
||||
// return the next time, but also do channel hopping here
|
||||
// identical to the EU868 version; but note that we only have BAND_CENTI
|
||||
// at work.
|
||||
///
|
||||
/// \brief change the TX channel given the desired tx time.
|
||||
///
|
||||
/// \param [in] now is the time at which we want to transmit. In fact, it's always
|
||||
/// the current time.
|
||||
///
|
||||
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
|
||||
/// selected channel.
|
||||
///
|
||||
/// \details
|
||||
/// We scan all the bands, creating a mask of all enabled channels that are
|
||||
/// feasible at the earliest possible time. We then randomly choose one from
|
||||
/// that, updating the shuffle mask.
|
||||
///
|
||||
/// \note
|
||||
/// identical to the EU868 version; but note that we only have BAND_CENTI
|
||||
/// in AS923.
|
||||
///
|
||||
ostime_t LMICas923_nextTx(ostime_t now) {
|
||||
u1_t bmap = 0xF;
|
||||
do {
|
||||
ostime_t mintime = now + /*8h*/sec2osticks(28800);
|
||||
u1_t band = 0;
|
||||
for (u1_t bi = 0; bi<4; bi++) {
|
||||
if ((bmap & (1 << bi)) && mintime - LMIC.bands[bi].avail > 0)
|
||||
mintime = LMIC.bands[band = bi].avail;
|
||||
u2_t availMap;
|
||||
u2_t feasibleMap;
|
||||
u1_t bandMap;
|
||||
|
||||
// set mintime to the earliest time of all enabled channels
|
||||
// (can't just look at bands); and for a given channel, we
|
||||
// can't tell if we're ready till we've checked all possible
|
||||
// avail times.
|
||||
bandMap = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
u2_t chnlBit = 1 << chnl;
|
||||
|
||||
// none at any higher numbers?
|
||||
if (LMIC.channelMap < chnlBit)
|
||||
break;
|
||||
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & chnlBit) == 0)
|
||||
continue;
|
||||
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
|
||||
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
|
||||
u1_t const thisBandBit = 1 << band;
|
||||
// already considered?
|
||||
if ((bandMap & thisBandBit) != 0)
|
||||
continue;
|
||||
|
||||
// consider this band.
|
||||
bandMap |= thisBandBit;
|
||||
|
||||
// enabled, not considered, feasible: adjust the min time.
|
||||
if ((s4_t)(mintime - LMIC.bands[band].avail) > 0)
|
||||
mintime = LMIC.bands[band].avail;
|
||||
}
|
||||
|
||||
// make a mask of candidates available for use
|
||||
availMap = 0;
|
||||
feasibleMap = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
u2_t chnlBit = 1 << chnl;
|
||||
|
||||
// none at any higher numbers?
|
||||
if (LMIC.channelMap < chnlBit)
|
||||
break;
|
||||
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & chnlBit) == 0)
|
||||
continue;
|
||||
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
|
||||
// This channel is feasible. But might not be available.
|
||||
feasibleMap |= chnlBit;
|
||||
|
||||
// not available yet?
|
||||
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
|
||||
if ((s4_t)(LMIC.bands[band].avail - mintime) > 0)
|
||||
continue;
|
||||
|
||||
// ok: this is a candidate.
|
||||
availMap |= chnlBit;
|
||||
}
|
||||
|
||||
// find the next available chennel.
|
||||
u2_t saveShuffleMap = LMIC.channelShuffleMap;
|
||||
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availMap, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
|
||||
|
||||
// restore bits in the shuffleMap that were on, but might have reset
|
||||
// if availMap was used to refresh shuffleMap. These are channels that
|
||||
// are feasble but not yet candidates due to band saturation
|
||||
LMIC.channelShuffleMap |= saveShuffleMap & feasibleMap & ~availMap;
|
||||
|
||||
if (candidateCh >= 0) {
|
||||
// update the channel; otherwise we'll just use the
|
||||
// most recent one.
|
||||
LMIC.txChnl = candidateCh;
|
||||
}
|
||||
// Find next channel in given band
|
||||
u1_t chnl = LMIC.bands[band].lastchnl;
|
||||
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) {
|
||||
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
|
||||
chnl -= MAX_CHANNELS;
|
||||
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
|
||||
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
|
||||
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
|
||||
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
|
||||
return mintime;
|
||||
}
|
||||
}
|
||||
if ((bmap &= ~(1 << band)) == 0) {
|
||||
// No feasible channel found!
|
||||
return mintime;
|
||||
}
|
||||
} while (1);
|
||||
}
|
||||
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
@ -334,23 +459,18 @@ ostime_t LMICas923_nextJoinState(void) {
|
||||
}
|
||||
#endif // !DISABLE_JOIN
|
||||
|
||||
// txDone handling for FSK.
|
||||
void
|
||||
LMICas923_txDoneFSK(ostime_t delay, osjobcb_t func) {
|
||||
LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160);
|
||||
LMIC.rxsyms = RXLEN_FSK;
|
||||
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
|
||||
}
|
||||
|
||||
void
|
||||
LMICas923_initJoinLoop(void) {
|
||||
LMIC.txParam = 0xFF;
|
||||
LMICeulike_initJoinLoop(NUM_DEFAULT_CHANNELS, /* adr dBm */ AS923_TX_EIRP_MAX_DBM);
|
||||
// LMIC.txParam is set to 0xFF by the central code at init time.
|
||||
LMICeulike_initJoinLoop(NUM_DEFAULT_CHANNELS, /* adr dBm */ AS923_REGION_TX_EIRP_MAX_DBM);
|
||||
}
|
||||
|
||||
void
|
||||
LMICas923_updateTx(ostime_t txbeg) {
|
||||
u4_t freq = LMIC.channelFreq[LMIC.txChnl];
|
||||
u4_t dwellDelay;
|
||||
u4_t globalDutyDelay;
|
||||
|
||||
// Update global/band specific duty cycle stats
|
||||
ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
|
||||
// Update channel/global duty cycle stats
|
||||
@ -358,8 +478,18 @@ LMICas923_updateTx(ostime_t txbeg) {
|
||||
LMIC.freq = freq & ~(u4_t)3;
|
||||
LMIC.txpow = LMICas923_getMaxEIRP(LMIC.txParam);
|
||||
band->avail = txbeg + airtime * band->txcap;
|
||||
if (LMIC.globalDutyRate != 0)
|
||||
LMIC.globalDutyAvail = txbeg + (airtime << LMIC.globalDutyRate);
|
||||
dwellDelay = globalDutyDelay = 0;
|
||||
if (LMIC.globalDutyRate != 0) {
|
||||
globalDutyDelay = (airtime << LMIC.globalDutyRate);
|
||||
}
|
||||
if (LMICas923_getUplinkDwellBit(LMIC.txParam)) {
|
||||
dwellDelay = AS923_UPLINK_DWELL_TIME_osticks;
|
||||
}
|
||||
if (dwellDelay > globalDutyDelay) {
|
||||
globalDutyDelay = dwellDelay;
|
||||
}
|
||||
if (globalDutyDelay != 0)
|
||||
LMIC.globalDutyAvail = txbeg + globalDutyDelay;
|
||||
}
|
||||
|
||||
|
||||
|
182
src/lmic/lmic_au921.c → src/lmic/lmic_au915.c
Executable file → Normal file
182
src/lmic/lmic_au921.c → src/lmic/lmic_au915.c
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -30,10 +30,10 @@
|
||||
|
||||
#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)[] = {
|
||||
@ -55,15 +55,68 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS
|
||||
};
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = {
|
||||
59+5, 59+5, 59+5, 123+5, 230+5, 230+5, 230+5, 255,
|
||||
41+5, 117+5, 230+5, 230+5, 230+5, 230+5 };
|
||||
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;
|
||||
}
|
||||
|
||||
uint8_t LMICau921_maxFrameLen(uint8_t dr) {
|
||||
if (dr < LENOF_TABLE(maxFrameLens))
|
||||
return TABLE_GET_U1(maxFrameLens, dr);
|
||||
static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
|
||||
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 0,
|
||||
61+5, 137+5, 250+5, 250+5, 250+5, 250+5 };
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = {
|
||||
0, 0, 19+5, 61+5, 133+5, 250+5, 250+5, 0,
|
||||
61+5, 137+5, 250+5, 250+5, 250+5, 250+5 };
|
||||
|
||||
static bit_t
|
||||
LMICau915_getUplinkDwellBit() {
|
||||
// if uninitialized, return default.
|
||||
if (LMIC.txParam == 0xFF) {
|
||||
return AU915_INITIAL_TxParam_UplinkDwellTime;
|
||||
}
|
||||
return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0;
|
||||
}
|
||||
|
||||
uint8_t LMICau915_maxFrameLen(uint8_t dr) {
|
||||
if (LMICau915_getUplinkDwellBit()) {
|
||||
if (dr < LENOF_TABLE(maxFrameLens_dwell0))
|
||||
return TABLE_GET_U1(maxFrameLens_dwell0, dr);
|
||||
else
|
||||
return 0xFF;
|
||||
return 0;
|
||||
} else {
|
||||
if (dr < LENOF_TABLE(maxFrameLens_dwell1))
|
||||
return TABLE_GET_U1(maxFrameLens_dwell1, dr);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// from LoRaWAN 5.8: mapping from txParam to MaxEIRP
|
||||
static CONST_TABLE(s1_t, TXMAXEIRP)[16] = {
|
||||
8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36
|
||||
};
|
||||
|
||||
static int8_t LMICau915_getMaxEIRP(uint8_t mcmd_txparam) {
|
||||
// if uninitialized, return default.
|
||||
if (mcmd_txparam == 0xFF)
|
||||
return AU915_TX_EIRP_MAX_DBM;
|
||||
else
|
||||
return TABLE_GET_S1(
|
||||
TXMAXEIRP,
|
||||
(mcmd_txparam & MCMD_TxParam_MaxEIRP_MASK) >>
|
||||
MCMD_TxParam_MaxEIRP_SHIFT
|
||||
);
|
||||
}
|
||||
|
||||
int8_t LMICau915_pow2dbm(uint8_t mcmd_ladr_p1) {
|
||||
if ((mcmd_ladr_p1 & MCMD_LinkADRReq_POW_MASK) == MCMD_LinkADRReq_POW_MASK)
|
||||
return -128;
|
||||
else {
|
||||
return ((s1_t)(LMICau915_getMaxEIRP(LMIC.txParam) - (((mcmd_ladr_p1)&MCMD_LinkADRReq_POW_MASK)<<1)));
|
||||
}
|
||||
}
|
||||
|
||||
static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = {
|
||||
@ -86,20 +139,37 @@ static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = {
|
||||
// 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
|
||||
// of the table.
|
||||
ostime_t LMICau921_dr2hsym(uint8_t dr) {
|
||||
ostime_t LMICau915_dr2hsym(uint8_t 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;
|
||||
if (freq < AU921_FREQ_MIN || freq > AU921_FREQ_MAX)
|
||||
if (freq < AU915_FREQ_MIN || freq > AU915_FREQ_MAX)
|
||||
freq = 0;
|
||||
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) {
|
||||
LMIC_API_PARAMETER(chidx);
|
||||
LMIC_API_PARAMETER(freq);
|
||||
@ -109,9 +179,11 @@ bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
return 0; // all channels are hardwired.
|
||||
}
|
||||
|
||||
void LMIC_disableChannel(u1_t channel) {
|
||||
bit_t LMIC_disableChannel(u1_t channel) {
|
||||
bit_t result = 0;
|
||||
if (channel < 72) {
|
||||
if (ENABLED_CHANNEL(channel)) {
|
||||
result = 1;
|
||||
if (IS_CHANNEL_125khz(channel))
|
||||
LMIC.activeChannels125khz--;
|
||||
else if (IS_CHANNEL_500khz(channel))
|
||||
@ -119,11 +191,14 @@ void LMIC_disableChannel(u1_t channel) {
|
||||
}
|
||||
LMIC.channelMap[channel >> 4] &= ~(1 << (channel & 0xF));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void LMIC_enableChannel(u1_t channel) {
|
||||
bit_t LMIC_enableChannel(u1_t channel) {
|
||||
bit_t result = 0;
|
||||
if (channel < 72) {
|
||||
if (!ENABLED_CHANNEL(channel)) {
|
||||
result = 1;
|
||||
if (IS_CHANNEL_125khz(channel))
|
||||
LMIC.activeChannels125khz++;
|
||||
else if (IS_CHANNEL_500khz(channel))
|
||||
@ -131,78 +206,101 @@ void LMIC_enableChannel(u1_t channel) {
|
||||
}
|
||||
LMIC.channelMap[channel >> 4] |= (1 << (channel & 0xF));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void LMIC_enableSubBand(u1_t band) {
|
||||
bit_t LMIC_enableSubBand(u1_t band) {
|
||||
ASSERT(band < 8);
|
||||
u1_t start = band * 8;
|
||||
u1_t end = start + 8;
|
||||
bit_t result = 0;
|
||||
|
||||
// enable all eight 125 kHz channels in this subband
|
||||
for (int channel = start; channel < end; ++channel)
|
||||
LMIC_enableChannel(channel);
|
||||
result |= LMIC_enableChannel(channel);
|
||||
|
||||
// there's a single 500 kHz channel associated with
|
||||
// each group of 8 125 kHz channels. Enable it, too.
|
||||
LMIC_enableChannel(64 + band);
|
||||
result |= LMIC_enableChannel(64 + band);
|
||||
return result;
|
||||
}
|
||||
void LMIC_disableSubBand(u1_t band) {
|
||||
|
||||
bit_t LMIC_disableSubBand(u1_t band) {
|
||||
ASSERT(band < 8);
|
||||
u1_t start = band * 8;
|
||||
u1_t end = start + 8;
|
||||
bit_t result = 0;
|
||||
|
||||
// disable all eight 125 kHz channels in this subband
|
||||
for (int channel = start; channel < end; ++channel)
|
||||
LMIC_disableChannel(channel);
|
||||
result |= LMIC_disableChannel(channel);
|
||||
|
||||
// there's a single 500 kHz channel associated with
|
||||
// each group of 8 125 kHz channels. Disable it, too.
|
||||
LMIC_disableChannel(64 + band);
|
||||
result |= LMIC_disableChannel(64 + band);
|
||||
return result;
|
||||
}
|
||||
void LMIC_selectSubBand(u1_t band) {
|
||||
|
||||
bit_t LMIC_selectSubBand(u1_t band) {
|
||||
bit_t result = 0;
|
||||
|
||||
ASSERT(band < 8);
|
||||
for (int b = 0; b<8; ++b) {
|
||||
if (band == b)
|
||||
LMIC_enableSubBand(b);
|
||||
result |= LMIC_enableSubBand(b);
|
||||
else
|
||||
LMIC_disableSubBand(b);
|
||||
result |= LMIC_disableSubBand(b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void LMICau921_updateTx(ostime_t txbeg) {
|
||||
void LMICau915_updateTx(ostime_t txbeg) {
|
||||
u1_t chnl = LMIC.txChnl;
|
||||
LMIC.txpow = AU921_TX_EIRP_MAX_DBM;
|
||||
LMIC.txpow = LMICau915_getMaxEIRP(LMIC.txParam);
|
||||
if (chnl < 64) {
|
||||
LMIC.freq = AU921_125kHz_UPFBASE + chnl*AU921_125kHz_UPFSTEP;
|
||||
LMIC.freq = AU915_125kHz_UPFBASE + chnl*AU915_125kHz_UPFSTEP;
|
||||
} else {
|
||||
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 stats
|
||||
// Update global duty cycle stat and deal with dwell time.
|
||||
u4_t dwellDelay;
|
||||
u4_t globalDutyDelay;
|
||||
dwellDelay = globalDutyDelay = 0;
|
||||
|
||||
if (LMIC.globalDutyRate != 0) {
|
||||
ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
|
||||
LMIC.globalDutyAvail = txbeg + (airtime << LMIC.globalDutyRate);
|
||||
globalDutyDelay = txbeg + (airtime << LMIC.globalDutyRate);
|
||||
}
|
||||
if (LMICau915_getUplinkDwellBit(LMIC.txParam)) {
|
||||
dwellDelay = AU915_UPLINK_DWELL_TIME_osticks;
|
||||
}
|
||||
if (dwellDelay > globalDutyDelay) {
|
||||
globalDutyDelay = dwellDelay;
|
||||
}
|
||||
if (globalDutyDelay != 0) {
|
||||
LMIC.globalDutyAvail = txbeg + globalDutyDelay;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
void LMICau921_setBcnRxParams(void) {
|
||||
void LMICau915_setBcnRxParams(void) {
|
||||
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);
|
||||
}
|
||||
#endif // !DISABLE_BEACONS
|
||||
|
||||
// set the Rx1 dndr, rps.
|
||||
void LMICau921_setRx1Params(void) {
|
||||
void LMICau915_setRx1Params(void) {
|
||||
u1_t const txdr = LMIC.dndr;
|
||||
u1_t candidateDr;
|
||||
LMIC.freq = AU921_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * AU921_500kHz_DNFSTEP;
|
||||
if ( /* TX datarate */txdr < AU921_DR_SF8C)
|
||||
LMIC.freq = AU915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * AU915_500kHz_DNFSTEP;
|
||||
if ( /* TX datarate */txdr < AU915_DR_SF8C)
|
||||
candidateDr = txdr + 8 - LMIC.rx1DrOffset;
|
||||
else
|
||||
candidateDr = AU921_DR_SF7CR;
|
||||
candidateDr = AU915_DR_SF7CR;
|
||||
|
||||
if (candidateDr < LORAWAN_DR8)
|
||||
candidateDr = LORAWAN_DR8;
|
||||
@ -213,9 +311,17 @@ void LMICau921_setRx1Params(void) {
|
||||
LMIC.rps = dndr2rps(LMIC.dndr);
|
||||
}
|
||||
|
||||
void LMICau915_initJoinLoop(void) {
|
||||
// LMIC.txParam is set to 0xFF by the central code at init time.
|
||||
LMICuslike_initJoinLoop();
|
||||
|
||||
// initialize the adrTxPower.
|
||||
LMIC.adrTxPow = LMICau915_getMaxEIRP(LMIC.txParam); // dBm
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// END: AU921 related stuff
|
||||
// END: AU915 related stuff
|
||||
//
|
||||
// ================================================================================
|
||||
#endif
|
102
src/lmic/lmic_bandplan.h
Executable file → Normal file
102
src/lmic/lmic_bandplan.h
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -37,10 +37,12 @@
|
||||
# include "lmic_bandplan_eu868.h"
|
||||
#elif defined(CFG_us915)
|
||||
# include "lmic_bandplan_us915.h"
|
||||
#elif defined(CFG_au921)
|
||||
# include "lmic_bandplan_au921.h"
|
||||
#elif defined(CFG_au915)
|
||||
# include "lmic_bandplan_au915.h"
|
||||
#elif defined(CFG_as923)
|
||||
# include "lmic_bandplan_as923.h"
|
||||
#elif defined(CFG_kr920)
|
||||
# include "lmic_bandplan_kr920.h"
|
||||
#elif defined(CFG_in866)
|
||||
# include "lmic_bandplan_in866.h"
|
||||
#else
|
||||
@ -52,8 +54,8 @@
|
||||
# error "DNW2_SAFETY_ZONE not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#ifndef maxFrameLen
|
||||
# error "maxFrameLen() not defined by bandplan"
|
||||
#ifndef LMICbandplan_maxFrameLen
|
||||
# error "LMICbandplan_maxFrameLen() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#ifndef pow2dBm
|
||||
@ -104,6 +106,10 @@
|
||||
# error "LMICbandplan_setBcnRxParams() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#if !defined(LMICbandplan_canMapChannels)
|
||||
# error "LMICbandplan_canMapChannels() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#if !defined(LMICbandplan_mapChannels)
|
||||
# error "LMICbandplan_mapChannels() not defined by bandplan"
|
||||
#endif
|
||||
@ -143,16 +149,73 @@
|
||||
#if !defined(LMICbandplan_init)
|
||||
# error "LMICbandplan_init() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#if !defined(LMICbandplan_saveAdrState)
|
||||
# error "LMICbandplan_saveAdrState() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#if !defined(LMICbandplan_compareAdrState)
|
||||
# error "LMICbandplan_compareAdrState() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#if !defined(LMICbandplan_restoreAdrState)
|
||||
# error "LMICbandplan_restoreAdrState() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#if !defined(LMICbandplan_isDataRateFeasible)
|
||||
# error "LMICbandplan_isDataRateFeasible() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#if !defined(LMICbandplan_validDR)
|
||||
# error "LMICbandplan_validDR() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
#if !defined(LMICbandplan_processJoinAcceptCFList)
|
||||
# error "LMICbandplan_processJoinAcceptCFList() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
//
|
||||
// Things common to lmic.c code
|
||||
//
|
||||
#define LMICbandplan_MINRX_SYMS_LoRa_ClassA 6
|
||||
#define LMICbandplan_RX_ERROR_ABS_osticks ms2osticks(10)
|
||||
|
||||
// Semtech inherently (by calculating in ms and taking ceilings)
|
||||
// rounds up to the next higher ms. It's a lot easier for us
|
||||
// to just add margin for things like hardware ramp-up time
|
||||
// and clock calibration when running from the LSE and HSI
|
||||
// clocks on an STM32.
|
||||
#define LMICbandplan_RX_EXTRA_MARGIN_osticks us2osticks(2000)
|
||||
|
||||
// probably this should be the same as the Class-A value, but
|
||||
// we have not the means to thoroughly test this. This is the
|
||||
// number of rxsyms used in the computations for ping and beacon
|
||||
// windows.
|
||||
#define LMICbandplan_MINRX_SYMS_LoRa_ClassB 5
|
||||
|
||||
#define LMICbandplan_PAMBL_SYMS 8
|
||||
#define LMICbandplan_PAMBL_FSK 5
|
||||
#define LMICbandplan_PRERX_FSK 1
|
||||
#define LMICbandplan_RXLEN_FSK (1+5+2)
|
||||
|
||||
// Legacy names
|
||||
#if !defined(MINRX_SYMS)
|
||||
#define MINRX_SYMS 5
|
||||
# define MINRX_SYMS LMICbandplan_MINRX_SYMS_LoRa_ClassB
|
||||
#endif // !defined(MINRX_SYMS)
|
||||
#define PAMBL_SYMS 8
|
||||
#define PAMBL_FSK 5
|
||||
#define PRERX_FSK 1
|
||||
#define RXLEN_FSK (1+5+2)
|
||||
#define PAMBL_SYMS LMICbandplan_PAMBL_SYMS
|
||||
#define PAMBL_FSK LMICbandplan_PAMBL_FSK
|
||||
#define PRERX_FSK LMICbandplan_PRERX_FSK
|
||||
#define RXLEN_FSK LMICbandplan_RXLEN_FSK
|
||||
|
||||
// this is regional, but so far all regions are the same
|
||||
#if !defined(LMICbandplan_MAX_FCNT_GAP)
|
||||
# define LMICbandplan_MAX_FCNT_GAP 16384
|
||||
#endif // !defined LWAN_MAX_FCNT_GAP
|
||||
|
||||
// this is probably regional, but for now default can be the same
|
||||
#if !defined(LMICbandplan_TX_RECOVERY_ms)
|
||||
# define LMICbandplan_TX_RECOVERY_ms 500
|
||||
#endif
|
||||
|
||||
#define BCN_INTV_osticks sec2osticks(BCN_INTV_sec)
|
||||
#define TXRX_GUARD_osticks ms2osticks(TXRX_GUARD_ms)
|
||||
@ -171,5 +234,24 @@
|
||||
// internal APIs
|
||||
ostime_t LMICcore_rndDelay(u1_t secSpan);
|
||||
void LMICcore_setDrJoin(u1_t reason, u1_t dr);
|
||||
ostime_t LMICcore_adjustForDrift(ostime_t delay, ostime_t hsym, rxsyms_t rxsyms_in);
|
||||
|
||||
// this has been exported to clients forever by lmic.h. including lorabase.h;
|
||||
// but with multiband lorabase can't really safely do this; it's really an LMIC-ism.
|
||||
// As are the rest of the static inlines..
|
||||
|
||||
///< \brief return non-zero if given DR is valid for this region.
|
||||
static inline bit_t validDR (dr_t dr) { return LMICbandplan_validDR(dr); } // in range
|
||||
|
||||
///< \brief region-specific table mapping DR to RPS/CRC bits; index by dr+1
|
||||
extern CONST_TABLE(u1_t, _DR2RPS_CRC)[];
|
||||
|
||||
static inline rps_t updr2rps (dr_t dr) { return (rps_t)TABLE_GET_U1(_DR2RPS_CRC, dr+1); }
|
||||
static inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); }
|
||||
static inline dr_t incDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+2)==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate
|
||||
static inline dr_t decDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr )==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate
|
||||
static inline dr_t assertDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)==ILLEGAL_RPS ? (dr_t)DR_DFLTMIN : dr; } // force into a valid DR
|
||||
static inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps
|
||||
|
||||
|
||||
#endif // _lmic_bandplan_h_
|
||||
|
24
src/lmic/lmic_bandplan_as923.h
Executable file → Normal file
24
src/lmic/lmic_bandplan_as923.h
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -26,15 +26,17 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _lmic_as923_h_
|
||||
# define _lmic_as923_h_
|
||||
#ifndef _lmic_bandplan_as923_h_
|
||||
# define _lmic_bandplan_as923_h_
|
||||
|
||||
#ifndef _lmic_eu_like_h_
|
||||
# include "lmic_eu_like.h"
|
||||
#endif
|
||||
|
||||
// return maximum frame length (including PHY header) for this data rate (as923); 0 --> not valid dr.
|
||||
uint8_t LMICas923_maxFrameLen(uint8_t dr);
|
||||
#define maxFrameLen(dr) LMICas923_maxFrameLen(dr)
|
||||
// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr.
|
||||
#define LMICbandplan_maxFrameLen(dr) LMICas923_maxFrameLen(dr)
|
||||
|
||||
int8_t LMICas923_pow2dBm(uint8_t mcmd_ladr_p1);
|
||||
#define pow2dBm(mcmd_ladr_p1) LMICas923_pow2dBm(mcmd_ladr_p1)
|
||||
@ -70,13 +72,7 @@ void LMICas923_init(void);
|
||||
|
||||
// override default for LMICbandplan_isFSK()
|
||||
#undef LMICbandplan_isFSK
|
||||
#define LMICbandplan_isFSK() (/* TX datarate */LMIC.rxsyms == AS923_DR_FSK)
|
||||
|
||||
// txDone handling for FSK.
|
||||
void
|
||||
LMICas923_txDoneFSK(ostime_t delay, osjobcb_t func);
|
||||
|
||||
#define LMICbandplan_txDoneFsk(delay, func) LMICas923_txDoneFSK(delay, func)
|
||||
#define LMICbandplan_isFSK() (/* RX datarate */LMIC.dndr == AS923_DR_FSK)
|
||||
|
||||
#define LMICbandplan_getInitialDrJoin() (AS923_DR_SF10)
|
||||
|
||||
@ -112,4 +108,8 @@ void LMICas923_updateTx(ostime_t txbeg);
|
||||
ostime_t LMICas923_nextJoinTime(ostime_t now);
|
||||
#define LMICbandplan_nextJoinTime(now) LMICas923_nextJoinTime(now)
|
||||
|
||||
#endif // _lmic_as923_h_
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICas923_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICas923_validDR(dr)
|
||||
|
||||
#endif // _lmic_bandplan_as923_h_
|
||||
|
50
src/lmic/lmic_bandplan_au921.h → src/lmic/lmic_bandplan_au915.h
Executable file → Normal file
50
src/lmic/lmic_bandplan_au921.h → src/lmic/lmic_bandplan_au915.h
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -26,38 +26,48 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _lmic_au921_h_
|
||||
# define _lmic_au921_h_
|
||||
#ifndef _lmic_bandplan_au915_h_
|
||||
# define _lmic_bandplan_au915_h_
|
||||
|
||||
// preconditions for lmic_us_like.h
|
||||
#define LMICuslike_getFirst500kHzDR() (AU921_DR_SF8C)
|
||||
|
||||
#define LMICuslike_getFirst500kHzDR() (LORAWAN_DR6)
|
||||
#define LMICuslike_getJoin125kHzDR() (LORAWAN_DR2)
|
||||
|
||||
#ifndef _lmic_us_like_h_
|
||||
# include "lmic_us_like.h"
|
||||
#endif
|
||||
|
||||
uint8_t LMICau921_maxFrameLen(uint8_t dr);
|
||||
#define maxFrameLen(dr) LMICau921_maxFrameLen(dr)
|
||||
// return maximum frame length (including PHY header) for this data rate (au915); 0 --> not valid dr.
|
||||
uint8_t LMICau915_maxFrameLen(uint8_t dr);
|
||||
// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr.
|
||||
#define LMICbandplan_maxFrameLen(dr) LMICau915_maxFrameLen(dr)
|
||||
|
||||
#define pow2dBm(mcmd_ladr_p1) ((s1_t)(30 - (((mcmd_ladr_p1)&MCMD_LADR_POW_MASK)<<1)))
|
||||
int8_t LMICau915_pow2dbm(uint8_t mcmd_ladr_p1);
|
||||
#define pow2dBm(mcmd_ladr_p1) LMICau915_pow2dbm(mcmd_ladr_p1)
|
||||
|
||||
ostime_t LMICau921_dr2hsym(uint8_t dr);
|
||||
#define dr2hsym(dr) LMICau921_dr2hsym(dr)
|
||||
ostime_t LMICau915_dr2hsym(uint8_t dr);
|
||||
#define dr2hsym(dr) LMICau915_dr2hsym(dr)
|
||||
|
||||
|
||||
#define LMICbandplan_getInitialDrJoin() (EU868_DR_SF7)
|
||||
#define LMICbandplan_getInitialDrJoin() (LORAWAN_DR2)
|
||||
|
||||
void LMICau921_setBcnRxParams(void);
|
||||
#define LMICbandplan_setBcnRxParams() LMICau921_setBcnRxParams()
|
||||
void LMICau915_initJoinLoop(void);
|
||||
#define LMICbandplan_initJoinLoop() LMICau915_initJoinLoop()
|
||||
|
||||
u4_t LMICau921_convFreq(xref2cu1_t ptr);
|
||||
#define LMICbandplan_convFreq(ptr) LMICau921_convFreq(ptr)
|
||||
void LMICau915_setBcnRxParams(void);
|
||||
#define LMICbandplan_setBcnRxParams() LMICau915_setBcnRxParams()
|
||||
|
||||
void LMICau921_setRx1Params(void);
|
||||
#define LMICbandplan_setRx1Params() LMICau921_setRx1Params()
|
||||
u4_t LMICau915_convFreq(xref2cu1_t ptr);
|
||||
#define LMICbandplan_convFreq(ptr) LMICau915_convFreq(ptr)
|
||||
|
||||
void LMICau921_updateTx(ostime_t txbeg);
|
||||
#define LMICbandplan_updateTx(txbeg) LMICau921_updateTx(txbeg)
|
||||
void LMICau915_setRx1Params(void);
|
||||
#define LMICbandplan_setRx1Params() LMICau915_setRx1Params()
|
||||
|
||||
#endif // _lmic_au921_h_
|
||||
void LMICau915_updateTx(ostime_t txbeg);
|
||||
#define LMICbandplan_updateTx(txbeg) LMICau915_updateTx(txbeg)
|
||||
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICau915_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICau915_validDR(dr)
|
||||
|
||||
#endif // _lmic_bandplan_au915_h_
|
21
src/lmic/lmic_bandplan_eu868.h
Executable file → Normal file
21
src/lmic/lmic_bandplan_eu868.h
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -33,8 +33,10 @@
|
||||
# include "lmic_eu_like.h"
|
||||
#endif
|
||||
|
||||
// return maximum frame length (including PHY header) for this data rate (eu868); 0 --> not valid dr.
|
||||
uint8_t LMICeu868_maxFrameLen(uint8_t dr);
|
||||
#define maxFrameLen(dr) LMICeu868_maxFrameLen(dr)
|
||||
// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr.
|
||||
#define LMICbandplan_maxFrameLen(dr) LMICeu868_maxFrameLen(dr)
|
||||
|
||||
int8_t LMICeu868_pow2dBm(uint8_t mcmd_ladr_p1);
|
||||
#define pow2dBm(mcmd_ladr_p1) LMICeu868_pow2dBm(mcmd_ladr_p1)
|
||||
@ -57,13 +59,7 @@ LMICeu868_isValidBeacon1(const uint8_t *d) {
|
||||
|
||||
// override default for LMICbandplan_isFSK()
|
||||
#undef LMICbandplan_isFSK
|
||||
#define LMICbandplan_isFSK() (/* TX datarate */LMIC.rxsyms == EU868_DR_FSK)
|
||||
|
||||
// txDone handling for FSK.
|
||||
void
|
||||
LMICeu868_txDoneFSK(ostime_t delay, osjobcb_t func);
|
||||
|
||||
#define LMICbandplan_txDoneFsk(delay, func) LMICeu868_txDoneFSK(delay, func)
|
||||
#define LMICbandplan_isFSK() (/* RX datarate */LMIC.dndr == EU868_DR_FSK)
|
||||
|
||||
#define LMICbandplan_getInitialDrJoin() (EU868_DR_SF7)
|
||||
|
||||
@ -89,4 +85,11 @@ void LMICeu868_initDefaultChannels(bit_t join);
|
||||
ostime_t LMICeu868_nextJoinTime(ostime_t now);
|
||||
#define LMICbandplan_nextJoinTime(now) LMICeu868_nextJoinTime(now)
|
||||
|
||||
void LMICeu868_setRx1Params(void);
|
||||
#define LMICbandplan_setRx1Params() LMICeu868_setRx1Params()
|
||||
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICeu868_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICeu868_validDR(dr)
|
||||
|
||||
#endif // _lmic_eu868_h_
|
||||
|
27
src/lmic/lmic_bandplan_in866.h
Executable file → Normal file
27
src/lmic/lmic_bandplan_in866.h
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -26,15 +26,17 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _lmic_in866_h_
|
||||
# define _lmic_in866_h_
|
||||
#ifndef _lmic_bandplan_in866_h_
|
||||
# define _lmic_bandplan_in866_h_
|
||||
|
||||
#ifndef _lmic_eu_like_h_
|
||||
# include "lmic_eu_like.h"
|
||||
#endif
|
||||
|
||||
// return maximum frame length (including PHY header) for this data rate (in866); 0 --> not valid dr.
|
||||
uint8_t LMICin866_maxFrameLen(uint8_t dr);
|
||||
#define maxFrameLen(dr) LMICin866_maxFrameLen(dr)
|
||||
// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr.
|
||||
#define LMICbandplan_maxFrameLen(dr) LMICin866_maxFrameLen(dr)
|
||||
|
||||
int8_t LMICin866_pow2dBm(uint8_t mcmd_ladr_p1);
|
||||
#define pow2dBm(mcmd_ladr_p1) LMICin866_pow2dBm(mcmd_ladr_p1)
|
||||
@ -54,13 +56,7 @@ LMICin866_isValidBeacon1(const uint8_t *d) {
|
||||
|
||||
// override default for LMICbandplan_isFSK()
|
||||
#undef LMICbandplan_isFSK
|
||||
#define LMICbandplan_isFSK() (/* TX datarate */LMIC.rxsyms == IN866_DR_FSK)
|
||||
|
||||
// txDone handling for FSK.
|
||||
void
|
||||
LMICin866_txDoneFSK(ostime_t delay, osjobcb_t func);
|
||||
|
||||
#define LMICbandplan_txDoneFsk(delay, func) LMICin866_txDoneFSK(delay, func)
|
||||
#define LMICbandplan_isFSK() (/* TX datarate */LMIC.dndr == IN866_DR_FSK)
|
||||
|
||||
#define LMICbandplan_getInitialDrJoin() (IN866_DR_SF7)
|
||||
|
||||
@ -82,4 +78,11 @@ ostime_t LMICin866_nextJoinState(void);
|
||||
void LMICin866_initDefaultChannels(bit_t join);
|
||||
#define LMICbandplan_initDefaultChannels(join) LMICin866_initDefaultChannels(join)
|
||||
|
||||
#endif // _lmic_in866_h_
|
||||
void LMICin866_setRx1Params(void);
|
||||
#define LMICbandplan_setRx1Params() LMICin866_setRx1Params()
|
||||
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICin866_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICin866_validDR(dr)
|
||||
|
||||
#endif // _lmic_bandplan_in866_h_
|
||||
|
95
src/lmic/lmic_bandplan_kr920.h
Normal file
95
src/lmic/lmic_bandplan_kr920.h
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the <organization> nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _lmic_kr920_h_
|
||||
# define _lmic_kr920_h_
|
||||
|
||||
#ifndef _lmic_eu_like_h_
|
||||
# include "lmic_eu_like.h"
|
||||
#endif
|
||||
|
||||
// return maximum frame length (including PHY header) for this data rate (kr920); 0 --> not valid dr.
|
||||
uint8_t LMICkr920_maxFrameLen(uint8_t dr);
|
||||
// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr.
|
||||
#define LMICbandplan_maxFrameLen(dr) LMICkr920_maxFrameLen(dr)
|
||||
|
||||
int8_t LMICkr920_pow2dBm(uint8_t mcmd_ladr_p1);
|
||||
#define pow2dBm(mcmd_ladr_p1) LMICkr920_pow2dBm(mcmd_ladr_p1)
|
||||
|
||||
// Times for half symbol per DR
|
||||
// Per DR table to minimize rounding errors
|
||||
ostime_t LMICkr920_dr2hsym(uint8_t dr);
|
||||
#define dr2hsym(dr) LMICkr920_dr2hsym(dr)
|
||||
|
||||
|
||||
// TODO(tmm@mcci.com) this looks bogus compared to current 1.02 regional
|
||||
// spec. https://github.com/mcci-catena/arduino-lmic/issues/18
|
||||
static inline int
|
||||
LMICkr920_isValidBeacon1(const uint8_t *d) {
|
||||
return d[OFF_BCN_CRC1] != (u1_t)os_crc16(d, OFF_BCN_CRC1);
|
||||
}
|
||||
|
||||
#undef LMICbandplan_isValidBeacon1
|
||||
#define LMICbandplan_isValidBeacon1(pFrame) LMICkr920_isValidBeacon1(pFrame)
|
||||
|
||||
// override default for LMICbandplan_isFSK()
|
||||
#undef LMICbandplan_isFSK
|
||||
#define LMICbandplan_isFSK() (/* always false */ 0)
|
||||
|
||||
#define LMICbandplan_getInitialDrJoin() (KR920_DR_SF7)
|
||||
|
||||
void LMICkr920_setBcnRxParams(void);
|
||||
#define LMICbandplan_setBcnRxParams() LMICkr920_setBcnRxParams()
|
||||
|
||||
u4_t LMICkr920_convFreq(xref2cu1_t ptr);
|
||||
#define LMICbandplan_convFreq(ptr) LMICkr920_convFreq(ptr)
|
||||
|
||||
void LMICkr920_initJoinLoop(void);
|
||||
#define LMICbandplan_initJoinLoop() LMICkr920_initJoinLoop()
|
||||
|
||||
ostime_t LMICkr920_nextTx(ostime_t now);
|
||||
#define LMICbandplan_nextTx(now) LMICkr920_nextTx(now)
|
||||
|
||||
ostime_t LMICkr920_nextJoinState(void);
|
||||
#define LMICbandplan_nextJoinState() LMICkr920_nextJoinState()
|
||||
|
||||
void LMICkr920_initDefaultChannels(bit_t join);
|
||||
#define LMICbandplan_initDefaultChannels(join) LMICkr920_initDefaultChannels(join)
|
||||
|
||||
void LMICkr920_setRx1Params(void);
|
||||
#define LMICbandplan_setRx1Params() LMICkr920_setRx1Params()
|
||||
|
||||
#undef LMICbandplan_updateTx
|
||||
void LMICkr920_updateTx(ostime_t txbeg);
|
||||
#define LMICbandplan_updateTx(t) LMICkr920_updateTx(t)
|
||||
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICkr920_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICkr920_validDR(dr)
|
||||
|
||||
#endif // _lmic_kr920_h_
|
27
src/lmic/lmic_bandplan_us915.h
Executable file → Normal file
27
src/lmic/lmic_bandplan_us915.h
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -26,26 +26,30 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _lmic_us915_h_
|
||||
# define _lmic_us915_h_
|
||||
#ifndef _lmic_bandplan_us915_h_
|
||||
# define _lmic_bandplan_us915_h_
|
||||
|
||||
// preconditions for lmic_us_like.h
|
||||
#define LMICuslike_getFirst500kHzDR() (US915_DR_SF8C)
|
||||
#define LMICuslike_getFirst500kHzDR() (LORAWAN_DR4)
|
||||
#define LMICuslike_getJoin125kHzDR() (LORAWAN_DR0)
|
||||
|
||||
#ifndef _lmic_us_like_h_
|
||||
# include "lmic_us_like.h"
|
||||
#endif
|
||||
|
||||
// return maximum frame length (including PHY header) for this data rate (us915); 0 --> not valid dr.
|
||||
uint8_t LMICus915_maxFrameLen(uint8_t dr);
|
||||
#define maxFrameLen(dr) LMICus915_maxFrameLen(dr)
|
||||
// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr.
|
||||
#define LMICbandplan_maxFrameLen(dr) LMICus915_maxFrameLen(dr)
|
||||
|
||||
#define pow2dBm(mcmd_ladr_p1) ((s1_t)(US915_TX_MAX_DBM - (((mcmd_ladr_p1)&MCMD_LADR_POW_MASK)<<1)))
|
||||
int8_t LMICus915_pow2dbm(uint8_t mcmd_ladr_p1);
|
||||
#define pow2dBm(mcmd_ladr_p1) LMICus915_pow2dbm(mcmd_ladr_p1)
|
||||
|
||||
ostime_t LMICus915_dr2hsym(uint8_t dr);
|
||||
#define dr2hsym(dr) LMICus915_dr2hsym(dr)
|
||||
|
||||
|
||||
#define LMICbandplan_getInitialDrJoin() (US915_DR_SF7)
|
||||
#define LMICbandplan_getInitialDrJoin() (LORAWAN_DR0)
|
||||
|
||||
void LMICus915_setBcnRxParams(void);
|
||||
#define LMICbandplan_setBcnRxParams() LMICus915_setBcnRxParams()
|
||||
@ -53,10 +57,17 @@ void LMICus915_setBcnRxParams(void);
|
||||
u4_t LMICus915_convFreq(xref2cu1_t ptr);
|
||||
#define LMICbandplan_convFreq(ptr) LMICus915_convFreq(ptr)
|
||||
|
||||
void LMICus915_initJoinLoop(void);
|
||||
#define LMICbandplan_initJoinLoop() LMICus915_initJoinLoop()
|
||||
|
||||
void LMICus915_setRx1Params(void);
|
||||
#define LMICbandplan_setRx1Params() LMICus915_setRx1Params()
|
||||
|
||||
void LMICus915_updateTx(ostime_t txbeg);
|
||||
#define LMICbandplan_updateTx(txbeg) LMICus915_updateTx(txbeg)
|
||||
|
||||
#endif // _lmic_us915_h_
|
||||
#undef LMICbandplan_validDR
|
||||
bit_t LMICus915_validDR(dr_t dr);
|
||||
#define LMICbandplan_validDR(dr) LMICus915_validDR(dr)
|
||||
|
||||
#endif // _lmic_bandplan_us915_h_
|
||||
|
217
src/lmic/lmic_channelshuffle.c
Normal file
217
src/lmic/lmic_channelshuffle.c
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
|
||||
Module: lmic_channelshuffle.c
|
||||
|
||||
Function:
|
||||
Channel scheduling without replacement.
|
||||
|
||||
Copyright and License:
|
||||
This file copyright (C) 2021 by
|
||||
|
||||
MCCI Corporation
|
||||
3520 Krums Corners Road
|
||||
Ithaca, NY 14850
|
||||
|
||||
See accompanying LICENSE file for copyright and license information.
|
||||
|
||||
Author:
|
||||
Terry Moore, MCCI Corporation April 2021
|
||||
|
||||
*/
|
||||
|
||||
#include "lmic.h"
|
||||
#include <string.h>
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Manifest constants and local declarations.
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries);
|
||||
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum);
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Read-only data.
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Variables.
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
/*
|
||||
|
||||
Name: LMIC_findNextChannel()
|
||||
|
||||
Function:
|
||||
Scan a shuffle mask, and select a channel (without replacement).
|
||||
|
||||
Definition:
|
||||
int LMIC_findNextChannel(
|
||||
uint16_t *pShuffleMask,
|
||||
const uint16_t *pEnableMask,
|
||||
uint16_t nEntries,
|
||||
int lastChannel
|
||||
);
|
||||
|
||||
Description:
|
||||
pShuffleMask and pEnableMask are bit vectors. Channels correspond to
|
||||
bits in little-endian order; entry [0] has channels 0 through 15, entry
|
||||
[1] channels 16 through 31, and so forth. nEntries specifies the number
|
||||
of entries in the mask vectors. The enable mask is 1 for a given channel
|
||||
if that channel is eligible for selection, 0 otherwise.
|
||||
|
||||
This routine selects channels from the shuffle mask until all entries
|
||||
are exhausted; it then refreshes the shuffle mask from the enable mask.
|
||||
|
||||
If it refreshes the channel mask, lastChannel is taken as a channel number
|
||||
that is to be avoided in the next selection. (This is to avoid back-to-back
|
||||
use of a channel across a refresh boundary.) Otherwise lastChannel is
|
||||
ignored. This avoidance can be suppresed by setting lastChannel to -1.
|
||||
If only one channel is enabled, lastChannel is also ignored. If lastChannel
|
||||
is actually disabled, lastChannel is also ignored.
|
||||
|
||||
Returns:
|
||||
A channel number, in 0 .. nEntries-1, or -1 if the enable mask is
|
||||
identically zero.
|
||||
|
||||
Notes:
|
||||
This routine is somewhat optimized for AVR processors, which don't have
|
||||
multi-bit shifts.
|
||||
|
||||
*/
|
||||
|
||||
int LMIC_findNextChannel(
|
||||
uint16_t *pShuffleMask,
|
||||
const uint16_t *pEnableMask,
|
||||
uint16_t nEntries,
|
||||
int lastChannel
|
||||
) {
|
||||
unsigned nSet16;
|
||||
uint16_t saveLastChannelVal;
|
||||
|
||||
// in case someone has changed the enable mask, update
|
||||
// the shuffle mask so there are no disable bits set.
|
||||
for (unsigned i = 0; i < nEntries; ++i) {
|
||||
pShuffleMask[i] &= pEnableMask[i];
|
||||
}
|
||||
|
||||
// count the set bits in the shuffle mask (with a factor of 16 for speed)
|
||||
nSet16 = sidewaysSum16(pShuffleMask, nEntries);
|
||||
|
||||
// if zero, copy the enable mask to the shuffle mask, and recount
|
||||
if (nSet16 == 0) {
|
||||
memcpy(pShuffleMask, pEnableMask, nEntries * sizeof(*pShuffleMask));
|
||||
nSet16 = sidewaysSum16(pShuffleMask, nEntries);
|
||||
} else {
|
||||
// don't try to skip the last channel because it can't be chosen.
|
||||
lastChannel = -1;
|
||||
}
|
||||
|
||||
// if still zero, return -1.
|
||||
if (nSet16 == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// if we have to skip a channel, and we have more than one choice, turn off
|
||||
// the last channel bit. Post condition: if we really clered a bit,
|
||||
// saveLastChannelVal will be non-zero.
|
||||
saveLastChannelVal = 0;
|
||||
if (nSet16 > 16 && lastChannel >= 0 && lastChannel <= (int)(nEntries * 16)) {
|
||||
uint16_t const saveLastChannelMask = (1 << (lastChannel & 0xF));
|
||||
|
||||
saveLastChannelVal = pShuffleMask[lastChannel >> 4] & saveLastChannelMask;
|
||||
pShuffleMask[lastChannel >> 4] &= ~saveLastChannelMask;
|
||||
|
||||
// if we cleared a bit, reduce the count.
|
||||
if (saveLastChannelVal > 0)
|
||||
nSet16 -= 16;
|
||||
}
|
||||
|
||||
if (saveLastChannelVal == 0) {
|
||||
// We didn't eliminate a channel, so we don't have to worry.
|
||||
lastChannel = -1;
|
||||
}
|
||||
|
||||
// get a random number
|
||||
unsigned choice = os_getRndU2() % ((uint16_t)nSet16 >> 4);
|
||||
|
||||
// choose a bit based on set bit
|
||||
unsigned channel = findNthSetBit(pShuffleMask, choice);
|
||||
pShuffleMask[channel / 16] ^= (1 << (channel & 0xF));
|
||||
|
||||
// handle channel skip
|
||||
if (lastChannel >= 0) {
|
||||
pShuffleMask[lastChannel >> 4] |= saveLastChannelVal;
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
static unsigned sidewaysSum16(const uint16_t *pMask, uint16_t nEntries) {
|
||||
unsigned result;
|
||||
|
||||
result = 0;
|
||||
for (; nEntries > 0; --nEntries, ++pMask)
|
||||
{
|
||||
uint16_t v = *pMask;
|
||||
|
||||
// the following is an adaptation of Knuth 7.1.3 (62). To avoid
|
||||
// lots of shifts (slow on AVR, and code intensive) and table lookups,
|
||||
// we sum popc * 16, then divide by 16.
|
||||
|
||||
// sum adjacent bits, making a series of 2-bit sums
|
||||
v = v - ((v >> 1) & 0x5555u);
|
||||
v = (v & 0x3333u) + ((v >> 2) & 0x3333u);
|
||||
// this assumes multiplies are essentialy free;
|
||||
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u);
|
||||
// Accumulate result, but note it's times 16.
|
||||
// AVR compiler should optimize the x8 shift.
|
||||
result += (v & 0xFF) + (v >> 8);
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
static unsigned findNthSetBit(const uint16_t *pMask, uint16_t bitnum) {
|
||||
unsigned result;
|
||||
result = 0;
|
||||
bitnum = bitnum * 16;
|
||||
for (;; result += 16) {
|
||||
uint16_t m = *pMask++;
|
||||
if (m == 0)
|
||||
continue;
|
||||
uint16_t v = m - ((m >> 1) & 0x5555u);
|
||||
v = (v & 0x3333u) + ((v >> 2) & 0x3333u);
|
||||
// this assumes multiplies are essentialy free;
|
||||
v = (v & 0xF0F0u) + ((v & 0x0F0Fu) * 16u);
|
||||
// Accumulate result, but note it's times 16.
|
||||
// AVR compiler should optimize the x8 shift.
|
||||
v = (v & 0xFF) + (v >> 8);
|
||||
if (v <= bitnum)
|
||||
bitnum -= v;
|
||||
else {
|
||||
// the selected bit is in this word. We need to count.
|
||||
while (bitnum > 0) {
|
||||
m &= m - 1;
|
||||
bitnum -= 16;
|
||||
}
|
||||
// now the lsb of m is our choice.
|
||||
// get a mask, then use Knuth 7.1.3 (59) to find the
|
||||
// bit number.
|
||||
m &= -m;
|
||||
result += ((m & 0x5555u) ? 0 : 1)
|
||||
+ ((m & 0x3333u) ? 0 : 2)
|
||||
+ ((m & 0x0F0Fu) ? 0 : 4)
|
||||
+ ((m & 0x00FFu) ? 0 : 8)
|
||||
;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
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_ */
|
771
src/lmic/lmic_compliance.c
Normal file
771
src/lmic/lmic_compliance.c
Normal file
@ -0,0 +1,771 @@
|
||||
/*
|
||||
|
||||
Module: lmic_compliance.c
|
||||
|
||||
Function:
|
||||
Implementation of the compliance engine.
|
||||
|
||||
Copyright notice and license info:
|
||||
See LICENSE file accompanying this project.
|
||||
|
||||
Author:
|
||||
Terry Moore, MCCI Corporation March 2019
|
||||
|
||||
Description:
|
||||
See function descriptions.
|
||||
|
||||
*/
|
||||
|
||||
#include "lmic.h"
|
||||
#include "lmic_compliance.h"
|
||||
#include "lorawan_spec_compliance.h"
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(LMIC_PRINTF_TO)
|
||||
# include <stdio.h>
|
||||
# define LMIC_COMPLIANCE_PRINTF(f, ...) printf(f, ## __VA_ARGS__)
|
||||
#else
|
||||
# define LMIC_COMPLIANCE_PRINTF(f, ...) do { ; } while (0)
|
||||
#endif
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Manifest constants and local declarations.
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
static void acEnterActiveMode(void);
|
||||
static void acExitActiveMode(void);
|
||||
static void acSendUplink(void);
|
||||
static void acSetTimer(ostime_t);
|
||||
static void acSendUplinkBuffer(void);
|
||||
static void evActivate(void);
|
||||
static void evDeactivate(void);
|
||||
static void evJoinCommand(void);
|
||||
static void evMessage(const uint8_t *pMessage, size_t nMessage);
|
||||
static lmic_compliance_fsmstate_t fsmDispatch(lmic_compliance_fsmstate_t, bool);
|
||||
static void fsmEval(void);
|
||||
static void fsmEvalDeferred(void);
|
||||
static osjobcbfn_t fsmJobCb;
|
||||
static bool isActivateMessage(const uint8_t *pMessage, size_t nMessage);
|
||||
static void evEchoCommand(const uint8_t *pMessage, size_t nMessage);
|
||||
static lmic_event_cb_t lmicEventCb;
|
||||
static lmic_txmessage_cb_t sendUplinkCompleteCb;
|
||||
static osjobcbfn_t timerExpiredCb;
|
||||
|
||||
/* these are declared global so the optimizer can chuck them without warnings */
|
||||
const char *LMICcompliance_txSuccessToString(int fSuccess);
|
||||
const char * LMICcompliance_fsmstate_getName(lmic_compliance_fsmstate_t state);
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Read-only data.
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Variables.
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
lmic_compliance_t LMIC_Compliance;
|
||||
|
||||
/*
|
||||
|
||||
Name: LMIC_complianceRxMessage()
|
||||
|
||||
Function:
|
||||
Add compliance-awareness to LMIC applications by filtering messages.
|
||||
|
||||
Definition:
|
||||
lmic_compliance_rx_action_t LMIC_complianceRxMessage(
|
||||
u1_t port,
|
||||
const u1_t *pMessage,
|
||||
size_t nMessage
|
||||
);
|
||||
|
||||
Description:
|
||||
Clients who want to handle the LoRaWAN compliance protocol on
|
||||
port 224 should call this routine each time a downlink message is
|
||||
received. This function will update the internal compliance state,
|
||||
and return an appropriate action to the user.
|
||||
|
||||
If the result is `LMIC_COMPLIANCE_RX_ACTION_PROCESS`, then the client should
|
||||
process the message as usual. Otherwise, the client should discard the
|
||||
message. The other values further allow the client to track entry into,
|
||||
and exit from, compliance state. `LMIC_COMPLIANCE_RX_ACTION_START` signals
|
||||
entry into compliance state; `LMIC_COMPLIANCE_RX_ACTION_END` signals exit
|
||||
from compliance state; and `LMIC_COMPLIANCE_RX_ACTION_IGNORE` indicates
|
||||
a mesage that should be discarded while already in compliance
|
||||
state.
|
||||
|
||||
Returns:
|
||||
See description.
|
||||
|
||||
*/
|
||||
|
||||
lmic_compliance_rx_action_t
|
||||
LMIC_complianceRxMessage(
|
||||
uint8_t port,
|
||||
const uint8_t *pMessage,
|
||||
size_t nMessage
|
||||
) {
|
||||
lmic_compliance_state_t const complianceState = LMIC_Compliance.state;
|
||||
|
||||
// update the counter used by the status message.
|
||||
++LMIC_Compliance.downlinkCount;
|
||||
|
||||
// filter normal messages.
|
||||
if (port != LORAWAN_PORT_COMPLIANCE) {
|
||||
return lmic_compliance_state_IsActive(complianceState)
|
||||
? LMIC_COMPLIANCE_RX_ACTION_PROCESS
|
||||
: LMIC_COMPLIANCE_RX_ACTION_IGNORE
|
||||
;
|
||||
}
|
||||
|
||||
// it's a message to port 224.
|
||||
// if we're not active, ignore everything but activation messages
|
||||
if (! lmic_compliance_state_IsActive(complianceState)) {
|
||||
if (isActivateMessage(pMessage, nMessage)) {
|
||||
evActivate();
|
||||
} // else ignore.
|
||||
} else {
|
||||
evMessage(pMessage, nMessage);
|
||||
}
|
||||
if (lmic_compliance_state_IsActive(complianceState) == lmic_compliance_state_IsActive(LMIC_Compliance.state))
|
||||
return LMIC_COMPLIANCE_RX_ACTION_IGNORE;
|
||||
else if (! lmic_compliance_state_IsActive(complianceState))
|
||||
return LMIC_COMPLIANCE_RX_ACTION_START;
|
||||
else
|
||||
return LMIC_COMPLIANCE_RX_ACTION_END;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Name: isActivateMessage()
|
||||
|
||||
Function:
|
||||
See whether a message is a LoRaWAN activate test mode message.
|
||||
|
||||
Definition:
|
||||
static bool isActivateMessage(
|
||||
const uint8_t *pMessage,
|
||||
size_t nMessage
|
||||
);
|
||||
|
||||
Description:
|
||||
The message body is compared to an activate message (per the
|
||||
LoRa Alliance End Device Certification spec).
|
||||
|
||||
Returns:
|
||||
The result is `true` if the message is an activation message;
|
||||
it's `false` otherwise.
|
||||
|
||||
*/
|
||||
|
||||
static bool
|
||||
isActivateMessage(
|
||||
const uint8_t *pMessage,
|
||||
size_t nMessage
|
||||
) {
|
||||
const uint8_t body[LORAWAN_COMPLIANCE_CMD_ACTIVATE_LEN] = {
|
||||
LORAWAN_COMPLIANCE_CMD_ACTIVATE,
|
||||
LORAWAN_COMPLIANCE_CMD_ACTIVATE_MAGIC,
|
||||
LORAWAN_COMPLIANCE_CMD_ACTIVATE_MAGIC,
|
||||
LORAWAN_COMPLIANCE_CMD_ACTIVATE_MAGIC,
|
||||
};
|
||||
|
||||
if (nMessage != sizeof(body))
|
||||
return false;
|
||||
|
||||
if (memcmp(pMessage, body, sizeof(body)) == 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Name: evActivate()
|
||||
|
||||
Function:
|
||||
Report an activation event to the finite state machine.
|
||||
|
||||
Definition:
|
||||
void evActivate(void);
|
||||
|
||||
Description:
|
||||
We report an activation event, and re-evaluate the FSM.
|
||||
|
||||
Returns:
|
||||
No explicit result.
|
||||
|
||||
*/
|
||||
|
||||
static void evActivate(void) {
|
||||
if (! lmic_compliance_state_IsActive(LMIC_Compliance.state)) {
|
||||
LMIC_Compliance.downlinkCount = 0;
|
||||
LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_ACTIVATE;
|
||||
LMIC_Compliance.state = LMIC_COMPLIANCE_STATE_ACTIVATING;
|
||||
|
||||
LMIC_Compliance.saveEvent.pEventCb = LMIC.client.eventCb;
|
||||
LMIC_Compliance.saveEvent.pUserData = LMIC.client.eventUserData;
|
||||
|
||||
#if CFG_LMIC_EU_like
|
||||
band_t *b = LMIC.bands;
|
||||
lmic_compliance_band_t *b_save = LMIC_Compliance.saveBands;
|
||||
|
||||
for (; b < &LMIC.bands[MAX_BANDS]; ++b, ++b_save) {
|
||||
b_save->txcap = b->txcap;
|
||||
b->txcap = 1;
|
||||
b->avail = os_getTime();
|
||||
}
|
||||
#endif // CFG_LMIC_EU_like
|
||||
|
||||
LMIC_registerEventCb(lmicEventCb, NULL);
|
||||
|
||||
fsmEvalDeferred();
|
||||
} else {
|
||||
LMIC_COMPLIANCE_PRINTF("Redundant ActivateTM message ignored.\n");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Name: evMessage()
|
||||
|
||||
Function:
|
||||
Process an inbound message while active.
|
||||
|
||||
Definition:
|
||||
void evMessage(const uint8_t *pMessage, size_t nMessage);
|
||||
|
||||
Description:
|
||||
The event is parsed, and the appropriate event(s) are sent into
|
||||
the finite state machine. Note that because of the way the LMIC
|
||||
works, we can assume that no uplink event is pending; so it's safe
|
||||
to launch a send from here.
|
||||
|
||||
Returns:
|
||||
No explicit result.
|
||||
|
||||
*/
|
||||
|
||||
static void evMessage(
|
||||
const uint8_t *pMessage,
|
||||
size_t nMessage
|
||||
) {
|
||||
if (nMessage == 0)
|
||||
return;
|
||||
|
||||
const uint8_t cmd = pMessage[0];
|
||||
switch (cmd) {
|
||||
case LORAWAN_COMPLIANCE_CMD_DEACTIVATE: {
|
||||
evDeactivate();
|
||||
break;
|
||||
}
|
||||
case LORAWAN_COMPLIANCE_CMD_ACTIVATE: {
|
||||
if (isActivateMessage(pMessage, nMessage))
|
||||
evActivate();
|
||||
break;
|
||||
}
|
||||
case LORAWAN_COMPLIANCE_CMD_SET_CONFIRM: {
|
||||
LMIC_Compliance.fsmFlags |= LMIC_COMPLIANCE_FSM_CONFIRM;
|
||||
break;
|
||||
}
|
||||
case LORAWAN_COMPLIANCE_CMD_SET_UNCONFIRM: {
|
||||
LMIC_Compliance.fsmFlags &= ~LMIC_COMPLIANCE_FSM_CONFIRM;
|
||||
break;
|
||||
}
|
||||
case LORAWAN_COMPLIANCE_CMD_ECHO: {
|
||||
evEchoCommand(pMessage, nMessage);
|
||||
break;
|
||||
}
|
||||
case LORAWAN_COMPLIANCE_CMD_LINK: {
|
||||
// we are required to initiate a Link
|
||||
break;
|
||||
}
|
||||
case LORAWAN_COMPLIANCE_CMD_JOIN: {
|
||||
evJoinCommand();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Name: evDeactivate()
|
||||
|
||||
Function:
|
||||
Report an deactivation event to the finite state machine.
|
||||
|
||||
Definition:
|
||||
void evDectivate(void);
|
||||
|
||||
Description:
|
||||
We report a deactivation event, and re-evaluate the FSM.
|
||||
We also set a flag so that we're return the appropriate
|
||||
status from the compliance entry point to the real
|
||||
application.
|
||||
|
||||
Returns:
|
||||
No explicit result.
|
||||
|
||||
*/
|
||||
|
||||
static void evDeactivate(void) {
|
||||
LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_DEACTIVATE;
|
||||
LMIC_Compliance.state = LMIC_COMPLIANCE_STATE_STOPPING;
|
||||
|
||||
// restore user's event handler.
|
||||
LMIC_registerEventCb(LMIC_Compliance.saveEvent.pEventCb, LMIC_Compliance.saveEvent.pUserData);
|
||||
|
||||
// restore band settings
|
||||
#if CFG_LMIC_EU_like
|
||||
band_t *b = LMIC.bands;
|
||||
lmic_compliance_band_t const *b_save = LMIC_Compliance.saveBands;
|
||||
|
||||
for (; b < &LMIC.bands[MAX_BANDS]; ++b, ++b_save) {
|
||||
b->txcap = b_save->txcap;
|
||||
}
|
||||
#endif // CFG_LMIC_EU_like
|
||||
|
||||
fsmEvalDeferred();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Name: evJoinCommand()
|
||||
|
||||
Function:
|
||||
Report that a join has been commanded.
|
||||
|
||||
Definition:
|
||||
void evJoinCommand(void);
|
||||
|
||||
Description:
|
||||
We unjoin from the network, and then report a deactivation
|
||||
of test mode. That will get us out of test mode and back
|
||||
to the compliance app. The next message send will trigger
|
||||
a join.
|
||||
|
||||
Returns:
|
||||
No explicit result.
|
||||
|
||||
*/
|
||||
|
||||
static void evJoinCommand(
|
||||
void
|
||||
) {
|
||||
LMIC_unjoin();
|
||||
evDeactivate();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Name: evEchoCommand()
|
||||
|
||||
Function:
|
||||
Format and transmit the response to an echo downlink (aka echo request).
|
||||
|
||||
Definition:
|
||||
void evEchoCommand(
|
||||
const uint8_t *pMessage,
|
||||
size_t nMessage
|
||||
);
|
||||
|
||||
Description:
|
||||
The echo response is formatted and transmitted. Since we just received
|
||||
a downlink, it's always safe to do this.
|
||||
|
||||
Returns:
|
||||
No explicit result.
|
||||
|
||||
*/
|
||||
|
||||
static void evEchoCommand(
|
||||
const uint8_t *pMessage,
|
||||
size_t nMessage
|
||||
) {
|
||||
uint8_t *pResponse;
|
||||
|
||||
if (nMessage > sizeof(LMIC_Compliance.uplinkMessage))
|
||||
return;
|
||||
|
||||
// create the echo message.
|
||||
pResponse = LMIC_Compliance.uplinkMessage;
|
||||
|
||||
// copy the command byte unchanged.
|
||||
*pResponse++ = *pMessage++;
|
||||
--nMessage;
|
||||
|
||||
// each byte in the body has to be incremented by one.
|
||||
for (; nMessage > 0; --nMessage) {
|
||||
*pResponse++ = (uint8_t)(*pMessage++ + 1);
|
||||
}
|
||||
|
||||
// now that the message is formatted, tell the fsm to send it;
|
||||
// need to use a separate job.
|
||||
LMIC_Compliance.uplinkSize = (uint8_t) (pResponse - LMIC_Compliance.uplinkMessage);
|
||||
LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_ECHO_REQUEST;
|
||||
fsmEvalDeferred();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Name: fsmEval()
|
||||
|
||||
Function:
|
||||
Evaluate the FSM, preventing recursion.
|
||||
|
||||
Definition:
|
||||
void fsmEval(void);
|
||||
|
||||
Description:
|
||||
We check for a nested call to evaluate the FSM;
|
||||
if detected, the processing is deferred until the
|
||||
current evaluation completes. Otherwise, we start
|
||||
a new FSM evaluation, which proceeds until the FSM
|
||||
returns a "no-change" result.
|
||||
|
||||
Returns:
|
||||
No explicit result.
|
||||
|
||||
*/
|
||||
|
||||
const char * LMICcompliance_fsmstate_getName(lmic_compliance_fsmstate_t state) {
|
||||
const char * const names[] = { LMIC_COMPLIANCE_FSMSTATE__NAMES };
|
||||
|
||||
if ((unsigned) state >= sizeof(names)/sizeof(names[0]))
|
||||
return "<<unknown>>";
|
||||
else
|
||||
return names[state];
|
||||
}
|
||||
|
||||
static void fsmEvalDeferred(void) {
|
||||
os_setCallback(&LMIC_Compliance.fsmJob, fsmJobCb);
|
||||
}
|
||||
|
||||
static void fsmJobCb(osjob_t *j) {
|
||||
LMIC_API_PARAMETER(j);
|
||||
fsmEval();
|
||||
}
|
||||
|
||||
static void fsmEval(void) {
|
||||
bool fNewState;
|
||||
|
||||
// check for reentry.
|
||||
do {
|
||||
lmic_compliance_fsmflags_t const fsmFlags = LMIC_Compliance.fsmFlags;
|
||||
|
||||
if (fsmFlags & LMIC_COMPLIANCE_FSM_ACTIVE) {
|
||||
LMIC_Compliance.fsmFlags = fsmFlags | LMIC_COMPLIANCE_FSM_REENTERED;
|
||||
return;
|
||||
}
|
||||
|
||||
// record that we're active
|
||||
LMIC_Compliance.fsmFlags = fsmFlags | LMIC_COMPLIANCE_FSM_ACTIVE;
|
||||
} while (0);
|
||||
|
||||
// evaluate and change state
|
||||
fNewState = false;
|
||||
for (;;) {
|
||||
lmic_compliance_fsmstate_t const oldState = LMIC_Compliance.fsmState;
|
||||
lmic_compliance_fsmstate_t newState;
|
||||
|
||||
newState = fsmDispatch(oldState, fNewState);
|
||||
|
||||
if (newState == LMIC_COMPLIANCE_FSMSTATE_NOCHANGE) {
|
||||
lmic_compliance_fsmflags_t const fsmFlags = LMIC_Compliance.fsmFlags;
|
||||
|
||||
if ((fsmFlags & LMIC_COMPLIANCE_FSM_REENTERED) == 0) {
|
||||
// not reentered, no change: get out.
|
||||
LMIC_Compliance.fsmFlags = fsmFlags & ~LMIC_COMPLIANCE_FSM_ACTIVE;
|
||||
return;
|
||||
} else {
|
||||
// reentered. reset reentered flag and keep going.
|
||||
LMIC_Compliance.fsmFlags = fsmFlags & ~LMIC_COMPLIANCE_FSM_REENTERED;
|
||||
fNewState = false;
|
||||
}
|
||||
} else {
|
||||
// state change!
|
||||
LMIC_COMPLIANCE_PRINTF("%s: change state %s(%u) => %s(%u)\n",
|
||||
__func__,
|
||||
LMICcompliance_fsmstate_getName(oldState), (unsigned) oldState,
|
||||
LMICcompliance_fsmstate_getName(newState), (unsigned) newState
|
||||
);
|
||||
fNewState = true;
|
||||
LMIC_Compliance.fsmState = newState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Name: fsmDispatch()
|
||||
|
||||
Function:
|
||||
Dispatch to the appropriate event handler.
|
||||
|
||||
Definition:
|
||||
lmic_compliance_fsmstate_t fsmDispatch(
|
||||
lmic_compliance_fsmstate_t state,
|
||||
bool fEntry
|
||||
);
|
||||
|
||||
Description:
|
||||
This function is called by the evalutator as needed. `state`
|
||||
is set to the current state of the FSM, and `fEntry` is
|
||||
true if and only if this state has just been entered via a
|
||||
transition arrow. (Might be a transition to self.)
|
||||
|
||||
Returns:
|
||||
This function returns LMIC_COMPLIANCE_FSMSTATE_NOCHANGE if
|
||||
the FSM is to remain in this state until an event occurs.
|
||||
Otherwise it returns the new state.
|
||||
|
||||
*/
|
||||
|
||||
static inline lmic_compliance_eventflags_t
|
||||
eventflags_TestAndClear(lmic_compliance_eventflags_t flag) {
|
||||
const lmic_compliance_eventflags_t old = LMIC_Compliance.eventflags;
|
||||
const lmic_compliance_eventflags_t result = old & flag;
|
||||
|
||||
if (result != 0)
|
||||
LMIC_Compliance.eventflags = old ^ result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static lmic_compliance_fsmstate_t
|
||||
fsmDispatch(
|
||||
lmic_compliance_fsmstate_t state,
|
||||
bool fEntry
|
||||
) {
|
||||
lmic_compliance_fsmstate_t newState;
|
||||
|
||||
// currently, this is a stub.
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_NOCHANGE;
|
||||
|
||||
switch (state) {
|
||||
case LMIC_COMPLIANCE_FSMSTATE_INITIAL: {
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_INACTIVE;
|
||||
break;
|
||||
}
|
||||
|
||||
case LMIC_COMPLIANCE_FSMSTATE_INACTIVE: {
|
||||
if (fEntry) {
|
||||
acExitActiveMode();
|
||||
}
|
||||
|
||||
if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_ACTIVATE)) {
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_ACTIVE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LMIC_COMPLIANCE_FSMSTATE_ACTIVE: {
|
||||
if (fEntry) {
|
||||
acEnterActiveMode();
|
||||
acSetTimer(sec2osticks(1));
|
||||
}
|
||||
if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED)) {
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_TESTMODE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LMIC_COMPLIANCE_FSMSTATE_TXBUSY: {
|
||||
if (fEntry) {
|
||||
acSetTimer(sec2osticks(1));
|
||||
}
|
||||
if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED)) {
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_TESTMODE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LMIC_COMPLIANCE_FSMSTATE_TESTMODE: {
|
||||
if (LMIC.opmode & OP_TXDATA) {
|
||||
// go back and wait some more.
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_TXBUSY;
|
||||
}
|
||||
if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_DEACTIVATE)) {
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_INACTIVE;
|
||||
} else if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_ECHO_REQUEST)) {
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_ECHOING;
|
||||
} else {
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_REPORTING;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LMIC_COMPLIANCE_FSMSTATE_ECHOING: {
|
||||
if (fEntry)
|
||||
acSendUplinkBuffer();
|
||||
|
||||
if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE)) {
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_RECOVERY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LMIC_COMPLIANCE_FSMSTATE_REPORTING: {
|
||||
if (fEntry)
|
||||
acSendUplink();
|
||||
|
||||
if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE)) {
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_RECOVERY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LMIC_COMPLIANCE_FSMSTATE_RECOVERY: {
|
||||
if (fEntry) {
|
||||
if (LMIC_Compliance.eventflags & (LMIC_COMPLIANCE_EVENT_DEACTIVATE |
|
||||
LMIC_COMPLIANCE_EVENT_ECHO_REQUEST)) {
|
||||
acSetTimer(sec2osticks(1));
|
||||
} else {
|
||||
acSetTimer(sec2osticks(5));
|
||||
}
|
||||
}
|
||||
|
||||
if (eventflags_TestAndClear(LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED)) {
|
||||
newState = LMIC_COMPLIANCE_FSMSTATE_TESTMODE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
static void acEnterActiveMode(void) {
|
||||
// indicate to the outer world that we're active.
|
||||
LMIC_Compliance.state = LMIC_COMPLIANCE_STATE_ACTIVE;
|
||||
}
|
||||
|
||||
void acSetTimer(ostime_t delay) {
|
||||
os_setTimedCallback(&LMIC_Compliance.timerJob, os_getTime() + delay, timerExpiredCb);
|
||||
}
|
||||
|
||||
static void timerExpiredCb(osjob_t *j) {
|
||||
LMIC_API_PARAMETER(j);
|
||||
LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED;
|
||||
fsmEval();
|
||||
}
|
||||
|
||||
static void lmicEventCb(
|
||||
void *pUserData,
|
||||
ev_t ev
|
||||
) {
|
||||
LMIC_API_PARAMETER(pUserData);
|
||||
|
||||
// pass to user handler
|
||||
if (LMIC_Compliance.saveEvent.pEventCb) {
|
||||
LMIC_Compliance.saveEvent.pEventCb(
|
||||
LMIC_Compliance.saveEvent.pUserData, ev
|
||||
);
|
||||
}
|
||||
|
||||
// if it's a EV_JOINED, or a TXCMOMPLETE, we should tell the FSM.
|
||||
if ((UINT32_C(1) << ev) & (EV_JOINED | EV_TXCOMPLETE)) {
|
||||
fsmEvalDeferred();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void acExitActiveMode(void) {
|
||||
LMIC_Compliance.state = LMIC_COMPLIANCE_STATE_IDLE;
|
||||
os_clearCallback(&LMIC_Compliance.timerJob);
|
||||
LMIC_clrTxData();
|
||||
}
|
||||
|
||||
|
||||
static void acSendUplink(void) {
|
||||
uint8_t payload[2];
|
||||
uint32_t const downlink = LMIC_Compliance.downlinkCount;
|
||||
|
||||
// build the uplink message
|
||||
payload[0] = (uint8_t) (downlink >> 8);
|
||||
payload[1] = (uint8_t) downlink;
|
||||
|
||||
// reset the flags
|
||||
LMIC_Compliance.eventflags &= ~LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE;
|
||||
|
||||
// don't try to send if busy; might be sending echo message.
|
||||
lmic_tx_error_t const eSend =
|
||||
LMIC_sendWithCallback_strict(
|
||||
LORAWAN_PORT_COMPLIANCE,
|
||||
payload, sizeof(payload),
|
||||
/* confirmed? */
|
||||
!! (LMIC_Compliance.fsmFlags & LMIC_COMPLIANCE_FSM_CONFIRM),
|
||||
sendUplinkCompleteCb, NULL
|
||||
);
|
||||
|
||||
if (eSend == LMIC_ERROR_SUCCESS) {
|
||||
// queued successfully
|
||||
LMIC_COMPLIANCE_PRINTF(
|
||||
"lmic_compliance.%s: queued uplink message(%u, %p)\n",
|
||||
__func__,
|
||||
(unsigned) downlink & 0xFFFF,
|
||||
LMIC.client.txMessageCb
|
||||
);
|
||||
} else {
|
||||
// failed to queue; just skip this cycle.
|
||||
LMIC_COMPLIANCE_PRINTF(
|
||||
"lmic_compliance.%s: error(%d) sending uplink message(%u), %u bytes\n",
|
||||
__func__,
|
||||
eSend,
|
||||
(unsigned) downlink & 0xFFFF,
|
||||
(unsigned) sizeof(payload)
|
||||
);
|
||||
LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE;
|
||||
fsmEval();
|
||||
}
|
||||
}
|
||||
|
||||
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_PRINTF("%s(%s)\n", __func__, LMICcompliance_txSuccessToString(fSuccess));
|
||||
fsmEvalDeferred();
|
||||
}
|
||||
|
||||
static void acSendUplinkBuffer(void) {
|
||||
// send uplink data.
|
||||
lmic_tx_error_t const eSend =
|
||||
LMIC_sendWithCallback_strict(
|
||||
LORAWAN_PORT_COMPLIANCE,
|
||||
LMIC_Compliance.uplinkMessage, LMIC_Compliance.uplinkSize,
|
||||
/* confirmed? */ (LMIC_Compliance.fsmFlags & LMIC_COMPLIANCE_FSM_CONFIRM) != 0,
|
||||
sendUplinkCompleteCb,
|
||||
NULL);
|
||||
|
||||
if (eSend == LMIC_ERROR_SUCCESS) {
|
||||
LMIC_COMPLIANCE_PRINTF("%s: queued %u bytes\n", __func__, LMIC_Compliance.uplinkSize);
|
||||
} else {
|
||||
LMIC_COMPLIANCE_PRINTF("%s: uplink %u bytes failed (error %d)\n", __func__, LMIC_Compliance.uplinkSize, eSend);
|
||||
if (eSend == LMIC_ERROR_TX_NOT_FEASIBLE) {
|
||||
// Reverse the increment of the downlink count. Needed for US compliance.
|
||||
if (CFG_region == LMIC_REGION_us915)
|
||||
--LMIC_Compliance.downlinkCount;
|
||||
}
|
||||
LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE;
|
||||
fsmEval();
|
||||
}
|
||||
}
|
||||
|
||||
const char *LMICcompliance_txSuccessToString(int fSuccess) {
|
||||
return fSuccess ? "ok" : "failed";
|
||||
}
|
138
src/lmic/lmic_compliance.h
Normal file
138
src/lmic/lmic_compliance.h
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
|
||||
Module: lmic_compliance.h
|
||||
|
||||
Function:
|
||||
Internal header file for compliance-related work.
|
||||
|
||||
Copyright notice and license info:
|
||||
See LICENSE file accompanying this project.
|
||||
|
||||
Author:
|
||||
Terry Moore, MCCI Corporation March 2019
|
||||
|
||||
Description:
|
||||
This header file allows us to break up the compliance
|
||||
functions into multiple .c files if we wish.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _lmic_compliance_h_ /* prevent multiple includes */
|
||||
#define _lmic_compliance_h_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"{
|
||||
#endif
|
||||
|
||||
#ifndef _lmic_h_
|
||||
# include "lmic.h"
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct lmic_compliance_s lmic_compliance_t;
|
||||
|
||||
// concrete type for the state enumeration for the compliance engine.
|
||||
typedef uint8_t lmic_compliance_state_t;
|
||||
|
||||
enum lmic_compliance_state_e {
|
||||
LMIC_COMPLIANCE_STATE_IDLE = 0, // app state
|
||||
LMIC_COMPLIANCE_STATE_STOPPING = 1, // transitioning back to app
|
||||
LMIC_COMPLIANCE_STATE_ACTIVATING = 2, // transitioning to compliance state
|
||||
LMIC_COMPLIANCE_STATE_ACTIVE = 3, // in compliance state
|
||||
};
|
||||
|
||||
// return true if a state value indicates that the FSM is active.
|
||||
static inline bool
|
||||
lmic_compliance_state_IsActive(lmic_compliance_state_t s) {
|
||||
return s >= LMIC_COMPLIANCE_STATE_ACTIVATING;
|
||||
}
|
||||
|
||||
// events from the outside world to the FSM
|
||||
typedef uint8_t lmic_compliance_eventflags_t;
|
||||
|
||||
enum lmic_compliance_eventflags_e {
|
||||
LMIC_COMPLIANCE_EVENT_ACTIVATE = 1u << 0,
|
||||
LMIC_COMPLIANCE_EVENT_DEACTIVATE = 1u << 1,
|
||||
LMIC_COMPLIANCE_EVENT_TIMER_EXPIRED = 1u << 2,
|
||||
LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE = 1u << 3,
|
||||
LMIC_COMPLIANCE_EVENT_ECHO_REQUEST = 1u << 4,
|
||||
};
|
||||
|
||||
typedef uint8_t lmic_compliance_fsmflags_t;
|
||||
enum lmic_compliance_fsmflags_e {
|
||||
LMIC_COMPLIANCE_FSM_ACTIVE = 1u << 0,
|
||||
LMIC_COMPLIANCE_FSM_REENTERED = 1u << 1,
|
||||
LMIC_COMPLIANCE_FSM_CONFIRM = 1u << 2,
|
||||
};
|
||||
|
||||
typedef uint8_t lmic_compliance_fsmstate_t;
|
||||
enum lmic_compliance_fsmstate_e {
|
||||
LMIC_COMPLIANCE_FSMSTATE_INITIAL = 0,
|
||||
LMIC_COMPLIANCE_FSMSTATE_NOCHANGE = 1,
|
||||
LMIC_COMPLIANCE_FSMSTATE_ACTIVE = 2,
|
||||
LMIC_COMPLIANCE_FSMSTATE_INACTIVE = 3,
|
||||
LMIC_COMPLIANCE_FSMSTATE_TESTMODE = 4, // sending test uplinks
|
||||
LMIC_COMPLIANCE_FSMSTATE_ECHOING = 5,
|
||||
LMIC_COMPLIANCE_FSMSTATE_REPORTING = 6,
|
||||
LMIC_COMPLIANCE_FSMSTATE_RECOVERY = 7,
|
||||
LMIC_COMPLIANCE_FSMSTATE_TXBUSY = 8,
|
||||
};
|
||||
|
||||
#define LMIC_COMPLIANCE_FSMSTATE__NAMES \
|
||||
"INITIAL", "NOCHANGE", "ACTIVE", "INACTIVE", "TESTMODE", \
|
||||
"ECHOING", "REPORTING", "RECOVERY", "TXBUSY"
|
||||
|
||||
typedef struct lmic_compliance_eventcb_s lmic_compliance_eventcb_t;
|
||||
struct lmic_compliance_eventcb_s {
|
||||
// save the user's event CB while active.
|
||||
lmic_event_cb_t *pEventCb;
|
||||
// save the user's event data while active.
|
||||
void *pUserData;
|
||||
};
|
||||
|
||||
// structure for saving band settings during test
|
||||
typedef struct lmic_compliance_band_s lmic_compliance_band_t;
|
||||
struct lmic_compliance_band_s {
|
||||
u2_t txcap; // saved 1/duty cycle
|
||||
};
|
||||
|
||||
// the state of the compliance engine.
|
||||
struct lmic_compliance_s {
|
||||
// uint64
|
||||
// uintptr
|
||||
osjob_t timerJob; // the job for driving uplinks
|
||||
osjob_t fsmJob; // job for reevaluating the FSM.
|
||||
lmic_compliance_eventcb_t saveEvent; // the user's event handler.
|
||||
|
||||
// uint32
|
||||
|
||||
// uint16
|
||||
#if CFG_LMIC_EU_like
|
||||
lmic_compliance_band_t saveBands[MAX_BANDS];
|
||||
#endif // CFG_LMIC_EU_like
|
||||
|
||||
// we are required to maintain a downlink count
|
||||
// that is reset on join/test entry and incremented for
|
||||
// each valid test message.
|
||||
uint16_t downlinkCount;
|
||||
|
||||
// uint8
|
||||
|
||||
lmic_compliance_state_t state; // current state of compliance engine.
|
||||
lmic_compliance_eventflags_t eventflags; // incoming events.
|
||||
lmic_compliance_fsmflags_t fsmFlags; // FSM operational flags
|
||||
lmic_compliance_fsmstate_t fsmState; // FSM current state
|
||||
|
||||
uint8_t uplinkSize;
|
||||
uint8_t uplinkMessage[MAX_LEN_PAYLOAD];
|
||||
};
|
||||
|
||||
extern lmic_compliance_t LMIC_Compliance;
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif /* _lmic_compliance_h_ */
|
177
src/lmic/lmic_config_preconditions.h
Executable file → Normal file
177
src/lmic/lmic_config_preconditions.h
Executable file → Normal file
@ -67,10 +67,10 @@ Revision history:
|
||||
#define LMIC_REGION_us915 2
|
||||
#define LMIC_REGION_cn783 3
|
||||
#define LMIC_REGION_eu433 4
|
||||
#define LMIC_REGION_au921 5
|
||||
#define LMIC_REGION_au915 5
|
||||
#define LMIC_REGION_cn490 6
|
||||
#define LMIC_REGION_as923 7
|
||||
#define LMIC_REGION_kr921 8
|
||||
#define LMIC_REGION_kr920 8
|
||||
#define LMIC_REGION_in866 9
|
||||
|
||||
// Some regions have country-specific overrides. For generality, we specify
|
||||
@ -93,6 +93,16 @@ Revision history:
|
||||
# include CFG_TEXT_1(ARDUINO_LMIC_PROJECT_CONFIG_H)
|
||||
#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
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
@ -101,37 +111,13 @@ Revision history:
|
||||
(1 << LMIC_REGION_us915) | \
|
||||
/* (1 << LMIC_REGION_cn783) | */ \
|
||||
/* (1 << LMIC_REGION_eu433) | */ \
|
||||
(1 << LMIC_REGION_au921) | \
|
||||
(1 << LMIC_REGION_au915) | \
|
||||
/* (1 << LMIC_REGION_cn490) | */ \
|
||||
(1 << LMIC_REGION_as923) | \
|
||||
/* (1 << LMIC_REGION_kr921) | */ \
|
||||
(1 << LMIC_REGION_kr920) | \
|
||||
(1 << LMIC_REGION_in866) | \
|
||||
0)
|
||||
|
||||
//
|
||||
// Our input is a -D of one of CFG_eu868, CFG_us915, CFG_as923, CFG_au915, CFG_in866
|
||||
// More will be added in the the future. So at this point we create CFG_region with
|
||||
// following values. These are in order of the sections in the manual. Not all of the
|
||||
// below are supported yet.
|
||||
//
|
||||
// CFG_as923jp is treated as a special case of CFG_as923, so it's not included in
|
||||
// the below.
|
||||
//
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
//
|
||||
# define CFG_LMIC_REGION_MASK \
|
||||
((defined(CFG_eu868) << LMIC_REGION_eu868) | \
|
||||
(defined(CFG_us915) << LMIC_REGION_us915) | \
|
||||
(defined(CFG_cn783) << LMIC_REGION_cn783) | \
|
||||
(defined(CFG_eu433) << LMIC_REGION_eu433) | \
|
||||
(defined(CFG_au921) << LMIC_REGION_au921) | \
|
||||
(defined(CFG_cn490) << LMIC_REGION_cn490) | \
|
||||
(defined(CFG_as923) << LMIC_REGION_as923) | \
|
||||
(defined(CFG_kr921) << LMIC_REGION_kr921) | \
|
||||
(defined(CFG_in866) << LMIC_REGION_in866) | \
|
||||
0)
|
||||
|
||||
// the selected region.
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
@ -143,8 +129,8 @@ Revision history:
|
||||
# define CFG_region LMIC_REGION_cn783
|
||||
#elif defined(CFG_eu433)
|
||||
# define CFG_region LMIC_REGION_eu433
|
||||
#elif defined(CFG_au921)
|
||||
# define CFG_region LMIC_REGION_au921
|
||||
#elif defined(CFG_au915)
|
||||
# define CFG_region LMIC_REGION_au915
|
||||
#elif defined(CFG_cn490)
|
||||
# define CFG_region LMIC_REGION_cn490
|
||||
#elif defined(CFG_as923jp)
|
||||
@ -153,46 +139,131 @@ Revision history:
|
||||
# define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP
|
||||
#elif defined(CFG_as923)
|
||||
# define CFG_region LMIC_REGION_as923
|
||||
#elif defined(CFG_kr921)
|
||||
# define CFG_region LMIC_REGION_kr921
|
||||
#elif defined(CFG_kr920)
|
||||
# define CFG_region LMIC_REGION_kr920
|
||||
#elif defined(CFG_in866)
|
||||
# define CFG_region LMIC_REGION_in866
|
||||
#else
|
||||
# define CFG_region 0
|
||||
#endif
|
||||
|
||||
// a bitmask of EU-like regions -- these are regions which have up to 16
|
||||
// channels indidually programmable via downloink.
|
||||
//
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
// LMIC_CFG_*_ENA -- either 1 or 0 based on whether that region
|
||||
// is enabled. Note: these must be after the code that special-cases
|
||||
// CFG_as923jp.
|
||||
#if defined(CFG_eu868)
|
||||
# define LMIC_CFG_eu868_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_eu868_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_us915)
|
||||
# define LMIC_CFG_us915_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_us915_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_cn783)
|
||||
# define LMIC_CFG_cn783_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_cn783_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_eu433)
|
||||
# define LMIC_CFG_eu433_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_eu433_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_au915)
|
||||
# define LMIC_CFG_au915_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_au915_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_cn490)
|
||||
# define LMIC_CFG_cn490_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_cn490_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_as923)
|
||||
# define LMIC_CFG_as923_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_as923_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_kr920)
|
||||
# define LMIC_CFG_kr920_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_kr920_ENA 0
|
||||
#endif
|
||||
|
||||
#if defined(CFG_in866)
|
||||
# define LMIC_CFG_in866_ENA 1
|
||||
#else
|
||||
# define LMIC_CFG_in866_ENA 0
|
||||
#endif
|
||||
|
||||
/// \brief Bitmask of configured regions
|
||||
///
|
||||
/// Our input is a -D of one of CFG_eu868, CFG_us915, CFG_as923, CFG_au915, CFG_in866
|
||||
/// More will be added in the the future. So at this point we create CFG_region with
|
||||
/// following values. These are in order of the sections in the manual. Not all of the
|
||||
/// below are supported yet.
|
||||
///
|
||||
/// CFG_as923jp is treated as a special case of CFG_as923, so it's not included in
|
||||
/// the below.
|
||||
///
|
||||
/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
/// user-editable.
|
||||
///
|
||||
# define CFG_LMIC_REGION_MASK \
|
||||
((LMIC_CFG_eu868_ENA << LMIC_REGION_eu868) | \
|
||||
(LMIC_CFG_us915_ENA << LMIC_REGION_us915) | \
|
||||
(LMIC_CFG_cn783_ENA << LMIC_REGION_cn783) | \
|
||||
(LMIC_CFG_eu433_ENA << LMIC_REGION_eu433) | \
|
||||
(LMIC_CFG_au915_ENA << LMIC_REGION_au915) | \
|
||||
(LMIC_CFG_cn490_ENA << LMIC_REGION_cn490) | \
|
||||
(LMIC_CFG_as923_ENA << LMIC_REGION_as923) | \
|
||||
(LMIC_CFG_kr920_ENA << LMIC_REGION_kr920) | \
|
||||
(LMIC_CFG_in866_ENA << LMIC_REGION_in866) | \
|
||||
0)
|
||||
|
||||
/// \brief a bitmask of EU-like regions
|
||||
///
|
||||
/// EU-like regions have up to 16 channels individually programmable via downlink.
|
||||
///
|
||||
/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
/// user-editable.
|
||||
#define CFG_LMIC_EU_like_MASK ( \
|
||||
(1 << LMIC_REGION_eu868) | \
|
||||
/* (1 << LMIC_REGION_us915) | */ \
|
||||
(1 << LMIC_REGION_cn783) | \
|
||||
(1 << LMIC_REGION_eu433) | \
|
||||
/* (1 << LMIC_REGION_au921) | */ \
|
||||
/* (1 << LMIC_REGION_au915) | */ \
|
||||
/* (1 << LMIC_REGION_cn490) | */ \
|
||||
(1 << LMIC_REGION_as923) | \
|
||||
(1 << LMIC_REGION_kr921) | \
|
||||
(1 << LMIC_REGION_kr920) | \
|
||||
(1 << LMIC_REGION_in866) | \
|
||||
0)
|
||||
|
||||
// a bitmask of` US-like regions -- these are regions with 64 fixed 125 kHz channels
|
||||
// overlaid by 8 500 kHz channels. The channel frequencies can't be changed, but
|
||||
// subsets of channels can be selected via masks.
|
||||
//
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
/// \brief bitmask of` US-like regions
|
||||
///
|
||||
/// US-like regions have 64 fixed 125-kHz channels overlaid by 8 500-kHz
|
||||
/// channels. The channel frequencies can't be changed, but
|
||||
/// subsets of channels can be selected via masks.
|
||||
///
|
||||
/// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
/// user-editable.
|
||||
#define CFG_LMIC_US_like_MASK ( \
|
||||
/* (1 << LMIC_REGION_eu868) | */ \
|
||||
(1 << LMIC_REGION_us915) | \
|
||||
/* (1 << LMIC_REGION_cn783) | */ \
|
||||
/* (1 << LMIC_REGION_eu433) | */ \
|
||||
(1 << LMIC_REGION_au921) | \
|
||||
(1 << LMIC_REGION_au915) | \
|
||||
/* (1 << LMIC_REGION_cn490) | */ \
|
||||
/* (1 << LMIC_REGION_as923) | */ \
|
||||
/* (1 << LMIC_REGION_kr921) | */ \
|
||||
/* (1 << LMIC_REGION_kr920) | */ \
|
||||
/* (1 << LMIC_REGION_in866) | */ \
|
||||
0)
|
||||
|
||||
@ -201,7 +272,19 @@ Revision history:
|
||||
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
|
||||
// user-editable.
|
||||
//
|
||||
|
||||
/// \brief true if configured region is EU-like, false otherwise.
|
||||
#define CFG_LMIC_EU_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_EU_like_MASK))
|
||||
/// \brief true if configured region is US-like, false otherwise.
|
||||
#define CFG_LMIC_US_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_US_like_MASK))
|
||||
|
||||
//
|
||||
// The supported LMIC LoRaWAN spec versions. These need to be numerically ordered,
|
||||
// so that we can (for example) compare
|
||||
//
|
||||
// LMIC_LORAWAN_SPEC_VERSION < LMIC_LORAWAN_SPEC_VERSION_1_0_3.
|
||||
//
|
||||
#define LMIC_LORAWAN_SPEC_VERSION_1_0_2 0x01000200u /// Refers to LoRaWAN spec 1.0.2
|
||||
#define LMIC_LORAWAN_SPEC_VERSION_1_0_3 0x01000300u /// Refers to LoRaWAN spec 1.0.3
|
||||
|
||||
#endif /* _LMIC_CONFIG_PRECONDITIONS_H_ */
|
||||
|
34
src/lmic/lmic_env.h
Executable file → Normal file
34
src/lmic/lmic_env.h
Executable file → Normal file
@ -214,4 +214,38 @@ Returns:
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
Macro: LMIC_DECLARE_FUNCTION_WEAK()
|
||||
|
||||
Function:
|
||||
Declare an external function as a weak reference.
|
||||
|
||||
Definition:
|
||||
#define LMIC_DECLARE_FUNCTION_WEAK(ReturnType, FunctionName, Params) ...
|
||||
|
||||
Description:
|
||||
This macro generates a weak reference to the specified function.
|
||||
|
||||
Example:
|
||||
LMIC_DECLARE_FUNCTION_WEAK(void, onEvent, (ev_t e));
|
||||
|
||||
This saya that onEvent is a weak external reference. When calling
|
||||
onEvent, you must always first check whether it's supplied:
|
||||
|
||||
if (onEvent != NULL)
|
||||
onEvent(e);
|
||||
|
||||
Returns:
|
||||
This macro expands to a declaration, without a trailing semicolon.
|
||||
|
||||
Notes:
|
||||
This form allows for compilers that use _Pragma(weak, name) instead
|
||||
of inline attributes.
|
||||
|
||||
*/
|
||||
|
||||
#define LMIC_DECLARE_FUNCTION_WEAK(a_ReturnType, a_FunctionName, a_Params) \
|
||||
a_ReturnType __attribute__((__weak__)) a_FunctionName a_Params
|
||||
|
||||
#endif /* _lmic_env_h_ */
|
262
src/lmic/lmic_eu868.c
Executable file → Normal file
262
src/lmic/lmic_eu868.c
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -49,21 +49,36 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS
|
||||
};
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = { 64,64,64,123 };
|
||||
bit_t
|
||||
LMICeu868_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = {
|
||||
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 250+5, 250+5
|
||||
};
|
||||
|
||||
uint8_t LMICeu868_maxFrameLen(uint8_t dr) {
|
||||
if (dr < LENOF_TABLE(maxFrameLens))
|
||||
return TABLE_GET_U1(maxFrameLens, dr);
|
||||
else
|
||||
return 0xFF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static CONST_TABLE(s1_t, TXPOWLEVELS)[] = {
|
||||
20, 14, 11, 8, 5, 2, 0,0, 0,0,0,0, 0,0,0,0
|
||||
16, 14, 12, 10, 8, 6, 4, 2
|
||||
};
|
||||
|
||||
int8_t LMICeu868_pow2dBm(uint8_t mcmd_ladr_p1) {
|
||||
return TABLE_GET_S1(TXPOWLEVELS, (mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT);
|
||||
uint8_t const pindex = (mcmd_ladr_p1&MCMD_LinkADRReq_POW_MASK)>>MCMD_LinkADRReq_POW_SHIFT;
|
||||
if (pindex < LENOF_TABLE(TXPOWLEVELS)) {
|
||||
return TABLE_GET_S1(TXPOWLEVELS, pindex);
|
||||
} else {
|
||||
return -128;
|
||||
}
|
||||
}
|
||||
|
||||
// only used in this module, but used by variant macro dr2hsym().
|
||||
@ -75,7 +90,7 @@ static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = {
|
||||
us2osticksRound(128 << 3), // DR_SF8
|
||||
us2osticksRound(128 << 2), // DR_SF7
|
||||
us2osticksRound(128 << 1), // DR_SF7B
|
||||
us2osticksRound(80) // FSK -- not used (time for 1/2 byte)
|
||||
us2osticksRound(80) // FSK -- time for 1/2 byte (unused by LMIC)
|
||||
};
|
||||
|
||||
ostime_t LMICeu868_dr2hsym(uint8_t dr) {
|
||||
@ -93,6 +108,9 @@ static CONST_TABLE(u4_t, iniChannelFreq)[6] = {
|
||||
|
||||
void LMICeu868_initDefaultChannels(bit_t join) {
|
||||
os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq));
|
||||
#if !defined(DISABLE_MCMD_DlChannelReq)
|
||||
os_clearMem(&LMIC.channelDlFreq, sizeof(LMIC.channelDlFreq));
|
||||
#endif // !DISABLE_MCMD_DlChannelReq
|
||||
os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap));
|
||||
os_clearMem(&LMIC.bands, sizeof(LMIC.bands));
|
||||
|
||||
@ -104,18 +122,9 @@ void LMICeu868_initDefaultChannels(bit_t join) {
|
||||
LMIC.channelDrMap[fu] = DR_RANGE_MAP(EU868_DR_SF12, EU868_DR_SF7);
|
||||
}
|
||||
|
||||
LMIC.bands[BAND_MILLI].txcap = 1000; // 0.1%
|
||||
LMIC.bands[BAND_MILLI].txpow = 14;
|
||||
LMIC.bands[BAND_MILLI].lastchnl = os_getRndU1() % MAX_CHANNELS;
|
||||
LMIC.bands[BAND_CENTI].txcap = 100; // 1%
|
||||
LMIC.bands[BAND_CENTI].txpow = 14;
|
||||
LMIC.bands[BAND_CENTI].lastchnl = os_getRndU1() % MAX_CHANNELS;
|
||||
LMIC.bands[BAND_DECI].txcap = 10; // 10%
|
||||
LMIC.bands[BAND_DECI].txpow = 27;
|
||||
LMIC.bands[BAND_DECI].lastchnl = os_getRndU1() % MAX_CHANNELS;
|
||||
LMIC.bands[BAND_MILLI].avail =
|
||||
LMIC.bands[BAND_CENTI].avail =
|
||||
LMIC.bands[BAND_DECI].avail = os_getTime();
|
||||
(void) LMIC_setupBand(BAND_MILLI, 14 /* dBm */, 1000 /* 0.1% */);
|
||||
(void) LMIC_setupBand(BAND_CENTI, 14 /* dBm */, 100 /* 1% */);
|
||||
(void) LMIC_setupBand(BAND_DECI, 27 /* dBm */, 10 /* 10% */);
|
||||
}
|
||||
|
||||
bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
|
||||
@ -129,26 +138,70 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// this table is from highest to lowest
|
||||
static CONST_TABLE(u4_t, bandAssignments)[] = {
|
||||
870000000 /* .. and above */ | BAND_MILLI,
|
||||
869700000 /* .. 869700000 */ | BAND_CENTI,
|
||||
869650000 /* .. 869700000 */ | BAND_MILLI,
|
||||
869400000 /* .. 869650000 */ | BAND_DECI,
|
||||
868600000 /* .. 869640000 */ | BAND_MILLI,
|
||||
865000000 /* .. 868400000 */ | BAND_CENTI,
|
||||
};
|
||||
|
||||
///
|
||||
/// \brief query number of default channels.
|
||||
///
|
||||
u1_t LMIC_queryNumDefaultChannels() {
|
||||
return NUM_DEFAULT_CHANNELS;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief LMIC_setupChannel for EU 868
|
||||
///
|
||||
/// \note according to LoRaWAN 1.0.3 section 5.6, "the acceptable range
|
||||
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
|
||||
/// This routine is used internally for MAC commands, so we enforce
|
||||
/// this for the extenal API as well.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
// zero the band bits in freq, just in case.
|
||||
freq &= ~3;
|
||||
|
||||
if (chidx < NUM_DEFAULT_CHANNELS) {
|
||||
// can't do anything to a default channel.
|
||||
return 0;
|
||||
}
|
||||
bit_t fEnable = (freq != 0);
|
||||
if (chidx >= MAX_CHANNELS)
|
||||
return 0;
|
||||
|
||||
if (band == -1) {
|
||||
if (freq >= 869400000 && freq <= 869650000)
|
||||
freq |= BAND_DECI; // 10% 27dBm
|
||||
else if ((freq >= 868000000 && freq <= 868600000) ||
|
||||
(freq >= 869700000 && freq <= 870000000))
|
||||
freq |= BAND_CENTI; // 1% 14dBm
|
||||
else
|
||||
freq |= BAND_MILLI; // 0.1% 14dBm
|
||||
for (u1_t i = 0; i < LENOF_TABLE(bandAssignments); ++i) {
|
||||
const u4_t thisFreqBand = TABLE_GET_U4(bandAssignments, i);
|
||||
const u4_t thisFreq = thisFreqBand & ~3;
|
||||
if (freq >= thisFreq) {
|
||||
band = ((u1_t)thisFreqBand & 3);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
if (band > BAND_AUX) return 0;
|
||||
freq = (freq&~3) | band;
|
||||
}
|
||||
|
||||
// if we didn't identify a frequency, it's millis.
|
||||
if (band == -1) {
|
||||
band = BAND_MILLI;
|
||||
}
|
||||
}
|
||||
|
||||
if ((u1_t)band > BAND_AUX)
|
||||
return 0;
|
||||
|
||||
freq |= band;
|
||||
|
||||
LMIC.channelFreq[chidx] = freq;
|
||||
// TODO(tmm@mcci.com): don't use US SF directly, use something from the LMIC context or a static const
|
||||
LMIC.channelDrMap[chidx] = drmap == 0 ? DR_RANGE_MAP(EU868_DR_SF12, EU868_DR_SF7) : drmap;
|
||||
if (fEnable)
|
||||
LMIC.channelMap |= 1 << chidx; // enabled right away
|
||||
else
|
||||
LMIC.channelMap &= ~(1 << chidx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -170,32 +223,111 @@ ostime_t LMICeu868_nextJoinTime(ostime_t time) {
|
||||
return time;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief change the TX channel given the desired tx time.
|
||||
///
|
||||
/// \param [in] now is the time at which we want to transmit. In fact, it's always
|
||||
/// the current time.
|
||||
///
|
||||
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
|
||||
/// selected channel.
|
||||
///
|
||||
/// \details
|
||||
/// We scan all the channels, creating a mask of all enabled channels that are
|
||||
/// feasible at the earliest possible time. We then randomly choose one from
|
||||
/// that, updating the shuffle mask.
|
||||
///
|
||||
/// One sublety is that we have to cope with an artifact of the shuffler.
|
||||
/// It will zero out bits for candidates that are real candidates, but
|
||||
/// not in the time window, and not consider them as early as it should.
|
||||
/// So we keep a mask of all feasible channels, and make sure that they
|
||||
/// remain set in the shuffle mask if appropriate.
|
||||
///
|
||||
ostime_t LMICeu868_nextTx(ostime_t now) {
|
||||
u1_t bmap = 0xF;
|
||||
do {
|
||||
ostime_t mintime = now + /*8h*/sec2osticks(28800);
|
||||
u1_t band = 0;
|
||||
for (u1_t bi = 0; bi<4; bi++) {
|
||||
if ((bmap & (1 << bi)) && mintime - LMIC.bands[bi].avail > 0)
|
||||
mintime = LMIC.bands[band = bi].avail;
|
||||
u2_t availMap;
|
||||
u2_t feasibleMap;
|
||||
u1_t bandMap;
|
||||
|
||||
// set mintime to the earliest time of all enabled channels
|
||||
// (can't just look at bands); and for a given channel, we
|
||||
// can't tell if we're ready till we've checked all possible
|
||||
// avail times.
|
||||
bandMap = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
u2_t chnlBit = 1 << chnl;
|
||||
|
||||
// none at any higher numbers?
|
||||
if (LMIC.channelMap < chnlBit)
|
||||
break;
|
||||
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & chnlBit) == 0)
|
||||
continue;
|
||||
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
|
||||
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
|
||||
u1_t const thisBandBit = 1 << band;
|
||||
// already considered?
|
||||
if ((bandMap & thisBandBit) != 0)
|
||||
continue;
|
||||
|
||||
// consider this band.
|
||||
bandMap |= thisBandBit;
|
||||
|
||||
// enabled, not considered, feasible: adjust the min time.
|
||||
if ((s4_t)(mintime - LMIC.bands[band].avail) > 0)
|
||||
mintime = LMIC.bands[band].avail;
|
||||
}
|
||||
|
||||
// make a mask of candidates available for use
|
||||
availMap = 0;
|
||||
feasibleMap = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
u2_t chnlBit = 1 << chnl;
|
||||
|
||||
// none at any higher numbers?
|
||||
if (LMIC.channelMap < chnlBit)
|
||||
break;
|
||||
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & chnlBit) == 0)
|
||||
continue;
|
||||
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
|
||||
// This channel is feasible. But might not be available.
|
||||
feasibleMap |= chnlBit;
|
||||
|
||||
// not available yet?
|
||||
u1_t const band = LMIC.channelFreq[chnl] & 0x3;
|
||||
if ((s4_t)(LMIC.bands[band].avail - mintime) > 0)
|
||||
continue;
|
||||
|
||||
// ok: this is a candidate.
|
||||
availMap |= chnlBit;
|
||||
}
|
||||
|
||||
// find the next available chennel.
|
||||
u2_t saveShuffleMap = LMIC.channelShuffleMap;
|
||||
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availMap, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
|
||||
|
||||
// restore bits in the shuffleMap that were on, but might have reset
|
||||
// if availMap was used to refresh shuffleMap. These are channels that
|
||||
// are feasble but not yet candidates due to band saturation
|
||||
LMIC.channelShuffleMap |= saveShuffleMap & feasibleMap & ~availMap;
|
||||
|
||||
if (candidateCh >= 0) {
|
||||
// update the channel; otherwise we'll just use the
|
||||
// most recent one.
|
||||
LMIC.txChnl = candidateCh;
|
||||
}
|
||||
// Find next channel in given band
|
||||
u1_t chnl = LMIC.bands[band].lastchnl;
|
||||
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) {
|
||||
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
|
||||
chnl -= MAX_CHANNELS;
|
||||
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
|
||||
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
|
||||
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
|
||||
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
|
||||
return mintime;
|
||||
}
|
||||
}
|
||||
if ((bmap &= ~(1 << band)) == 0) {
|
||||
// No feasible channel found!
|
||||
return mintime;
|
||||
}
|
||||
} while (1);
|
||||
}
|
||||
|
||||
|
||||
@ -213,12 +345,28 @@ ostime_t LMICeu868_nextJoinState(void) {
|
||||
}
|
||||
#endif // !DISABLE_JOIN
|
||||
|
||||
// txDone handling for FSK.
|
||||
void
|
||||
LMICeu868_txDoneFSK(ostime_t delay, osjobcb_t func) {
|
||||
LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160);
|
||||
LMIC.rxsyms = RXLEN_FSK;
|
||||
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
|
||||
// set the Rx1 dndr, rps.
|
||||
void LMICeu868_setRx1Params(void) {
|
||||
u1_t const txdr = LMIC.dndr;
|
||||
s1_t drOffset;
|
||||
s1_t candidateDr;
|
||||
|
||||
LMICeulike_setRx1Freq();
|
||||
|
||||
if ( LMIC.rx1DrOffset <= 5)
|
||||
drOffset = (s1_t) LMIC.rx1DrOffset;
|
||||
else
|
||||
// make a reasonable assumption for unspecified value.
|
||||
drOffset = 5;
|
||||
|
||||
candidateDr = (s1_t) txdr - drOffset;
|
||||
if (candidateDr < LORAWAN_DR0)
|
||||
candidateDr = 0;
|
||||
else if (candidateDr > LORAWAN_DR7)
|
||||
candidateDr = LORAWAN_DR7;
|
||||
|
||||
LMIC.dndr = (u1_t) candidateDr;
|
||||
LMIC.rps = dndr2rps(LMIC.dndr);
|
||||
}
|
||||
|
||||
void
|
||||
|
189
src/lmic/lmic_eu_like.c
Executable file → Normal file
189
src/lmic/lmic_eu_like.c
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -32,35 +32,95 @@
|
||||
|
||||
#if CFG_LMIC_EU_like
|
||||
|
||||
void LMIC_enableSubBand(u1_t band) {
|
||||
bit_t LMIC_enableSubBand(u1_t band) {
|
||||
LMIC_API_PARAMETER(band);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void LMIC_disableSubBand(u1_t band) {
|
||||
bit_t LMIC_disableSubBand(u1_t band) {
|
||||
LMIC_API_PARAMETER(band);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void LMIC_disableChannel(u1_t channel) {
|
||||
bit_t LMIC_disableChannel(u1_t channel) {
|
||||
u2_t old_chmap = LMIC.channelMap;
|
||||
LMIC.channelFreq[channel] = 0;
|
||||
LMIC.channelDrMap[channel] = 0;
|
||||
LMIC.channelMap &= ~(1 << channel);
|
||||
LMIC.channelMap = old_chmap & ~(1 << channel);
|
||||
return LMIC.channelMap != old_chmap;
|
||||
}
|
||||
|
||||
// this is a no-op provided for compatibilty
|
||||
void LMIC_enableChannel(u1_t channel) {
|
||||
bit_t LMIC_enableChannel(u1_t channel) {
|
||||
LMIC_API_PARAMETER(channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
u1_t LMICeulike_mapChannels(u1_t chpage, u2_t chmap) {
|
||||
// Bad page, disable all channel, enable non-existent
|
||||
if (chpage != 0 || chmap == 0 || (chmap & ~LMIC.channelMap) != 0)
|
||||
return 0; // illegal input
|
||||
// check whether a map operation will work.
|
||||
// chpage is 0 or 6; 6 turns all on; 0 selects channels 0..15 via mask.
|
||||
// The spec is unclear as to whether we should veto a channel mask that enables
|
||||
// a channel that hasn't been configured; we veto it.
|
||||
bit_t LMICeulike_canMapChannels(u1_t chpage, u2_t chmap) {
|
||||
switch (chpage) {
|
||||
case MCMD_LinkADRReq_ChMaskCntl_EULIKE_DIRECT:
|
||||
// we don't allow any channel to be turned on if its frequency is zero.
|
||||
for (u1_t chnl = 0; chnl<MAX_CHANNELS; chnl++) {
|
||||
if ((chmap & (1 << chnl)) != 0 && LMIC.channelFreq[chnl] == 0)
|
||||
chmap &= ~(1 << chnl); // ignore - channel is not defined
|
||||
if ((chmap & (1 << chnl)) != 0 && (LMIC.channelFreq[chnl]&~3) == 0)
|
||||
return 0; // fail - channel is not defined
|
||||
}
|
||||
LMIC.channelMap = chmap;
|
||||
return 1;
|
||||
|
||||
case MCMD_LinkADRReq_ChMaskCntl_EULIKE_ALL_ON:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// assumes that LMICeulike_canMapChannels passed. Return true if this would
|
||||
// be a valid final configuration.
|
||||
// chpage is 0 or 0x60; 0x60 turns all on; 0 selects channels 0..15 via mask.
|
||||
// Assumes canMapChannels has already approved this change.
|
||||
bit_t LMICeulike_mapChannels(u1_t chpage, u2_t chmap) {
|
||||
switch (chpage) {
|
||||
case MCMD_LinkADRReq_ChMaskCntl_EULIKE_DIRECT:
|
||||
LMIC.channelMap = chmap;
|
||||
break;
|
||||
|
||||
case MCMD_LinkADRReq_ChMaskCntl_EULIKE_ALL_ON: {
|
||||
u2_t new_chmap = 0;
|
||||
for (u1_t chnl = 0; chnl<MAX_CHANNELS; chnl++) {
|
||||
if ((LMIC.channelFreq[chnl]&~3) != 0) {
|
||||
new_chmap |= (1 << chnl);
|
||||
}
|
||||
}
|
||||
LMIC.channelMap = new_chmap;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// do nothing.
|
||||
break;
|
||||
}
|
||||
return LMIC.channelMap != 0;
|
||||
}
|
||||
|
||||
bit_t LMICeulike_isDataRateFeasible(dr_t dr) {
|
||||
// if the region uses TxpParam, then someone
|
||||
// could have changed TxDwell, which makes some
|
||||
// otherwise-legal DRs infeasible.
|
||||
#if LMIC_ENABLE_TxParamSetupReq
|
||||
if (LMICbandplan_maxFrameLen(dr) == 0) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
|
||||
(LMIC.channelDrMap[chnl] & (1 << dr)) != 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if !defined(DISABLE_JOIN)
|
||||
@ -68,7 +128,9 @@ void LMICeulike_initJoinLoop(uint8_t nDefaultChannels, s1_t adrTxPow) {
|
||||
#if CFG_TxContinuousMode
|
||||
LMIC.txChnl = 0
|
||||
#else
|
||||
LMIC.txChnl = os_getRndU1() % nDefaultChannels;
|
||||
uint16_t enableMap = (1 << nDefaultChannels) - 1;
|
||||
LMIC.channelShuffleMap = enableMap;
|
||||
LMIC.txChnl = LMIC_findNextChannel(&LMIC.channelShuffleMap, &enableMap, 1, -1);
|
||||
#endif
|
||||
LMIC.adrTxPow = adrTxPow;
|
||||
// TODO(tmm@mcci.com) don't use EU directly, use a table. That
|
||||
@ -106,35 +168,41 @@ void LMICeulike_updateTx(ostime_t txbeg) {
|
||||
//
|
||||
ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
|
||||
u1_t failed = 0;
|
||||
u2_t enableMap = (1 << nDefaultChannels) - 1;
|
||||
|
||||
// Try each default channel with same DR
|
||||
// If all fail try next lower datarate
|
||||
if (++LMIC.txChnl == /* NUM_DEFAULT_CHANNELS */ nDefaultChannels)
|
||||
LMIC.txChnl = 0;
|
||||
if ((++LMIC.txCnt % nDefaultChannels) == 0) {
|
||||
if (LMIC.channelShuffleMap == 0) {
|
||||
// Lower DR every nth try (having all default channels with same DR)
|
||||
//
|
||||
// TODO(tmm@mcci.com) add new DR_REGIN_JOIN_MIN instead of LORAWAN_DR0;
|
||||
// TODO(tmm@mcci.com) add new DR_REGION_JOIN_MIN instead of LORAWAN_DR0;
|
||||
// then we can eliminate the LMIC_REGION_as923 below because we'll set
|
||||
// the failed flag here. This will cause the outer caller to take the
|
||||
// appropriate join path. Or add new LMICeulike_GetLowestJoinDR()
|
||||
//
|
||||
if (LMIC.datarate == LORAWAN_DR0)
|
||||
failed = 1; // we have tried all DR - signal EV_JOIN_FAILED
|
||||
else
|
||||
{
|
||||
// TODO(tmm@mcci.com) - see above; please remove regional dependency from this file.
|
||||
#if CFG_region != LMIC_REGION_as923
|
||||
LMICcore_setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
|
||||
#else
|
||||
#if CFG_region == LMIC_REGION_as923
|
||||
// in the join of AS923 v1.1 or older, only DR2 is used.
|
||||
// no need to change the DR.
|
||||
LMIC.datarate = AS923_DR_SF10;
|
||||
failed = 1;
|
||||
#else
|
||||
if (LMIC.datarate == LORAWAN_DR0) {
|
||||
failed = 1; // we have tried all DR - signal EV_JOIN_FAILED
|
||||
} else {
|
||||
LMICcore_setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// Clear NEXTCHNL because join state engine controls channel hopping
|
||||
|
||||
// find new channel, avoiding repeats.
|
||||
int newCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &enableMap, 1, LMIC.txChnl);
|
||||
if (newCh >= 0)
|
||||
LMIC.txChnl = newCh;
|
||||
|
||||
// Clear OP_NEXTCHNL because join state engine controls channel hopping
|
||||
LMIC.opmode &= ~OP_NEXTCHNL;
|
||||
|
||||
// Move txend to randomize synchronized concurrent joins.
|
||||
// Duty cycle is based on txend.
|
||||
ostime_t const time = LMICbandplan_nextJoinTime(os_getTime());
|
||||
@ -159,4 +227,71 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
|
||||
}
|
||||
#endif // !DISABLE_JOIN
|
||||
|
||||
#if !defined(DISABLE_JOIN)
|
||||
void LMICeulike_processJoinAcceptCFList(void) {
|
||||
if ( LMICbandplan_hasJoinCFlist() &&
|
||||
LMIC.frame[OFF_CFLIST + 15] == LORAWAN_JoinAccept_CFListType_FREQUENCIES) {
|
||||
u1_t dlen;
|
||||
u1_t nDefault = LMIC_queryNumDefaultChannels();
|
||||
|
||||
dlen = OFF_CFLIST;
|
||||
for( u1_t chidx = nDefault; chidx < nDefault + 5; chidx++, dlen+=3 ) {
|
||||
u4_t freq = LMICbandplan_convFreq(&LMIC.frame[dlen]);
|
||||
if( freq ) {
|
||||
LMIC_setupChannel(chidx, freq, 0, -1);
|
||||
#if LMIC_DEBUG_LEVEL > 1
|
||||
LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel, idx=%d, freq=%"PRIu32"\n", os_getTime(), chidx, freq);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !DISABLE_JOIN
|
||||
|
||||
void LMICeulike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) {
|
||||
os_copyMem(
|
||||
pStateBuffer->channelFreq,
|
||||
LMIC.channelFreq,
|
||||
sizeof(LMIC.channelFreq)
|
||||
);
|
||||
pStateBuffer->channelMap = LMIC.channelMap;
|
||||
}
|
||||
|
||||
bit_t LMICeulike_compareAdrState(const lmic_saved_adr_state_t *pStateBuffer) {
|
||||
if (memcmp(pStateBuffer->channelFreq, LMIC.channelFreq, sizeof(LMIC.channelFreq)) != 0)
|
||||
return 1;
|
||||
return pStateBuffer->channelMap != LMIC.channelMap;
|
||||
}
|
||||
|
||||
void LMICeulike_restoreAdrState(const lmic_saved_adr_state_t *pStateBuffer) {
|
||||
os_copyMem(
|
||||
LMIC.channelFreq,
|
||||
pStateBuffer->channelFreq,
|
||||
sizeof(LMIC.channelFreq)
|
||||
);
|
||||
LMIC.channelMap = pStateBuffer->channelMap;
|
||||
}
|
||||
|
||||
void LMICeulike_setRx1Freq(void) {
|
||||
#if !defined(DISABLE_MCMD_DlChannelReq)
|
||||
uint32_t dlFreq = LMIC.channelDlFreq[LMIC.txChnl];
|
||||
if (dlFreq != 0)
|
||||
LMIC.freq = dlFreq;
|
||||
#endif // !DISABLE_MCMD_DlChannelReq
|
||||
}
|
||||
|
||||
// Class A txDone handling for FSK.
|
||||
void
|
||||
LMICeulike_txDoneFSK(ostime_t delay, osjobcb_t func) {
|
||||
// one symbol == one bit at 50kHz == 20us.
|
||||
ostime_t const hsym = us2osticksRound(10);
|
||||
|
||||
// start a little earlier. PRERX_FSK is in bytes; one byte at 50 kHz == 160us
|
||||
delay -= LMICbandplan_PRERX_FSK * us2osticksRound(160);
|
||||
|
||||
// set LMIC.rxtime and LMIC.rxsyms:
|
||||
LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay, hsym, 8 * LMICbandplan_RXLEN_FSK);
|
||||
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - os_getRadioRxRampup(), func);
|
||||
}
|
||||
|
||||
#endif // CFG_LMIC_EU_like
|
||||
|
34
src/lmic/lmic_eu_like.h
Executable file → Normal file
34
src/lmic/lmic_eu_like.h
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -56,7 +56,8 @@ LMICeulike_isValidBeacon1(const uint8_t *d) {
|
||||
#define LMICbandplan_isFSK() (0)
|
||||
|
||||
// provide a default LMICbandplan_txDoneDoFSK()
|
||||
#define LMICbandplan_txDoneFSK(delay, func) do { } while (0)
|
||||
void LMICeulike_txDoneFSK(ostime_t delay, osjobcb_t func);
|
||||
#define LMICbandplan_txDoneFSK(delay, func) LMICeulike_txDoneFSK(delay, func)
|
||||
|
||||
#define LMICbandplan_joinAcceptChannelClear() LMICbandplan_initDefaultChannels(/* normal, not join */ 0)
|
||||
|
||||
@ -65,6 +66,11 @@ enum { BAND_MILLI = 0, BAND_CENTI = 1, BAND_DECI = 2, BAND_AUX = 3 };
|
||||
// there's a CFList on joins for EU-like plans
|
||||
#define LMICbandplan_hasJoinCFlist() (1)
|
||||
|
||||
/// \brief process CFLists from JoinAccept for EU-like regions
|
||||
void LMICeulike_processJoinAcceptCFList(void);
|
||||
/// \brief by default, EU-like plans use LMICeulike_processJoinAcceptCFList
|
||||
#define LMICbandplan_processJoinAcceptCFList LMICeulike_processJoinAcceptCFList
|
||||
|
||||
#define LMICbandplan_advanceBeaconChannel() \
|
||||
do { /* nothing */ } while (0)
|
||||
|
||||
@ -74,14 +80,14 @@ enum { BAND_MILLI = 0, BAND_CENTI = 1, BAND_DECI = 2, BAND_AUX = 3 };
|
||||
#define LMICbandplan_setSessionInitDefaultChannels() \
|
||||
do { LMICbandplan_initDefaultChannels(/* normal, not join */ 0); } while (0)
|
||||
|
||||
u1_t LMICeulike_mapChannels(u1_t chpage, u2_t chmap);
|
||||
bit_t LMICeulike_canMapChannels(u1_t chpage, u2_t chmap);
|
||||
#define LMICbandplan_canMapChannels(c, m) LMICeulike_canMapChannels(c, m)
|
||||
|
||||
bit_t LMICeulike_mapChannels(u1_t chpage, u2_t chmap);
|
||||
#define LMICbandplan_mapChannels(c, m) LMICeulike_mapChannels(c, m)
|
||||
|
||||
void LMICeulike_initJoinLoop(u1_t nDefaultChannels, s1_t adrTxPow);
|
||||
|
||||
#define LMICbandplan_setRx1Params() \
|
||||
do { /*LMIC.freq/rps remain unchanged*/ } while (0)
|
||||
|
||||
void LMICeulike_updateTx(ostime_t txbeg);
|
||||
#define LMICbandplan_updateTx(t) LMICeulike_updateTx(t)
|
||||
|
||||
@ -95,4 +101,20 @@ static inline ostime_t LMICeulike_nextJoinTime(ostime_t now) {
|
||||
#define LMICbandplan_init() \
|
||||
do { /* nothing */ } while (0)
|
||||
|
||||
void LMICeulike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer);
|
||||
#define LMICbandplan_saveAdrState(pState) LMICeulike_saveAdrState(pState)
|
||||
|
||||
bit_t LMICeulike_compareAdrState(const lmic_saved_adr_state_t *pStateBuffer);
|
||||
#define LMICbandplan_compareAdrState(pState) LMICeulike_compareAdrState(pState)
|
||||
|
||||
void LMICeulike_restoreAdrState(const lmic_saved_adr_state_t *pStateBuffer);
|
||||
#define LMICbandplan_restoreAdrState(pState) LMICeulike_restoreAdrState(pState)
|
||||
|
||||
// set Rx1 frequency (might be different than uplink).
|
||||
void LMICeulike_setRx1Freq(void);
|
||||
|
||||
bit_t LMICeulike_isDataRateFeasible(dr_t dr);
|
||||
#define LMICbandplan_isDataRateFeasible(dr) LMICeulike_isDataRateFeasible(dr)
|
||||
|
||||
|
||||
#endif // _lmic_eu_like_h_
|
||||
|
134
src/lmic/lmic_in866.c
Executable file → Normal file
134
src/lmic/lmic_in866.c
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -49,21 +49,36 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS
|
||||
};
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = { 59+5,59+5,59+5,123+5, 230+5, 230+5 };
|
||||
bit_t
|
||||
LMICin866_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = {
|
||||
59+5, 59+5, 59+5, 123+5, 250+5, 250+5, 0, 250+5
|
||||
};
|
||||
|
||||
uint8_t LMICin866_maxFrameLen(uint8_t dr) {
|
||||
if (dr < LENOF_TABLE(maxFrameLens))
|
||||
return TABLE_GET_U1(maxFrameLens, dr);
|
||||
else
|
||||
return 0xFF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static CONST_TABLE(s1_t, TXPOWLEVELS)[] = {
|
||||
20, 14, 11, 8, 5, 2, 0,0, 0,0,0,0, 0,0,0,0
|
||||
30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10
|
||||
};
|
||||
|
||||
int8_t LMICin866_pow2dBm(uint8_t mcmd_ladr_p1) {
|
||||
return TABLE_GET_S1(TXPOWLEVELS, (mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT);
|
||||
uint8_t const pindex = (mcmd_ladr_p1&MCMD_LinkADRReq_POW_MASK)>>MCMD_LinkADRReq_POW_SHIFT;
|
||||
if (pindex < LENOF_TABLE(TXPOWLEVELS)) {
|
||||
return TABLE_GET_S1(TXPOWLEVELS, pindex);
|
||||
} else {
|
||||
return -128;
|
||||
}
|
||||
}
|
||||
|
||||
// only used in this module, but used by variant macro dr2hsym().
|
||||
@ -98,6 +113,9 @@ void LMICin866_initDefaultChannels(bit_t join) {
|
||||
LMIC_API_PARAMETER(join);
|
||||
|
||||
os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq));
|
||||
#if !defined(DISABLE_MCMD_DlChannelReq)
|
||||
os_clearMem(&LMIC.channelDlFreq, sizeof(LMIC.channelDlFreq));
|
||||
#endif // !DISABLE_MCMD_DlChannelReq
|
||||
os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap));
|
||||
os_clearMem(&LMIC.bands, sizeof(LMIC.bands));
|
||||
|
||||
@ -124,18 +142,43 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief query number of default channels.
|
||||
///
|
||||
u1_t LMIC_queryNumDefaultChannels() {
|
||||
return NUM_DEFAULT_CHANNELS;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief LMIC_setupChannel for IN region
|
||||
///
|
||||
/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range
|
||||
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
|
||||
/// This routine is used internally for MAC commands, so we enforce
|
||||
/// this for the extenal API as well.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
// zero the band bits in freq, just in case.
|
||||
freq &= ~3;
|
||||
|
||||
if (chidx < NUM_DEFAULT_CHANNELS) {
|
||||
return 0;
|
||||
}
|
||||
bit_t fEnable = (freq != 0);
|
||||
if (chidx >= MAX_CHANNELS)
|
||||
return 0;
|
||||
if (band == -1) {
|
||||
freq |= BAND_MILLI;
|
||||
freq = (freq&~3) | BAND_MILLI;
|
||||
} else {
|
||||
if (band > BAND_MILLI) return 0;
|
||||
freq = (freq&~3) | band;
|
||||
}
|
||||
LMIC.channelFreq[chidx] = freq;
|
||||
LMIC.channelDrMap[chidx] = drmap == 0 ? DR_RANGE_MAP(IN866_DR_SF12, IN866_DR_SF7) : drmap;
|
||||
if (fEnable)
|
||||
LMIC.channelMap |= 1 << chidx; // enabled right away
|
||||
else
|
||||
LMIC.channelMap &= ~(1 << chidx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -148,28 +191,44 @@ u4_t LMICin866_convFreq(xref2cu1_t ptr) {
|
||||
return freq;
|
||||
}
|
||||
|
||||
// return the next time, but also do channel hopping here
|
||||
// since there's no duty cycle limitation, and no dwell limitation,
|
||||
// we simply loop through the channels sequentially.
|
||||
///
|
||||
/// \brief change the TX channel given the desired tx time.
|
||||
///
|
||||
/// \param [in] now is the time at which we want to transmit. In fact, it's always
|
||||
/// the current time.
|
||||
///
|
||||
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
|
||||
/// selected channel.
|
||||
///
|
||||
/// \details
|
||||
/// We scan all the bands, creating a mask of all enabled channels that are
|
||||
/// feasible at the earliest possible time. We then randomly choose one from
|
||||
/// that, updating the shuffle mask.
|
||||
///
|
||||
/// Since there's no duty cycle limitation, and no dwell limitation,
|
||||
/// we just choose a channel from the shuffle and return the current time.
|
||||
///
|
||||
ostime_t LMICin866_nextTx(ostime_t now) {
|
||||
const u1_t band = BAND_MILLI;
|
||||
uint16_t availmask;
|
||||
|
||||
for (u1_t ci = 0; ci < MAX_CHANNELS; ci++) {
|
||||
// Find next channel in given band
|
||||
u1_t chnl = LMIC.bands[band].lastchnl;
|
||||
for (u1_t ci = 0; ci<MAX_CHANNELS; ci++) {
|
||||
if ((chnl = (chnl + 1)) >= MAX_CHANNELS)
|
||||
chnl -= MAX_CHANNELS;
|
||||
if ((LMIC.channelMap & (1 << chnl)) != 0 && // channel enabled
|
||||
(LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) != 0 &&
|
||||
band == (LMIC.channelFreq[chnl] & 0x3)) { // in selected band
|
||||
LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
|
||||
return now;
|
||||
}
|
||||
}
|
||||
// scan all the enabled channels and make a mask of candidates
|
||||
availmask = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & (1 << chnl)) == 0)
|
||||
continue;
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
availmask |= 1 << chnl;
|
||||
}
|
||||
|
||||
// no enabled channel found! just use the last channel.
|
||||
// now: calculate the mask
|
||||
int candidateCh = LMIC_findNextChannel(&LMIC.channelShuffleMap, &availmask, 1, LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl);
|
||||
if (candidateCh >= 0) {
|
||||
// update the channel.
|
||||
LMIC.txChnl = candidateCh;
|
||||
}
|
||||
return now;
|
||||
}
|
||||
|
||||
@ -187,12 +246,27 @@ ostime_t LMICin866_nextJoinState(void) {
|
||||
}
|
||||
#endif // !DISABLE_JOIN
|
||||
|
||||
// txDone handling for FSK.
|
||||
void
|
||||
LMICin866_txDoneFSK(ostime_t delay, osjobcb_t func) {
|
||||
LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160);
|
||||
LMIC.rxsyms = RXLEN_FSK;
|
||||
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
|
||||
// set the Rx1 dndr, rps.
|
||||
void LMICin866_setRx1Params(void) {
|
||||
u1_t const txdr = LMIC.dndr;
|
||||
s1_t drOffset;
|
||||
s1_t candidateDr;
|
||||
|
||||
LMICeulike_setRx1Freq();
|
||||
|
||||
if ( LMIC.rx1DrOffset <= 5)
|
||||
drOffset = (s1_t) LMIC.rx1DrOffset;
|
||||
else
|
||||
drOffset = 5 - (s1_t) LMIC.rx1DrOffset;
|
||||
|
||||
candidateDr = (s1_t) txdr - drOffset;
|
||||
if (candidateDr < LORAWAN_DR0)
|
||||
candidateDr = 0;
|
||||
else if (candidateDr > LORAWAN_DR5)
|
||||
candidateDr = LORAWAN_DR5;
|
||||
|
||||
LMIC.dndr = (u1_t) candidateDr;
|
||||
LMIC.rps = dndr2rps(LMIC.dndr);
|
||||
}
|
||||
|
||||
void
|
||||
|
309
src/lmic/lmic_kr920.c
Normal file
309
src/lmic/lmic_kr920.c
Normal file
@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the <organization> nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define LMIC_DR_LEGACY 0
|
||||
|
||||
#include "lmic_bandplan.h"
|
||||
|
||||
#if defined(CFG_kr920)
|
||||
// ================================================================================
|
||||
//
|
||||
// BEG: KR920 related stuff
|
||||
//
|
||||
|
||||
CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS,
|
||||
(u1_t)MAKERPS(SF12, BW125, CR_4_5, 0, 0), // [0]
|
||||
(u1_t)MAKERPS(SF11, BW125, CR_4_5, 0, 0), // [1]
|
||||
(u1_t)MAKERPS(SF10, BW125, CR_4_5, 0, 0), // [2]
|
||||
(u1_t)MAKERPS(SF9, BW125, CR_4_5, 0, 0), // [3]
|
||||
(u1_t)MAKERPS(SF8, BW125, CR_4_5, 0, 0), // [4]
|
||||
(u1_t)MAKERPS(SF7, BW125, CR_4_5, 0, 0), // [5]
|
||||
ILLEGAL_RPS, // [6]
|
||||
};
|
||||
|
||||
bit_t
|
||||
LMICkr920_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = {
|
||||
59+5, 59+5, 59+5, 123+5, 250+5, 250+5
|
||||
};
|
||||
|
||||
uint8_t LMICkr920_maxFrameLen(uint8_t dr) {
|
||||
if (dr < LENOF_TABLE(maxFrameLens))
|
||||
return TABLE_GET_U1(maxFrameLens, dr);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static CONST_TABLE(s1_t, TXPOWLEVELS)[] = {
|
||||
14, 12, 10, 8, 6, 4, 2, 0
|
||||
};
|
||||
|
||||
int8_t LMICkr920_pow2dBm(uint8_t mcmd_ladr_p1) {
|
||||
uint8_t const pindex = (mcmd_ladr_p1&MCMD_LinkADRReq_POW_MASK)>>MCMD_LinkADRReq_POW_SHIFT;
|
||||
if (pindex < LENOF_TABLE(TXPOWLEVELS)) {
|
||||
return TABLE_GET_S1(TXPOWLEVELS, pindex);
|
||||
} else {
|
||||
return -128;
|
||||
}
|
||||
}
|
||||
|
||||
// only used in this module, but used by variant macro dr2hsym().
|
||||
static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = {
|
||||
us2osticksRound(128 << 7), // DR_SF12
|
||||
us2osticksRound(128 << 6), // DR_SF11
|
||||
us2osticksRound(128 << 5), // DR_SF10
|
||||
us2osticksRound(128 << 4), // DR_SF9
|
||||
us2osticksRound(128 << 3), // DR_SF8
|
||||
us2osticksRound(128 << 2), // DR_SF7
|
||||
};
|
||||
|
||||
ostime_t LMICkr920_dr2hsym(uint8_t dr) {
|
||||
return TABLE_GET_OSTIME(DR2HSYM_osticks, dr);
|
||||
}
|
||||
|
||||
|
||||
// All frequencies are marked as BAND_MILLI, and we don't do duty-cycle. But this lets
|
||||
// us reuse code.
|
||||
enum { NUM_DEFAULT_CHANNELS = 3 };
|
||||
static CONST_TABLE(u4_t, iniChannelFreq)[NUM_DEFAULT_CHANNELS] = {
|
||||
// Default operational frequencies
|
||||
KR920_F1 | BAND_MILLI,
|
||||
KR920_F2 | BAND_MILLI,
|
||||
KR920_F3 | BAND_MILLI,
|
||||
};
|
||||
|
||||
// korea ignores the join flag, becuase the channel setup is the same either way.
|
||||
void LMICkr920_initDefaultChannels(bit_t join) {
|
||||
LMIC_API_PARAMETER(join);
|
||||
|
||||
os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq));
|
||||
#if !defined(DISABLE_MCMD_DlChannelReq)
|
||||
os_clearMem(&LMIC.channelDlFreq, sizeof(LMIC.channelDlFreq));
|
||||
#endif // !DISABLE_MCMD_DlChannelReq
|
||||
os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap));
|
||||
os_clearMem(&LMIC.bands, sizeof(LMIC.bands));
|
||||
|
||||
LMIC.channelMap = (1 << NUM_DEFAULT_CHANNELS) - 1;
|
||||
for (u1_t fu = 0; fu<NUM_DEFAULT_CHANNELS; fu++) {
|
||||
LMIC.channelFreq[fu] = TABLE_GET_U4(iniChannelFreq, fu);
|
||||
LMIC.channelDrMap[fu] = DR_RANGE_MAP(KR920_DR_SF12, KR920_DR_SF7);
|
||||
}
|
||||
|
||||
LMIC.bands[BAND_MILLI].txcap = 1; // no limit, in effect.
|
||||
LMIC.bands[BAND_MILLI].txpow = KR920_TX_EIRP_MAX_DBM;
|
||||
LMIC.bands[BAND_MILLI].lastchnl = os_getRndU1() % MAX_CHANNELS;
|
||||
LMIC.bands[BAND_MILLI].avail = os_getTime();
|
||||
}
|
||||
|
||||
void
|
||||
LMICkr920_init(void) {
|
||||
// set LBT mode
|
||||
LMIC.lbt_ticks = us2osticks(KR920_LBT_US);
|
||||
LMIC.lbt_dbmax = KR920_LBT_DB_MAX;
|
||||
}
|
||||
|
||||
void
|
||||
LMICas923_resetDefaultChannels(void) {
|
||||
// set LBT mode
|
||||
LMIC.lbt_ticks = us2osticks(KR920_LBT_US);
|
||||
LMIC.lbt_dbmax = KR920_LBT_DB_MAX;
|
||||
}
|
||||
|
||||
|
||||
bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
|
||||
if (bandidx > BAND_MILLI) return 0;
|
||||
//band_t* b = &LMIC.bands[bandidx];
|
||||
xref2band_t b = &LMIC.bands[bandidx];
|
||||
b->txpow = txpow;
|
||||
b->txcap = txcap;
|
||||
b->avail = os_getTime();
|
||||
b->lastchnl = os_getRndU1() % MAX_CHANNELS;
|
||||
return 1;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief query number of default channels.
|
||||
///
|
||||
u1_t LMIC_queryNumDefaultChannels() {
|
||||
return NUM_DEFAULT_CHANNELS;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief LMIC_setupChannel for KR920
|
||||
///
|
||||
/// \note according to LoRaWAN 1.3 section 5.6, "the acceptable range
|
||||
/// for **ChIndex** is N to 16", where N is our \c NUM_DEFAULT_CHANNELS.
|
||||
/// This routine is used internally for MAC commands, so we enforce
|
||||
/// this for the extenal API as well.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
// zero the band bits in freq, just in case.
|
||||
freq &= ~3;
|
||||
|
||||
if (chidx < NUM_DEFAULT_CHANNELS) {
|
||||
// can't change a default channel.
|
||||
return 0;
|
||||
}
|
||||
bit_t fEnable = (freq != 0);
|
||||
if (chidx >= MAX_CHANNELS)
|
||||
return 0;
|
||||
if (band == -1) {
|
||||
freq = (freq&~3) | BAND_MILLI;
|
||||
} else {
|
||||
if (band > BAND_MILLI) return 0;
|
||||
freq = (freq&~3) | band;
|
||||
}
|
||||
LMIC.channelFreq[chidx] = freq;
|
||||
LMIC.channelDrMap[chidx] = drmap == 0 ? DR_RANGE_MAP(KR920_DR_SF12, KR920_DR_SF7) : drmap;
|
||||
if (fEnable)
|
||||
LMIC.channelMap |= 1 << chidx; // enabled right away
|
||||
else
|
||||
LMIC.channelMap &= ~(1 << chidx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
u4_t LMICkr920_convFreq(xref2cu1_t ptr) {
|
||||
u4_t freq = (os_rlsbf4(ptr - 1) >> 8) * 100;
|
||||
if (freq < KR920_FREQ_MIN || freq > KR920_FREQ_MAX)
|
||||
freq = 0;
|
||||
return freq;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief change the TX channel given the desired tx time.
|
||||
///
|
||||
/// \param [in] now is the time at which we want to transmit. In fact, it's always
|
||||
/// the current time.
|
||||
///
|
||||
/// \returns the actual time at which we can transmit. \c LMIC.txChnl is set to the
|
||||
/// selected channel.
|
||||
///
|
||||
/// \details
|
||||
/// We scan all the bands, creating a mask of all enabled channels that are
|
||||
/// feasible at the earliest possible time. We then randomly choose one from
|
||||
/// that, updating the shuffle mask.
|
||||
///
|
||||
/// Since there's no duty cycle limitation, and no dwell limitation,
|
||||
/// we just choose a channel from the shuffle and return the current time.
|
||||
///
|
||||
ostime_t LMICkr920_nextTx(ostime_t now) {
|
||||
uint16_t availmask;
|
||||
|
||||
// scan all the enabled channels and make a mask of candidates
|
||||
availmask = 0;
|
||||
for (u1_t chnl = 0; chnl < MAX_CHANNELS; ++chnl) {
|
||||
// not enabled?
|
||||
if ((LMIC.channelMap & (1 << chnl)) == 0)
|
||||
continue;
|
||||
// not feasible?
|
||||
if ((LMIC.channelDrMap[chnl] & (1 << (LMIC.datarate & 0xF))) == 0)
|
||||
continue;
|
||||
availmask |= 1 << chnl;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
#if !defined(DISABLE_BEACONS)
|
||||
void LMICkr920_setBcnRxParams(void) {
|
||||
LMIC.dataLen = 0;
|
||||
LMIC.freq = LMIC.channelFreq[LMIC.bcnChnl] & ~(u4_t)3;
|
||||
LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN), 1), LEN_BCN);
|
||||
}
|
||||
#endif // !DISABLE_BEACONS
|
||||
|
||||
#if !defined(DISABLE_JOIN)
|
||||
ostime_t LMICkr920_nextJoinState(void) {
|
||||
return LMICeulike_nextJoinState(NUM_DEFAULT_CHANNELS);
|
||||
}
|
||||
#endif // !DISABLE_JOIN
|
||||
|
||||
// set the Rx1 dndr, rps.
|
||||
void LMICkr920_setRx1Params(void) {
|
||||
u1_t const txdr = LMIC.dndr;
|
||||
s1_t drOffset;
|
||||
s1_t candidateDr;
|
||||
|
||||
LMICeulike_setRx1Freq();
|
||||
|
||||
if ( LMIC.rx1DrOffset <= 5)
|
||||
drOffset = (s1_t) LMIC.rx1DrOffset;
|
||||
else
|
||||
drOffset = 5 - (s1_t) LMIC.rx1DrOffset;
|
||||
|
||||
candidateDr = (s1_t) txdr - drOffset;
|
||||
if (candidateDr < LORAWAN_DR0)
|
||||
candidateDr = 0;
|
||||
else if (candidateDr > LORAWAN_DR5)
|
||||
candidateDr = LORAWAN_DR5;
|
||||
|
||||
LMIC.dndr = (u1_t) candidateDr;
|
||||
LMIC.rps = dndr2rps(LMIC.dndr);
|
||||
}
|
||||
|
||||
void
|
||||
LMICkr920_initJoinLoop(void) {
|
||||
LMICeulike_initJoinLoop(NUM_DEFAULT_CHANNELS, /* adr dBm */ KR920_TX_EIRP_MAX_DBM);
|
||||
}
|
||||
|
||||
void LMICkr920_updateTx(ostime_t txbeg) {
|
||||
u4_t freq = LMIC.channelFreq[LMIC.txChnl];
|
||||
// Update global/band specific duty cycle stats
|
||||
ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
|
||||
// Update channel/global duty cycle stats
|
||||
xref2band_t band = &LMIC.bands[freq & 0x3];
|
||||
LMIC.freq = freq & ~(u4_t)3;
|
||||
LMIC.txpow = band->txpow;
|
||||
if (LMIC.freq <= KR920_FDOWN && LMIC.txpow > KR920_TX_EIRP_MAX_DBM_LOW) {
|
||||
LMIC.txpow = KR920_TX_EIRP_MAX_DBM_LOW;
|
||||
}
|
||||
band->avail = txbeg + airtime * band->txcap;
|
||||
if (LMIC.globalDutyRate != 0)
|
||||
LMIC.globalDutyAvail = txbeg + (airtime << LMIC.globalDutyRate);
|
||||
}
|
||||
|
||||
//
|
||||
// END: KR920 related stuff
|
||||
//
|
||||
// ================================================================================
|
||||
#endif
|
132
src/lmic/lmic_us915.c
Executable file → Normal file
132
src/lmic/lmic_us915.c
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -55,13 +55,34 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
|
||||
ILLEGAL_RPS // [14]
|
||||
};
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = { 24,66,142,255,255,255,255,255, 66,142 };
|
||||
bit_t
|
||||
LMICus915_validDR(dr_t dr) {
|
||||
// use subtract here to avoid overflow
|
||||
if (dr >= LENOF_TABLE(_DR2RPS_CRC) - 2)
|
||||
return 0;
|
||||
return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS;
|
||||
}
|
||||
|
||||
static CONST_TABLE(u1_t, maxFrameLens)[] = {
|
||||
19+5, 61+5, 133+5, 250+5, 250+5, 0, 0,0,
|
||||
61+5, 133+5, 250+5, 250+5, 250+5, 250+5
|
||||
};
|
||||
|
||||
uint8_t LMICus915_maxFrameLen(uint8_t dr) {
|
||||
if (dr < LENOF_TABLE(maxFrameLens))
|
||||
return TABLE_GET_U1(maxFrameLens, dr);
|
||||
else
|
||||
return 0xFF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t LMICus915_pow2dbm(uint8_t mcmd_ladr_p1) {
|
||||
if ((mcmd_ladr_p1 & MCMD_LinkADRReq_POW_MASK) >
|
||||
((LMIC_LORAWAN_SPEC_VERSION < LMIC_LORAWAN_SPEC_VERSION_1_0_3)
|
||||
? US915_LinkAdrReq_POW_MAX_1_0_2
|
||||
: US915_LinkAdrReq_POW_MAX_1_0_3))
|
||||
return -128;
|
||||
else
|
||||
return ((s1_t)(US915_TX_MAX_DBM - (((mcmd_ladr_p1)&MCMD_LinkADRReq_POW_MASK)<<1)));
|
||||
}
|
||||
|
||||
static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = {
|
||||
@ -86,21 +107,36 @@ u4_t LMICus915_convFreq(xref2cu1_t ptr) {
|
||||
return freq;
|
||||
}
|
||||
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
LMIC_API_PARAMETER(band);
|
||||
|
||||
if (chidx < 72 || chidx >= 72 + MAX_XCHANNELS)
|
||||
return 0; // channels 0..71 are hardwired
|
||||
LMIC.xchFreq[chidx - 72] = freq;
|
||||
// TODO(tmm@mcci.com): don't use US SF directly, use something from the LMIC context or a static const
|
||||
LMIC.xchDrMap[chidx - 72] = drmap == 0 ? DR_RANGE_MAP(US915_DR_SF10, US915_DR_SF8C) : drmap;
|
||||
LMIC.channelMap[chidx >> 4] |= (1 << (chidx & 0xF));
|
||||
return 1;
|
||||
///
|
||||
/// \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;
|
||||
}
|
||||
|
||||
void LMIC_disableChannel(u1_t channel) {
|
||||
if (channel < 72 + MAX_XCHANNELS) {
|
||||
///
|
||||
/// \brief LMIC_setupChannel for US915
|
||||
///
|
||||
/// \note there are no progammable channels for US915, so this API
|
||||
/// always returns FALSE.
|
||||
///
|
||||
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
|
||||
LMIC_API_PARAMETER(chidx);
|
||||
LMIC_API_PARAMETER(freq);
|
||||
LMIC_API_PARAMETER(drmap);
|
||||
LMIC_API_PARAMETER(band);
|
||||
|
||||
return 0; // channels 0..71 are hardwired
|
||||
}
|
||||
|
||||
bit_t LMIC_disableChannel(u1_t channel) {
|
||||
bit_t result = 0;
|
||||
if (channel < 72) {
|
||||
if (ENABLED_CHANNEL(channel)) {
|
||||
result = 1;
|
||||
if (IS_CHANNEL_125khz(channel))
|
||||
LMIC.activeChannels125khz--;
|
||||
else if (IS_CHANNEL_500khz(channel))
|
||||
@ -108,11 +144,14 @@ void LMIC_disableChannel(u1_t channel) {
|
||||
}
|
||||
LMIC.channelMap[channel >> 4] &= ~(1 << (channel & 0xF));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void LMIC_enableChannel(u1_t channel) {
|
||||
if (channel < 72 + MAX_XCHANNELS) {
|
||||
bit_t LMIC_enableChannel(u1_t channel) {
|
||||
bit_t result = 0;
|
||||
if (channel < 72) {
|
||||
if (!ENABLED_CHANNEL(channel)) {
|
||||
result = 1;
|
||||
if (IS_CHANNEL_125khz(channel))
|
||||
LMIC.activeChannels125khz++;
|
||||
else if (IS_CHANNEL_500khz(channel))
|
||||
@ -120,42 +159,52 @@ void LMIC_enableChannel(u1_t channel) {
|
||||
}
|
||||
LMIC.channelMap[channel >> 4] |= (1 << (channel & 0xF));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void LMIC_enableSubBand(u1_t band) {
|
||||
bit_t LMIC_enableSubBand(u1_t band) {
|
||||
ASSERT(band < 8);
|
||||
u1_t start = band * 8;
|
||||
u1_t end = start + 8;
|
||||
bit_t result = 0;
|
||||
|
||||
// enable all eight 125 kHz channels in this subband
|
||||
for (int channel = start; channel < end; ++channel)
|
||||
LMIC_enableChannel(channel);
|
||||
result |= LMIC_enableChannel(channel);
|
||||
|
||||
// there's a single 500 kHz channel associated with
|
||||
// each group of 8 125 kHz channels. Enable it, too.
|
||||
LMIC_enableChannel(64 + band);
|
||||
result |= LMIC_enableChannel(64 + band);
|
||||
return result;
|
||||
}
|
||||
void LMIC_disableSubBand(u1_t band) {
|
||||
|
||||
bit_t LMIC_disableSubBand(u1_t band) {
|
||||
ASSERT(band < 8);
|
||||
u1_t start = band * 8;
|
||||
u1_t end = start + 8;
|
||||
bit_t result = 0;
|
||||
|
||||
// disable all eight 125 kHz channels in this subband
|
||||
for (int channel = start; channel < end; ++channel)
|
||||
LMIC_disableChannel(channel);
|
||||
result |= LMIC_disableChannel(channel);
|
||||
|
||||
// there's a single 500 kHz channel associated with
|
||||
// each group of 8 125 kHz channels. Disable it, too.
|
||||
LMIC_disableChannel(64 + band);
|
||||
result |= LMIC_disableChannel(64 + band);
|
||||
return result;
|
||||
}
|
||||
void LMIC_selectSubBand(u1_t band) {
|
||||
|
||||
bit_t LMIC_selectSubBand(u1_t band) {
|
||||
bit_t result = 0;
|
||||
|
||||
ASSERT(band < 8);
|
||||
for (int b = 0; b<8; ++b) {
|
||||
if (band == b)
|
||||
LMIC_enableSubBand(b);
|
||||
result |= LMIC_enableSubBand(b);
|
||||
else
|
||||
LMIC_disableSubBand(b);
|
||||
result |= LMIC_disableSubBand(b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void LMICus915_updateTx(ostime_t txbeg) {
|
||||
@ -169,14 +218,8 @@ void LMICus915_updateTx(ostime_t txbeg) {
|
||||
} else {
|
||||
// at 500kHz bandwidth, we're allowed more power.
|
||||
LMIC.txpow = 26;
|
||||
if (chnl < 64 + 8) {
|
||||
LMIC.freq = US915_500kHz_UPFBASE + (chnl - 64)*US915_500kHz_UPFSTEP;
|
||||
}
|
||||
else {
|
||||
ASSERT(chnl < 64 + 8 + MAX_XCHANNELS);
|
||||
LMIC.freq = LMIC.xchFreq[chnl - 72];
|
||||
}
|
||||
}
|
||||
|
||||
// Update global duty cycle stats
|
||||
if (LMIC.globalDutyRate != 0) {
|
||||
@ -193,16 +236,31 @@ void LMICus915_setBcnRxParams(void) {
|
||||
}
|
||||
#endif // !DISABLE_BEACONS
|
||||
|
||||
// TODO(tmm@mcci.com): parmeterize for US-like
|
||||
// set the Rx1 dndr, rps.
|
||||
void LMICus915_setRx1Params(void) {
|
||||
u1_t const txdr = LMIC.dndr;
|
||||
u1_t candidateDr;
|
||||
LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP;
|
||||
if( /* TX datarate */LMIC.dndr < US915_DR_SF8C )
|
||||
LMIC.dndr += US915_DR_SF10CR - US915_DR_SF10;
|
||||
else if( LMIC.dndr == US915_DR_SF8C )
|
||||
LMIC.dndr = US915_DR_SF7CR;
|
||||
if ( /* TX datarate */txdr < LORAWAN_DR4)
|
||||
candidateDr = txdr + 10 - LMIC.rx1DrOffset;
|
||||
else
|
||||
candidateDr = LORAWAN_DR13 - LMIC.rx1DrOffset;
|
||||
|
||||
if (candidateDr < LORAWAN_DR8)
|
||||
candidateDr = LORAWAN_DR8;
|
||||
else if (candidateDr > LORAWAN_DR13)
|
||||
candidateDr = LORAWAN_DR13;
|
||||
|
||||
LMIC.dndr = candidateDr;
|
||||
LMIC.rps = dndr2rps(LMIC.dndr);
|
||||
}
|
||||
|
||||
void LMICus915_initJoinLoop(void) {
|
||||
LMICuslike_initJoinLoop();
|
||||
|
||||
// initialize the adrTxPower.
|
||||
LMIC.adrTxPow = 20; // dBm
|
||||
}
|
||||
|
||||
//
|
||||
// END: US915 related stuff
|
||||
|
238
src/lmic/lmic_us_like.c
Executable file → Normal file
238
src/lmic/lmic_us_like.c
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -36,36 +36,34 @@
|
||||
# error "LMICuslike_getFirst500kHzDR() not defined by bandplan"
|
||||
#endif
|
||||
|
||||
static void setNextChannel(uint start, uint end, uint count) {
|
||||
///
|
||||
/// \brief set LMIC.txChan to the next selected channel.
|
||||
///
|
||||
/// \param [in] start first channel number
|
||||
/// \param [in] end one past the last channel number
|
||||
///
|
||||
/// \details
|
||||
/// We set up a call to LMIC_findNextChannel using the channelShuffleMap and
|
||||
/// the channelEnableMap. We subset these based on start and end. \p start must
|
||||
/// be a multiple of 16.
|
||||
///
|
||||
static void setNextChannel(uint16_t start, uint16_t end, uint16_t count) {
|
||||
ASSERT(count>0);
|
||||
ASSERT(start<end);
|
||||
ASSERT(count <= (end - start));
|
||||
// We used to pick a random channel once and then just increment. That is not per spec.
|
||||
// Now we use a new random number each time, because they are not very expensive.
|
||||
// Regarding the algo below, we cannot pick a number and scan until we hit an enabled channel.
|
||||
// That would result in the first enabled channel following a set of disabled ones
|
||||
// being used more frequently than the other enabled channels.
|
||||
ASSERT((start & 0xF) == 0);
|
||||
uint16_t const mapStart = start >> 4;
|
||||
uint16_t const mapEntries = (end - start + 15) >> 4;
|
||||
|
||||
// Last used channel is in range. It is not a candidate, per spec.
|
||||
uint lastTxChan = LMIC.txChnl;
|
||||
if (start <= lastTxChan && lastTxChan<end &&
|
||||
// Adjust count only if still enabled. Otherwise, no chance of selection.
|
||||
ENABLED_CHANNEL(lastTxChan)) {
|
||||
--count;
|
||||
if (count == 0) {
|
||||
return; // Only one active channel, so keep using it.
|
||||
}
|
||||
}
|
||||
int candidate = start + LMIC_findNextChannel(
|
||||
LMIC.channelShuffleMap + mapStart,
|
||||
LMIC.channelMap + mapStart,
|
||||
mapEntries,
|
||||
LMIC.txChnl == 0xFF ? -1 : LMIC.txChnl
|
||||
);
|
||||
|
||||
uint nth = os_getRndU1() % count;
|
||||
for (u1_t chnl = start; chnl<end; chnl++) {
|
||||
// Scan for nth enabled channel that is not the last channel used
|
||||
if (chnl != lastTxChan && ENABLED_CHANNEL(chnl) && (nth--) == 0) {
|
||||
LMIC.txChnl = chnl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// No feasible channel found! Keep old one.
|
||||
if (candidate >= 0)
|
||||
LMIC.txChnl = candidate;
|
||||
}
|
||||
|
||||
|
||||
@ -87,35 +85,69 @@ void LMICuslike_initDefaultChannels(bit_t fJoin) {
|
||||
for (u1_t i = 0; i<4; i++)
|
||||
LMIC.channelMap[i] = 0xFFFF;
|
||||
LMIC.channelMap[4] = 0x00FF;
|
||||
os_clearMem(LMIC.channelShuffleMap, sizeof(LMIC.channelShuffleMap));
|
||||
LMIC.activeChannels125khz = 64;
|
||||
LMIC.activeChannels500khz = 8;
|
||||
// choose a random channel.
|
||||
LMIC.txChnl = 0xFF;
|
||||
}
|
||||
|
||||
u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) {
|
||||
// verify that a given setting is permitted
|
||||
bit_t LMICuslike_canMapChannels(u1_t chpage, u2_t chmap) {
|
||||
/*
|
||||
|| MCMD_LADR_CHP_125ON and MCMD_LADR_CHP_125OFF are special. The
|
||||
|| MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON and MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF are special. The
|
||||
|| channel map appllies to 500kHz (ch 64..71) and in addition
|
||||
|| all channels 0..63 are turned off or on. MCMC_LADR_CHP_BANK
|
||||
|| is also special, in that it enables subbands.
|
||||
||
|
||||
|| TODO(tmm@mcci.com) revise the 0xFF00 mask for regions with other than
|
||||
|| eight 500 kHz channels.
|
||||
*/
|
||||
u1_t base, top;
|
||||
|
||||
if (chpage < MCMD_LADR_CHP_USLIKE_SPECIAL) {
|
||||
// operate on channels 0..15, 16..31, 32..47, 48..63
|
||||
base = chpage << 4;
|
||||
top = base + 16;
|
||||
if (base == 64) {
|
||||
if (chpage < MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL) {
|
||||
// operate on channels 0..15, 16..31, 32..47, 48..63, 64..71
|
||||
if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_500K) {
|
||||
if (chmap & 0xFF00) {
|
||||
// those are reserved bits, fail.
|
||||
return 0;
|
||||
}
|
||||
top = 72;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else if (chpage == MCMD_LADR_CHP_BANK) {
|
||||
if (chmap & 0xFF00) {
|
||||
// those are resreved bits, fail.
|
||||
} else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK) {
|
||||
if ((chmap & 0xFF00) != 0) {
|
||||
// Reserved bits set, fail.
|
||||
return 0;
|
||||
}
|
||||
} else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON ||
|
||||
chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF) {
|
||||
//
|
||||
// if disabling all 125kHz chans, you might think we must have
|
||||
// at least one 500kHz chan; but that's a local conclusion.
|
||||
// Some network servers will disable all (including 500kHz)
|
||||
// then turn things back on in the next LinkADRReq. So
|
||||
// we can't fail that here.
|
||||
//
|
||||
// But don't allow reserved bits to be set in chmap.
|
||||
//
|
||||
if ((chmap & 0xFF00) != 0)
|
||||
return 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if we get here, it looks legal.
|
||||
return 1;
|
||||
}
|
||||
|
||||
// map channels. return true if configuration looks valid.
|
||||
bit_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) {
|
||||
/*
|
||||
|| MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON and MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF are special. The
|
||||
|| channel map appllies to 500kHz (ch 64..71) and in addition
|
||||
|| all channels 0..63 are turned off or on. MCMC_LADR_CHP_BANK
|
||||
|| is also special, in that it enables subbands.
|
||||
*/
|
||||
if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK) {
|
||||
// each bit enables a bank of channels
|
||||
for (u1_t subband = 0; subband < 8; ++subband, chmap >>= 1) {
|
||||
if (chmap & 1) {
|
||||
@ -123,12 +155,22 @@ u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) {
|
||||
} else {
|
||||
LMIC_disableSubBand(subband);
|
||||
}
|
||||
|
||||
// don't change any channels below
|
||||
base = top = 0;
|
||||
}
|
||||
} else if (chpage == MCMD_LADR_CHP_125ON || chpage == MCMD_LADR_CHP_125OFF) {
|
||||
u1_t const en125 = chpage == MCMD_LADR_CHP_125ON;
|
||||
} else {
|
||||
u1_t base, top;
|
||||
|
||||
if (chpage < MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL) {
|
||||
// operate on channels 0..15, 16..31, 32..47, 48..63
|
||||
// note that the chpage hasn't been shifted right, so
|
||||
// it's really the base.
|
||||
base = chpage;
|
||||
top = base + 16;
|
||||
if (base == 64) {
|
||||
top = 72;
|
||||
}
|
||||
} else /* if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON ||
|
||||
chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF) */ {
|
||||
u1_t const en125 = chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON;
|
||||
|
||||
// enable or disable all 125kHz channels
|
||||
for (u1_t chnl = 0; chnl < 64; ++chnl) {
|
||||
@ -141,8 +183,6 @@ u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) {
|
||||
// then apply mask to top 8 channels.
|
||||
base = 64;
|
||||
top = 72;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// apply chmap to channels in [base..top-1].
|
||||
@ -153,7 +193,10 @@ u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) {
|
||||
else
|
||||
LMIC_disableChannel(chnl);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
LMICOS_logEventUint32("LMICuslike_mapChannels", ((u4_t)LMIC.activeChannels125khz << 16u)|(LMIC.activeChannels500khz << 0u));
|
||||
return (LMIC.activeChannels125khz > 0) || (LMIC.activeChannels500khz > 0);
|
||||
}
|
||||
|
||||
// US does not have duty cycling - return now as earliest TX time
|
||||
@ -161,40 +204,56 @@ u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap) {
|
||||
ostime_t LMICuslike_nextTx(ostime_t now) {
|
||||
// TODO(tmm@mcci.com): use a static const for US-like
|
||||
if (LMIC.datarate >= LMICuslike_getFirst500kHzDR()) { // 500kHz
|
||||
ASSERT(LMIC.activeChannels500khz>0);
|
||||
if (LMIC.activeChannels500khz > 0) {
|
||||
setNextChannel(64, 64 + 8, LMIC.activeChannels500khz);
|
||||
} else if (LMIC.activeChannels125khz > 0) {
|
||||
LMIC.datarate = lowerDR(LMICuslike_getFirst500kHzDR(), 1);
|
||||
setNextChannel(0, 64, LMIC.activeChannels125khz);
|
||||
LMICOS_logEvent("LMICuslike_nextTx: no 500k, choose 125k");
|
||||
} else {
|
||||
LMICOS_logEvent("LMICuslike_nextTx: no channels at all (500)");
|
||||
}
|
||||
}
|
||||
else { // 125kHz
|
||||
ASSERT(LMIC.activeChannels125khz>0);
|
||||
if (LMIC.activeChannels125khz > 0) {
|
||||
setNextChannel(0, 64, LMIC.activeChannels125khz);
|
||||
} else if (LMIC.activeChannels500khz > 0) {
|
||||
LMIC.datarate = LMICuslike_getFirst500kHzDR();
|
||||
setNextChannel(64, 64 + 8, LMIC.activeChannels500khz);
|
||||
LMICOS_logEvent("LMICuslike_nextTx: no 125k, choose 500k");
|
||||
} else {
|
||||
LMICOS_logEvent("LMICuslike_nextTx: no channels at all (125)");
|
||||
}
|
||||
}
|
||||
return now;
|
||||
}
|
||||
|
||||
bit_t LMICuslike_isDataRateFeasible(dr_t dr) {
|
||||
if (dr >= LMICuslike_getFirst500kHzDR()) { // 500kHz
|
||||
return LMIC.activeChannels500khz > 0;
|
||||
} else {
|
||||
return LMIC.activeChannels125khz > 6;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(DISABLE_JOIN)
|
||||
void LMICuslike_initJoinLoop(void) {
|
||||
// set an initial condition so that setNextChannel()'s preconds are met
|
||||
LMIC.txChnl = 0;
|
||||
LMIC.txChnl = 0xFF;
|
||||
|
||||
// then chose a new channel. This gives us a random first channel for
|
||||
// the join. Minor nit: if channel 0 is enabled, it will never be used
|
||||
// as the first join channel. The join logic uses the current txChnl,
|
||||
// the join. The join logic uses the current txChnl,
|
||||
// then changes after the rx window expires; so we need to set a valid
|
||||
// starting point.
|
||||
setNextChannel(0, 64, LMIC.activeChannels125khz);
|
||||
|
||||
// initialize the adrTxPower.
|
||||
// TODO(tmm@mcci.com): is this right for all US-like regions
|
||||
LMIC.adrTxPow = 20; // dBm
|
||||
ASSERT((LMIC.opmode & OP_NEXTCHNL) == 0);
|
||||
|
||||
// make sure LMIC.txend is valid.
|
||||
LMIC.txend = os_getTime();
|
||||
ASSERT((LMIC.opmode & OP_NEXTCHNL) == 0);
|
||||
|
||||
// make sure the datarate is set to DR0 per LoRaWAN regional reqts V1.0.2,
|
||||
// section 2.2.2
|
||||
// TODO(tmm@mcci.com): parameterize this for US-like
|
||||
LMICcore_setDrJoin(DRCHG_SET, LORAWAN_DR0);
|
||||
// make sure the datarate is set to DR2 per LoRaWAN regional reqts V1.0.2,
|
||||
// section 2.*.2
|
||||
LMICcore_setDrJoin(DRCHG_SET, LMICbandplan_getInitialDrJoin());
|
||||
|
||||
// TODO(tmm@mcci.com) need to implement the transmit randomization and
|
||||
// duty cycle restrictions from LoRaWAN V1.0.2 section 7.
|
||||
@ -226,19 +285,23 @@ ostime_t LMICuslike_nextJoinState(void) {
|
||||
if (LMIC.datarate != LMICuslike_getFirst500kHzDR()) {
|
||||
// assume that 500 kHz equiv of last 125 kHz channel
|
||||
// is also enabled, and use it next.
|
||||
LMIC.txChnl_125kHz = LMIC.txChnl;
|
||||
LMIC.txChnl = 64 + (LMIC.txChnl >> 3);
|
||||
LMICcore_setDrJoin(DRCHG_SET, LMICuslike_getFirst500kHzDR());
|
||||
}
|
||||
else {
|
||||
// restore invariant
|
||||
LMIC.txChnl = LMIC.txChnl_125kHz;
|
||||
setNextChannel(0, 64, LMIC.activeChannels125khz);
|
||||
|
||||
// TODO(tmm@mcci.com) parameterize
|
||||
s1_t dr = LORAWAN_DR0;
|
||||
s1_t dr = LMICuslike_getJoin125kHzDR();
|
||||
if ((++LMIC.txCnt & 0x7) == 0) {
|
||||
failed = 1; // All DR exhausted - signal failed
|
||||
}
|
||||
LMICcore_setDrJoin(DRCHG_SET, dr);
|
||||
}
|
||||
// tell the main loop that we've already selected a channel.
|
||||
LMIC.opmode &= ~OP_NEXTCHNL;
|
||||
|
||||
// TODO(tmm@mcci.com): change delay to (0:1) secs + a known t0, but randomized;
|
||||
@ -260,4 +323,55 @@ ostime_t LMICuslike_nextJoinState(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(DISABLE_JOIN)
|
||||
void LMICuslike_processJoinAcceptCFList(void) {
|
||||
if ( LMICbandplan_hasJoinCFlist() &&
|
||||
LMIC.frame[OFF_CFLIST + 15] == LORAWAN_JoinAccept_CFListType_MASK ) {
|
||||
u1_t dlen;
|
||||
|
||||
dlen = OFF_CFLIST;
|
||||
for( u1_t chidx = 0; chidx < 8 * sizeof(LMIC.channelMap); chidx += 16, dlen += 2 ) {
|
||||
u2_t mask = os_rlsbf2(&LMIC.frame[dlen]);
|
||||
#if LMIC_DEBUG_LEVEL > 1
|
||||
LMIC_DEBUG_PRINTF("%"LMIC_PRId_ostime_t": Setup channel mask, group=%u, mask=%04x\n", os_getTime(), chidx, mask);
|
||||
#endif
|
||||
for ( u1_t chnum = chidx; chnum < chidx + 16; ++chnum, mask >>= 1) {
|
||||
if (chnum >= 72) {
|
||||
break;
|
||||
} else if (mask & 1) {
|
||||
LMIC_enableChannel(chnum);
|
||||
} else {
|
||||
LMIC_disableChannel(chnum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !DISABLE_JOIN
|
||||
|
||||
void LMICuslike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer) {
|
||||
os_copyMem(
|
||||
pStateBuffer->channelMap,
|
||||
LMIC.channelMap,
|
||||
sizeof(LMIC.channelMap)
|
||||
);
|
||||
pStateBuffer->activeChannels125khz = LMIC.activeChannels125khz;
|
||||
pStateBuffer->activeChannels500khz = LMIC.activeChannels500khz;
|
||||
}
|
||||
|
||||
void LMICuslike_restoreAdrState(const lmic_saved_adr_state_t *pStateBuffer) {
|
||||
os_copyMem(
|
||||
LMIC.channelMap,
|
||||
pStateBuffer->channelMap,
|
||||
sizeof(LMIC.channelMap)
|
||||
);
|
||||
LMIC.activeChannels125khz = pStateBuffer->activeChannels125khz;
|
||||
LMIC.activeChannels500khz = pStateBuffer->activeChannels500khz;
|
||||
}
|
||||
|
||||
|
||||
bit_t LMICuslike_compareAdrState(const lmic_saved_adr_state_t *pStateBuffer) {
|
||||
return memcmp(pStateBuffer->channelMap, LMIC.channelMap, sizeof(LMIC.channelMap)) != 0;
|
||||
}
|
||||
|
||||
#endif // CFG_LMIC_US_like
|
||||
|
39
src/lmic/lmic_us_like.h
Executable file → Normal file
39
src/lmic/lmic_us_like.h
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyright (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -43,6 +43,9 @@
|
||||
#define IS_CHANNEL_500khz(c) (c>=64 && c<72)
|
||||
#define ENABLED_CHANNEL(chnl) ((LMIC.channelMap[(chnl >> 4)] & (1<<(chnl & 0x0F))) != 0)
|
||||
|
||||
// library functions: called from bandplan
|
||||
void LMICuslike_initJoinLoop(void);
|
||||
|
||||
// provide the isValidBeacon1 function -- int for bool.
|
||||
static inline int
|
||||
LMICuslike_isValidBeacon1(const uint8_t *d) {
|
||||
@ -60,8 +63,14 @@ LMICuslike_isValidBeacon1(const uint8_t *d) {
|
||||
// provide a default LMICbandplan_joinAcceptChannelClear()
|
||||
#define LMICbandplan_joinAcceptChannelClear() do { } while (0)
|
||||
|
||||
// no CFList on joins for US-like plans
|
||||
#define LMICbandplan_hasJoinCFlist() (0)
|
||||
/// \brief there's a CFList on joins for US-like plans
|
||||
#define LMICbandplan_hasJoinCFlist() (1)
|
||||
|
||||
/// \brief process CFLists from JoinAccept for EU-like regions
|
||||
void LMICuslike_processJoinAcceptCFList(void);
|
||||
/// \brief by default, EU-like plans use LMICuslike_processJoinAcceptCFList
|
||||
#define LMICbandplan_processJoinAcceptCFList LMICuslike_processJoinAcceptCFList
|
||||
|
||||
|
||||
#define LMICbandplan_advanceBeaconChannel() \
|
||||
do { LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; } while (0)
|
||||
@ -77,24 +86,36 @@ void LMICuslike_initDefaultChannels(bit_t fJoin);
|
||||
#define LMICbandplan_setSessionInitDefaultChannels() \
|
||||
do { /* nothing */} while (0)
|
||||
|
||||
u1_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap);
|
||||
bit_t LMICuslike_canMapChannels(u1_t chpage, u2_t chmap);
|
||||
#define LMICbandplan_canMapChannels(chpage, chmap) LMICuslike_canMapChannels(chpage, chmap)
|
||||
|
||||
bit_t LMICuslike_mapChannels(u1_t chpage, u2_t chmap);
|
||||
#define LMICbandplan_mapChannels(chpage, chmap) LMICuslike_mapChannels(chpage, chmap)
|
||||
|
||||
ostime_t LMICuslike_nextTx(ostime_t now);
|
||||
#define LMICbandplan_nextTx(now) LMICuslike_nextTx(now)
|
||||
|
||||
void LMICuslike_initJoinLoop(void);
|
||||
#define LMICbandplan_initJoinLoop() LMICuslike_initJoinLoop()
|
||||
|
||||
ostime_t LMICuslike_nextJoinState(void);
|
||||
#define LMICbandplan_nextJoinState() LMICuslike_nextJoinState();
|
||||
|
||||
static inline ostime_t LMICeulike_nextJoinTime(ostime_t now) {
|
||||
static inline ostime_t LMICuslike_nextJoinTime(ostime_t now) {
|
||||
return now;
|
||||
}
|
||||
#define LMICbandplan_nextJoinTime(now) LMICeulike_nextJoinTime(now)
|
||||
#define LMICbandplan_nextJoinTime(now) LMICuslike_nextJoinTime(now)
|
||||
|
||||
#define LMICbandplan_init() \
|
||||
do { /* nothing */ } while (0)
|
||||
|
||||
void LMICuslike_saveAdrState(lmic_saved_adr_state_t *pStateBuffer);
|
||||
#define LMICbandplan_saveAdrState(pState) LMICuslike_saveAdrState(pState)
|
||||
|
||||
bit_t LMICuslike_compareAdrState(const lmic_saved_adr_state_t *pStateBuffer);
|
||||
#define LMICbandplan_compareAdrState(pState) LMICuslike_compareAdrState(pState)
|
||||
|
||||
void LMICuslike_restoreAdrState(const lmic_saved_adr_state_t *pStateBuffer);
|
||||
#define LMICbandplan_restoreAdrState(pState) LMICuslike_restoreAdrState(pState)
|
||||
|
||||
bit_t LMICuslike_isDataRateFeasible(dr_t dr);
|
||||
#define LMICbandplan_isDataRateFeasible(dr) LMICuslike_isDataRateFeasible(dr)
|
||||
|
||||
#endif // _lmic_us_like_h_
|
||||
|
0
src/lmic/lmic_util.c
Executable file → Normal file
0
src/lmic/lmic_util.c
Executable file → Normal file
0
src/lmic/lmic_util.h
Executable file → Normal file
0
src/lmic/lmic_util.h
Executable file → Normal file
303
src/lmic/lorabase.h
Executable file → Normal file
303
src/lmic/lorabase.h
Executable file → Normal file
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* Copyritght (c) 2017 MCCI Corporation.
|
||||
* Copyright (c) 2017-2021 MCCI Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -44,7 +44,15 @@ typedef u1_t cr_t;
|
||||
typedef u1_t sf_t;
|
||||
typedef u1_t bw_t;
|
||||
typedef u1_t dr_t;
|
||||
typedef u2_t rxsyms_t;
|
||||
|
||||
// Radio parameter set (encodes SF/BW/CR/IH/NOCRC)
|
||||
// 2..0: Spreading factor
|
||||
// 4..3: bandwidth: 0 == 125kHz, 1 == 250 kHz, 2 == 500 kHz. 3 == reserved.
|
||||
// 6..5: coding rate: 0 == 4/5, 1 == 4/6, 2 == 4/7, 3 == 4/8
|
||||
// 7: nocrc: 0 == with crc, 1 == without crc
|
||||
// 15..8: Implicit header control: 0 ==> none, 1..0xFF ==> length in bytes.
|
||||
|
||||
typedef u2_t rps_t;
|
||||
TYPEDEF_xref2rps_t;
|
||||
|
||||
@ -52,7 +60,7 @@ enum { ILLEGAL_RPS = 0xFF };
|
||||
|
||||
// Global maximum frame length
|
||||
enum { STD_PREAMBLE_LEN = 8 };
|
||||
enum { MAX_LEN_FRAME = 64 };
|
||||
enum { MAX_LEN_FRAME = LMIC_MAX_FRAME_LENGTH };
|
||||
enum { LEN_DEVNONCE = 2 };
|
||||
enum { LEN_ARTNONCE = 3 };
|
||||
enum { LEN_NETID = 3 };
|
||||
@ -97,6 +105,8 @@ enum _dr_code_t {
|
||||
// post conditions from this block: symbols used by general code that is not
|
||||
// ostensiblly region-specific.
|
||||
// DR_DFLTMIN must be defined as a suitable substititute value if we get a bogus DR
|
||||
// It is misnamed, it should be the maximum DR (which is the minimum SF) for
|
||||
// 125 kHz.
|
||||
// DR_PAGE is used only for a non-supported debug system, but should be defined.
|
||||
// CHNL_DNW2 is the channel to be used for RX2
|
||||
// FREQ_DNW2 is the frequency to be used for RX2
|
||||
@ -217,28 +227,28 @@ enum _dr_configured_t {
|
||||
};
|
||||
# endif // LMIC_DR_LEGACY
|
||||
|
||||
#elif defined(CFG_au921) // =========================================
|
||||
#elif defined(CFG_au915) // =========================================
|
||||
|
||||
#include "lorabase_au921.h"
|
||||
#include "lorabase_au915.h"
|
||||
|
||||
// per 2.5.3: not implemented
|
||||
#define LMIC_ENABLE_TxParamSetupReq 0
|
||||
// per 2.5.3: must be implemented
|
||||
#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
|
||||
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 { FREQ_PING = AU921_500kHz_DNFBASE + 0*AU921_500kHz_DNFSTEP }; // default ping freq
|
||||
enum { DR_PING = AU921_DR_SF10CR }; // default ping DR
|
||||
enum { FREQ_PING = AU915_500kHz_DNFBASE + 0*AU915_500kHz_DNFSTEP }; // default ping freq
|
||||
enum { DR_PING = AU915_DR_SF10CR }; // default ping DR
|
||||
//enum { CHNL_DNW2 = 0 };
|
||||
enum { FREQ_DNW2 = AU921_500kHz_DNFBASE + 0*AU921_500kHz_DNFSTEP };
|
||||
enum { DR_DNW2 = AU921_DR_SF12CR }; // DR8
|
||||
enum { FREQ_DNW2 = AU915_500kHz_DNFBASE + 0*AU915_500kHz_DNFSTEP };
|
||||
enum { DR_DNW2 = AU915_DR_SF12CR }; // DR8
|
||||
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 { LMIC_REGION_EIRP = AU921_LMIC_REGION_EIRP }; // region uses EIRP
|
||||
enum { LMIC_REGION_EIRP = AU915_LMIC_REGION_EIRP }; // region uses EIRP
|
||||
|
||||
enum {
|
||||
// Beacon frame format AU DR10/SF10 500kHz
|
||||
@ -255,20 +265,20 @@ enum {
|
||||
|
||||
# if LMIC_DR_LEGACY
|
||||
enum _dr_configured_t {
|
||||
DR_SF12 = AU921_DR_SF12,
|
||||
DR_SF11 = AU921_DR_SF11,
|
||||
DR_SF10 = AU921_DR_SF10,
|
||||
DR_SF9 = AU921_DR_SF9,
|
||||
DR_SF8 = AU921_DR_SF8,
|
||||
DR_SF7 = AU921_DR_SF7,
|
||||
DR_SF8C = AU921_DR_SF8C,
|
||||
DR_NONE = AU921_DR_NONE,
|
||||
DR_SF12CR = AU921_DR_SF12CR,
|
||||
DR_SF11CR = AU921_DR_SF11CR,
|
||||
DR_SF10CR = AU921_DR_SF10CR,
|
||||
DR_SF9CR = AU921_DR_SF9CR,
|
||||
DR_SF8CR = AU921_DR_SF8CR,
|
||||
DR_SF7CR = AU921_DR_SF7CR
|
||||
DR_SF12 = AU915_DR_SF12,
|
||||
DR_SF11 = AU915_DR_SF11,
|
||||
DR_SF10 = AU915_DR_SF10,
|
||||
DR_SF9 = AU915_DR_SF9,
|
||||
DR_SF8 = AU915_DR_SF8,
|
||||
DR_SF7 = AU915_DR_SF7,
|
||||
DR_SF8C = AU915_DR_SF8C,
|
||||
DR_NONE = AU915_DR_NONE,
|
||||
DR_SF12CR = AU915_DR_SF12CR,
|
||||
DR_SF11CR = AU915_DR_SF11CR,
|
||||
DR_SF10CR = AU915_DR_SF10CR,
|
||||
DR_SF9CR = AU915_DR_SF9CR,
|
||||
DR_SF8CR = AU915_DR_SF8CR,
|
||||
DR_SF7CR = AU915_DR_SF7CR
|
||||
};
|
||||
# endif // LMIC_DR_LEGACY
|
||||
|
||||
@ -319,6 +329,51 @@ enum _dr_configured_t {
|
||||
};
|
||||
# endif // LMIC_DR_LEGACY
|
||||
|
||||
#elif defined(CFG_kr920) // ==============================================
|
||||
|
||||
#include "lorabase_kr920.h"
|
||||
|
||||
// per 2.8.3 (1.0.3 2.9.3): is not implemented
|
||||
#define LMIC_ENABLE_TxParamSetupReq 0
|
||||
|
||||
enum { DR_DFLTMIN = KR920_DR_SF12 }; // DR2
|
||||
// DR_PAGE is a debugging parameter
|
||||
enum { DR_PAGE = DR_PAGE_KR920 };
|
||||
|
||||
enum { FREQ_PING = KR920_FBCN }; // default ping freq
|
||||
enum { DR_PING = KR920_DR_SF9 }; // default ping DR: DR3
|
||||
enum { FREQ_DNW2 = KR920_FDOWN };
|
||||
enum { DR_DNW2 = KR920_DR_SF12 };
|
||||
enum { CHNL_BCN = 11 };
|
||||
enum { FREQ_BCN = KR920_FBCN };
|
||||
enum { DR_BCN = KR920_DR_SF9 };
|
||||
enum { AIRTIME_BCN = 144384 }; // micros
|
||||
enum { LMIC_REGION_EIRP = KR920_LMIC_REGION_EIRP }; // region uses EIRP
|
||||
|
||||
enum {
|
||||
// Beacon frame format KR SF9
|
||||
OFF_BCN_NETID = 0,
|
||||
OFF_BCN_TIME = 2,
|
||||
OFF_BCN_CRC1 = 6,
|
||||
OFF_BCN_INFO = 8,
|
||||
OFF_BCN_LAT = 9,
|
||||
OFF_BCN_LON = 12,
|
||||
OFF_BCN_CRC2 = 15,
|
||||
LEN_BCN = 17
|
||||
};
|
||||
|
||||
# if LMIC_DR_LEGACY
|
||||
enum _dr_configured_t {
|
||||
DR_SF12 = KR920_DR_SF12,
|
||||
DR_SF11 = KR920_DR_SF11,
|
||||
DR_SF10 = KR920_DR_SF10,
|
||||
DR_SF9 = KR920_DR_SF9,
|
||||
DR_SF8 = KR920_DR_SF8,
|
||||
DR_SF7 = KR920_DR_SF7,
|
||||
DR_NONE = KR920_DR_NONE
|
||||
};
|
||||
# endif // LMIC_DR_LEGACY
|
||||
|
||||
#elif defined(CFG_in866) // ==============================================
|
||||
|
||||
#include "lorabase_in866.h"
|
||||
@ -390,6 +445,13 @@ enum {
|
||||
LEN_JA = 17,
|
||||
LEN_JAEXT = 17+16
|
||||
};
|
||||
|
||||
enum {
|
||||
// JoinAccept CFList types
|
||||
LORAWAN_JoinAccept_CFListType_FREQUENCIES = 0, ///< the CFList contains 5 frequencies
|
||||
LORAWAN_JoinAccept_CFListType_MASK = 1, ///< the CFList contains channel-mask data
|
||||
};
|
||||
|
||||
enum {
|
||||
// Data frame format
|
||||
OFF_DAT_HDR = 0,
|
||||
@ -414,7 +476,6 @@ enum {
|
||||
HDR_FTYPE_DADN = 0x60, // data (unconfirmed) dn
|
||||
HDR_FTYPE_DCUP = 0x80, // data confirmed up
|
||||
HDR_FTYPE_DCDN = 0xA0, // data confirmed dn
|
||||
HDR_FTYPE_REJOIN = 0xC0, // rejoin for roaming
|
||||
HDR_FTYPE_PROP = 0xE0
|
||||
};
|
||||
enum {
|
||||
@ -423,7 +484,7 @@ enum {
|
||||
enum {
|
||||
// Bitfields in frame control octet
|
||||
FCT_ADREN = 0x80,
|
||||
FCT_ADRARQ = 0x40,
|
||||
FCT_ADRACKReq = 0x40,
|
||||
FCT_ACK = 0x20,
|
||||
FCT_MORE = 0x10, // also in DN direction: Class B indicator
|
||||
FCT_OPTLEN = 0x0F,
|
||||
@ -432,6 +493,11 @@ enum {
|
||||
// In UP direction: signals class B enabled
|
||||
FCT_CLASSB = FCT_MORE
|
||||
};
|
||||
|
||||
enum {
|
||||
LWAN_FCtrl_FOptsLen_MAX = 0x0Fu, // maximum size of embedded MAC commands
|
||||
};
|
||||
|
||||
enum {
|
||||
NWKID_MASK = (int)0xFE000000,
|
||||
NWKID_BITS = 7
|
||||
@ -440,65 +506,78 @@ enum {
|
||||
// MAC uplink commands downwlink too
|
||||
enum {
|
||||
// Class A
|
||||
MCMD_LCHK_REQ = 0x02, // - LinkCheckReq : -
|
||||
MCMD_LADR_ANS = 0x03, // - LinkADRAnd : u1:7-3:RFU, 3/2/1: pow/DR/Ch ACK
|
||||
MCMD_DCAP_ANS = 0x04, // - DutyCycleAns : -
|
||||
MCMD_DN2P_ANS = 0x05, // - RxParamSetupAns : u1:7-2:RFU 1/0:datarate/channel ack
|
||||
MCMD_DEVS_ANS = 0x06, // - DevStatusAns : u1:battery 0,1-254,255=?, u1:7-6:RFU,5-0:margin(-32..31)
|
||||
MCMD_SNCH_ANS = 0x07, // - NewChannelAns : u1: 7-2=RFU, 1/0:DR/freq ACK
|
||||
MCMD_RXTimingSetupAns = 0x08, // : -
|
||||
MCMD_TxParamSetupAns = 0x09, // : -
|
||||
MCMD_DIChannelAns = 0x0A, // : u1: [7-2]:RFU 1:exists 0:OK
|
||||
MCMD_DeviceTimeReq = 0x0D,
|
||||
MCMD_LinkCheckReq = 0x02, // -
|
||||
MCMD_LinkADRAns = 0x03, // u1:7-3:RFU, 3/2/1: pow/DR/Ch ACK
|
||||
MCMD_DutyCycleAns = 0x04, // -
|
||||
MCMD_RXParamSetupAns = 0x05, // u1:7-2:RFU 1/0:datarate/channel ack
|
||||
MCMD_DevStatusAns = 0x06, // u1:battery 0,1-254,255=?, u1:7-6:RFU,5-0:margin(-32..31)
|
||||
MCMD_NewChannelAns = 0x07, // u1: 7-2=RFU, 1/0:DR/freq ACK
|
||||
MCMD_RXTimingSetupAns = 0x08, // -
|
||||
MCMD_TxParamSetupAns = 0x09, // -
|
||||
MCMD_DlChannelAns = 0x0A, // u1: [7-2]:RFU 1:exists 0:OK
|
||||
MCMD_DeviceTimeReq = 0x0D, // -
|
||||
|
||||
// Class B
|
||||
MCMD_PING_IND = 0x10, // - pingability indic : u1: 7=RFU, 6-4:interval, 3-0:datarate
|
||||
MCMD_PING_ANS = 0x11, // - ack ping freq : u1: 7-1:RFU, 0:freq ok
|
||||
MCMD_BCNI_REQ = 0x12, // - next beacon start : -
|
||||
MCMD_PingSlotInfoReq = 0x10, // u1: 7=RFU, 6-4:interval, 3-0:datarate
|
||||
MCMD_PingSlotChannelAns = 0x11, // u1: 7-1:RFU, 0:freq ok
|
||||
MCMD_BeaconInfoReq = 0x12, // - (DEPRECATED)
|
||||
MCMD_BeaconFreqAns = 0x13, // u1: 7-1:RFU, 0:freq ok
|
||||
};
|
||||
|
||||
// MAC downlink commands
|
||||
enum {
|
||||
// Class A
|
||||
MCMD_LCHK_ANS = 0x02, // LinkCheckAns : u1:margin 0-254,255=unknown margin / u1:gwcnt LinkCheckReq
|
||||
MCMD_LADR_REQ = 0x03, // LinkADRReq : u1:DR/TXPow, u2:chmask, u1:chpage/repeat
|
||||
MCMD_DCAP_REQ = 0x04, // DutyCycleReq : u1:255 dead [7-4]:RFU, [3-0]:cap 2^-k
|
||||
MCMD_DN2P_SET = 0x05, // RXParamSetupReq : u1:7-4:RFU/3-0:datarate, u3:freq
|
||||
MCMD_DEVS_REQ = 0x06, // DevStatusReq : -
|
||||
MCMD_SNCH_REQ = 0x07, // NewChannelReq : u1:chidx, u3:freq, u1:DRrange
|
||||
MCMD_RXTimingSetupReq = 0x08, // : u1: [7-4]:RFU [3-0]: Delay 1-15s (0 => 1)
|
||||
MCMD_TxParamSetupReq = 0x09, // : u1: [7-6]:RFU [5:4]: dl dwell/ul dwell [3:0] max EIRP
|
||||
MCMD_DIChannelReq = 0x0A, // : u1: channel, u3: frequency
|
||||
MCMD_DeviceTimeAns = 0x0D,
|
||||
MCMD_LinkCheckAns = 0x02, // u1:margin 0-254,255=unknown margin / u1:gwcnt LinkCheckReq
|
||||
MCMD_LinkADRReq = 0x03, // u1:DR/TXPow, u2:chmask, u1:chpage/repeat
|
||||
MCMD_DutyCycleReq = 0x04, // u1:255 dead [7-4]:RFU, [3-0]:cap 2^-k
|
||||
MCMD_RXParamSetupReq = 0x05, // u1:7-4:RFU/3-0:datarate, u3:freq
|
||||
MCMD_DevStatusReq = 0x06, // -
|
||||
MCMD_NewChannelReq = 0x07, // u1:chidx, u3:freq, u1:DRrange
|
||||
MCMD_RXTimingSetupReq = 0x08, // u1: [7-4]:RFU [3-0]: Delay 1-15s (0 => 1)
|
||||
MCMD_TxParamSetupReq = 0x09, // u1: [7-6]:RFU [5:4]: dl dwell/ul dwell [3:0] max EIRP
|
||||
MCMD_DlChannelReq = 0x0A, // u1: channel, u3: frequency
|
||||
MCMD_DeviceTimeAns = 0x0D, // u4: seconds since epoch, u1: fractional second
|
||||
|
||||
// Class B
|
||||
MCMD_PING_SET = 0x11, // set ping freq : u3: freq
|
||||
MCMD_BCNI_ANS = 0x12, // next beacon start : u2: delay(in TUNIT millis), u1:channel
|
||||
MCMD_PingSlotInfoAns = 0x10, // -
|
||||
MCMD_PingSlotChannelReq = 0x11, // u3: freq, u1:dr [7-4]:RFU [3:0]:datarate
|
||||
MCMD_BeaconTimingAns = 0x12, // u2: delay(in TUNIT millis), u1:channel (DEPRECATED)
|
||||
MCMD_BeaconFreqReq = 0x13, // u3: freq
|
||||
};
|
||||
|
||||
enum {
|
||||
MCMD_BCNI_TUNIT = 30 // time unit of delay value in millis
|
||||
MCMD_BeaconTimingAns_TUNIT = 30 // time unit of delay value in millis
|
||||
};
|
||||
enum {
|
||||
MCMD_LADR_ANS_RFU = 0xF8, // RFU bits
|
||||
MCMD_LADR_ANS_POWACK = 0x04, // 0=not supported power level
|
||||
MCMD_LADR_ANS_DRACK = 0x02, // 0=unknown data rate
|
||||
MCMD_LADR_ANS_CHACK = 0x01, // 0=unknown channel enabled
|
||||
MCMD_LinkADRAns_RFU = 0xF8, // RFU bits
|
||||
MCMD_LinkADRAns_PowerACK = 0x04, // 0=not supported power level
|
||||
MCMD_LinkADRAns_DataRateACK = 0x02, // 0=unknown data rate
|
||||
MCMD_LinkADRAns_ChannelACK = 0x01, // 0=unknown channel enabled
|
||||
};
|
||||
enum {
|
||||
MCMD_DN2P_ANS_RFU = 0xF8, // RFU bits
|
||||
MCMD_DN2P_ANS_RX1DrOffsetAck = 0x04, // 0=dr2 not allowed
|
||||
MCMD_DN2P_ANS_DRACK = 0x02, // 0=unknown data rate
|
||||
MCMD_DN2P_ANS_CHACK = 0x01, // 0=unknown channel enabled
|
||||
MCMD_RXParamSetupAns_RFU = 0xF8, // RFU bits
|
||||
MCMD_RXParamSetupAns_RX1DrOffsetAck = 0x04, // 0=dr2 not allowed
|
||||
MCMD_RXParamSetupAns_RX2DataRateACK = 0x02, // 0=unknown data rate
|
||||
MCMD_RXParamSetupAns_ChannelACK = 0x01, // 0=unknown channel enabled
|
||||
};
|
||||
enum {
|
||||
MCMD_SNCH_ANS_RFU = 0xFC, // RFU bits
|
||||
MCMD_SNCH_ANS_DRACK = 0x02, // 0=unknown data rate
|
||||
MCMD_SNCH_ANS_FQACK = 0x01, // 0=rejected channel frequency
|
||||
MCMD_NewChannelAns_RFU = 0xFC, // RFU bits
|
||||
MCMD_NewChannelAns_DataRateACK = 0x02, // 0=unknown data rate
|
||||
MCMD_NewChannelAns_ChannelACK = 0x01, // 0=rejected channel frequency
|
||||
};
|
||||
enum {
|
||||
MCMD_PING_ANS_RFU = 0xFE,
|
||||
MCMD_PING_ANS_FQACK = 0x01
|
||||
MCMD_RXTimingSetupReq_RFU = 0xF0, // RFU bits
|
||||
MCMD_RXTimingSetupReq_Delay = 0x0F, // delay in secs, 1..15; 0 is mapped to 1.
|
||||
};
|
||||
enum {
|
||||
MCMD_DlChannelAns_RFU = 0xFC, // RFU bits
|
||||
MCMD_DlChannelAns_FreqACK = 0x02, // 0 = uplink frequency not defined for this channel
|
||||
MCMD_DlChannelAns_ChannelACK = 0x01, // 0 = rejected channel freq
|
||||
};
|
||||
enum {
|
||||
MCMD_PingSlotFreqAns_RFU = 0xFC,
|
||||
MCMD_PingSlotFreqAns_DataRateACK = 0x02,
|
||||
MCMD_PingSlotFreqAns_ChannelACK = 0x01,
|
||||
};
|
||||
|
||||
enum {
|
||||
@ -508,65 +587,30 @@ enum {
|
||||
MCMD_DEVS_BATT_NOINFO = 0xFF, // unknown battery level
|
||||
};
|
||||
|
||||
// Bit fields byte#3 of MCMD_LADR_REQ payload
|
||||
// Bit fields byte#3 of MCMD_LinkADRReq payload
|
||||
enum {
|
||||
MCMD_LADR_CHP_USLIKE_SPECIAL = 0x50, // first special for us-like
|
||||
MCMD_LADR_CHP_BANK = 0x50, // special: bits are banks.
|
||||
MCMD_LADR_CHP_125ON = 0x60, // special channel page enable, bits applied to 64..71
|
||||
MCMD_LADR_CHP_125OFF = 0x70, // special channel page: disble 125K, bits apply to 64..71
|
||||
MCMD_LADR_N3RFU_MASK = 0x80,
|
||||
MCMD_LADR_CHPAGE_MASK = 0xF0,
|
||||
MCMD_LADR_REPEAT_MASK = 0x0F,
|
||||
MCMD_LADR_REPEAT_1 = 0x01,
|
||||
MCMD_LADR_CHPAGE_1 = 0x10
|
||||
MCMD_LinkADRReq_Redundancy_RFU = 0x80, ///< mask for RFU bit
|
||||
MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK= 0x70, ///< mask for the channel-mask control field.
|
||||
MCMD_LinkADRReq_Redundancy_NbTrans_MASK = 0x0F, ///< mask for the `NbTrans` (repetition) field.
|
||||
|
||||
MCMD_LinkADRReq_ChMaskCntl_EULIKE_DIRECT = 0x00, ///< EU-like: direct masking for EU
|
||||
MCMD_LinkADRReq_ChMaskCntl_EULIKE_ALL_ON = 0x60, ///< EU-like: enable everything.
|
||||
|
||||
MCMD_LinkADRReq_ChMaskCntl_USLIKE_500K = 0x40, ///< US-like: mask is for the 8 us-like 500 kHz channels
|
||||
MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL = 0x50, ///< US-like: first special for us-like
|
||||
MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK = 0x50, ///< US-like: special: bits are banks.
|
||||
MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON = 0x60, ///< US-like: special channel page enable, bits applied to 64..71
|
||||
MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF = 0x70, ///< US-like: special channel page: disable 125K, bits apply to 64..71
|
||||
|
||||
MCMD_LinkADRReq_ChMaskCntl_CN470_ALL_ON = 0x60, ///< CN-470: turn all on for China.
|
||||
};
|
||||
// Bit fields byte#0 of MCMD_LADR_REQ payload
|
||||
|
||||
// Bit fields byte#0 of MCMD_LinkADRReq payload
|
||||
enum {
|
||||
MCMD_LADR_DR_MASK = 0xF0,
|
||||
MCMD_LADR_POW_MASK = 0x0F,
|
||||
MCMD_LADR_DR_SHIFT = 4,
|
||||
MCMD_LADR_POW_SHIFT = 0,
|
||||
#if defined(CFG_eu868) // TODO(tmm@mcci.com): complete refactor.
|
||||
EU868_MCMD_LADR_SF12 = EU868_DR_SF12<<4,
|
||||
EU868_MCMD_LADR_SF11 = EU868_DR_SF11<<4,
|
||||
EU868_MCMD_LADR_SF10 = EU868_DR_SF10<<4,
|
||||
EU868_MCMD_LADR_SF9 = EU868_DR_SF9 <<4,
|
||||
EU868_MCMD_LADR_SF8 = EU868_DR_SF8 <<4,
|
||||
EU868_MCMD_LADR_SF7 = EU868_DR_SF7 <<4,
|
||||
EU868_MCMD_LADR_SF7B = EU868_DR_SF7B<<4,
|
||||
EU868_MCMD_LADR_FSK = EU868_DR_FSK <<4,
|
||||
|
||||
EU868_MCMD_LADR_20dBm = 0,
|
||||
EU868_MCMD_LADR_14dBm = 1,
|
||||
EU868_MCMD_LADR_11dBm = 2,
|
||||
EU868_MCMD_LADR_8dBm = 3,
|
||||
EU868_MCMD_LADR_5dBm = 4,
|
||||
EU868_MCMD_LADR_2dBm = 5,
|
||||
#elif defined(CFG_us915)
|
||||
US915_MCMD_LADR_SF10 = US915_DR_SF10<<4,
|
||||
US915_MCMD_LADR_SF9 = US915_DR_SF9 <<4,
|
||||
US915_MCMD_LADR_SF8 = US915_DR_SF8 <<4,
|
||||
US915_MCMD_LADR_SF7 = US915_DR_SF7 <<4,
|
||||
US915_MCMD_LADR_SF8C = US915_DR_SF8C<<4,
|
||||
US915_MCMD_LADR_SF12CR = US915_DR_SF12CR<<4,
|
||||
US915_MCMD_LADR_SF11CR = US915_DR_SF11CR<<4,
|
||||
US915_MCMD_LADR_SF10CR = US915_DR_SF10CR<<4,
|
||||
US915_MCMD_LADR_SF9CR = US915_DR_SF9CR<<4,
|
||||
US915_MCMD_LADR_SF8CR = US915_DR_SF8CR<<4,
|
||||
US915_MCMD_LADR_SF7CR = US915_DR_SF7CR<<4,
|
||||
|
||||
US915_MCMD_LADR_30dBm = 0,
|
||||
US915_MCMD_LADR_28dBm = 1,
|
||||
US915_MCMD_LADR_26dBm = 2,
|
||||
US915_MCMD_LADR_24dBm = 3,
|
||||
US915_MCMD_LADR_22dBm = 4,
|
||||
US915_MCMD_LADR_20dBm = 5,
|
||||
US915_MCMD_LADR_18dBm = 6,
|
||||
US915_MCMD_LADR_16dBm = 7,
|
||||
US915_MCMD_LADR_14dBm = 8,
|
||||
US915_MCMD_LADR_12dBm = 9,
|
||||
US915_MCMD_LADR_10dBm = 10
|
||||
#endif
|
||||
MCMD_LinkADRReq_DR_MASK = 0xF0,
|
||||
MCMD_LinkADRReq_POW_MASK = 0x0F,
|
||||
MCMD_LinkADRReq_DR_SHIFT = 4,
|
||||
MCMD_LinkADRReq_POW_SHIFT = 0,
|
||||
};
|
||||
|
||||
// bit fields of the TxParam request
|
||||
@ -601,17 +645,8 @@ static inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc) {
|
||||
#define MAKERPS(sf,bw,cr,ih,nocrc) ((rps_t)((sf) | ((bw)<<3) | ((cr)<<5) | ((nocrc)?(1<<7):0) | ((ih&0xFF)<<8)))
|
||||
// Two frames with params r1/r2 would interfere on air: same SFx + BWx
|
||||
static inline int sameSfBw(rps_t r1, rps_t r2) { return ((r1^r2)&0x1F) == 0; }
|
||||
|
||||
extern CONST_TABLE(u1_t, _DR2RPS_CRC)[];
|
||||
static inline rps_t updr2rps (dr_t dr) { return (rps_t)TABLE_GET_U1(_DR2RPS_CRC, dr+1); }
|
||||
static inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); }
|
||||
static inline int isFasterDR (dr_t dr1, dr_t dr2) { return dr1 > dr2; }
|
||||
static inline int isSlowerDR (dr_t dr1, dr_t dr2) { return dr1 < dr2; }
|
||||
static inline dr_t incDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+2)==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate
|
||||
static inline dr_t decDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr )==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate
|
||||
static inline dr_t assertDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)==ILLEGAL_RPS ? (dr_t)DR_DFLTMIN : dr; } // force into a valid DR
|
||||
static inline bit_t validDR (dr_t dr) { return TABLE_GET_U1(_DR2RPS_CRC, dr+1)!=ILLEGAL_RPS; } // in range
|
||||
static inline dr_t lowerDR (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps
|
||||
|
||||
//
|
||||
// BEG: Keep in sync with lorabase.hpp
|
||||
|
10
src/lmic/lorabase_as923.h
Executable file → Normal file
10
src/lmic/lorabase_as923.h
Executable file → Normal file
@ -68,6 +68,7 @@ enum {
|
||||
AS923_FREQ_MAX = 928000000
|
||||
};
|
||||
enum {
|
||||
AS923_JP_TX_EIRP_MAX_DBM = 13, // 13 dBm = 19.95mW < 20mW
|
||||
AS923_TX_EIRP_MAX_DBM = 16 // 16 dBm
|
||||
};
|
||||
enum { DR_PAGE_AS923 = 0x10 * (LMIC_REGION_as923 - 1) };
|
||||
@ -93,4 +94,13 @@ enum { AS923_V102_TX_CAP = 100 }; // v1.0.2 allows 100%
|
||||
# define AS923_TX_CAP AS923_V102_TX_CAP
|
||||
#endif
|
||||
|
||||
// TxParam defaults
|
||||
enum {
|
||||
// initial value of UplinkDwellTime before TxParamSetupReq received.
|
||||
AS923_INITIAL_TxParam_UplinkDwellTime = 1,
|
||||
// initial value of DownlinkDwellTime before TxParamSetupReq received.
|
||||
AS923_INITIAL_TxParam_DownlinkDwellTime = 1,
|
||||
AS923_UPLINK_DWELL_TIME_osticks = sec2osticks(20),
|
||||
};
|
||||
|
||||
#endif /* _lorabase_as923_h_ */
|
||||
|
69
src/lmic/lorabase_au921.h → src/lmic/lorabase_au915.h
Executable file → Normal file
69
src/lmic/lorabase_au921.h → src/lmic/lorabase_au915.h
Executable file → Normal file
@ -28,8 +28,8 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _lorabase_au921_h_
|
||||
#define _lorabase_au921_h_
|
||||
#ifndef _lorabase_au915_h_
|
||||
#define _lorabase_au915_h_
|
||||
|
||||
#ifndef _LMIC_CONFIG_PRECONDITIONS_H_
|
||||
# include "lmic_config_preconditions.h"
|
||||
@ -37,48 +37,53 @@
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Basic definitions for AS921 (always in scope)
|
||||
| Basic definitions for AU 915 (always in scope)
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
// Frequency plan for AU 921 MHz
|
||||
enum _dr_as921_t {
|
||||
AU921_DR_SF12 = 0,
|
||||
AU921_DR_SF11,
|
||||
AU921_DR_SF10,
|
||||
AU921_DR_SF9,
|
||||
AU921_DR_SF8,
|
||||
AU921_DR_SF7,
|
||||
AU921_DR_SF8C,
|
||||
AU921_DR_NONE,
|
||||
// Frequency plan for AU 915 MHz
|
||||
enum _dr_au915_t {
|
||||
AU915_DR_SF12 = 0,
|
||||
AU915_DR_SF11,
|
||||
AU915_DR_SF10,
|
||||
AU915_DR_SF9,
|
||||
AU915_DR_SF8,
|
||||
AU915_DR_SF7,
|
||||
AU915_DR_SF8C,
|
||||
AU915_DR_NONE,
|
||||
// Devices behind a router:
|
||||
AU921_DR_SF12CR = 8,
|
||||
AU921_DR_SF11CR,
|
||||
AU921_DR_SF10CR,
|
||||
AU921_DR_SF9CR,
|
||||
AU921_DR_SF8CR,
|
||||
AU921_DR_SF7CR
|
||||
AU915_DR_SF12CR = 8,
|
||||
AU915_DR_SF11CR,
|
||||
AU915_DR_SF10CR,
|
||||
AU915_DR_SF9CR,
|
||||
AU915_DR_SF8CR,
|
||||
AU915_DR_SF7CR
|
||||
};
|
||||
|
||||
// Default frequency plan for AU 921MHz
|
||||
// Default frequency plan for AU 915MHz
|
||||
enum {
|
||||
AU921_125kHz_UPFBASE = 915200000,
|
||||
AU921_125kHz_UPFSTEP = 200000,
|
||||
AU921_500kHz_UPFBASE = 915900000,
|
||||
AU921_500kHz_UPFSTEP = 1600000,
|
||||
AU921_500kHz_DNFBASE = 923300000,
|
||||
AU921_500kHz_DNFSTEP = 600000
|
||||
AU915_125kHz_UPFBASE = 915200000,
|
||||
AU915_125kHz_UPFSTEP = 200000,
|
||||
AU915_500kHz_UPFBASE = 915900000,
|
||||
AU915_500kHz_UPFSTEP = 1600000,
|
||||
AU915_500kHz_DNFBASE = 923300000,
|
||||
AU915_500kHz_DNFSTEP = 600000
|
||||
};
|
||||
enum {
|
||||
AU921_FREQ_MIN = 915000000,
|
||||
AU921_FREQ_MAX = 928000000
|
||||
AU915_FREQ_MIN = 915000000,
|
||||
AU915_FREQ_MAX = 928000000
|
||||
};
|
||||
enum {
|
||||
AU921_TX_EIRP_MAX_DBM = 30 // 30 dBm
|
||||
AU915_TX_EIRP_MAX_DBM = 30 // 30 dBm
|
||||
};
|
||||
enum {
|
||||
// initial value of UplinkDwellTime before TxParamSetupReq received.
|
||||
AU915_INITIAL_TxParam_UplinkDwellTime = 1,
|
||||
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_ */
|
0
src/lmic/lorabase_eu868.h
Executable file → Normal file
0
src/lmic/lorabase_eu868.h
Executable file → Normal file
0
src/lmic/lorabase_in866.h
Executable file → Normal file
0
src/lmic/lorabase_in866.h
Executable file → Normal file
84
src/lmic/lorabase_kr920.h
Normal file
84
src/lmic/lorabase_kr920.h
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 IBM Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2017, 2019 MCCI Corporation
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the <organization> nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _lorabase_kr920_h_
|
||||
#define _lorabase_kr920_h_
|
||||
|
||||
#ifndef _LMIC_CONFIG_PRECONDITIONS_H_
|
||||
# include "lmic_config_preconditions.h"
|
||||
#endif
|
||||
|
||||
/****************************************************************************\
|
||||
|
|
||||
| Basic definitions for KR920 (always in scope)
|
||||
|
|
||||
\****************************************************************************/
|
||||
|
||||
enum _dr_kr920_t {
|
||||
KR920_DR_SF12 = 0, // DR0
|
||||
KR920_DR_SF11, // DR1
|
||||
KR920_DR_SF10, // DR2
|
||||
KR920_DR_SF9, // DR3
|
||||
KR920_DR_SF8, // DR4
|
||||
KR920_DR_SF7, // DR5
|
||||
KR920_DR_NONE
|
||||
};
|
||||
|
||||
// There is no dwell-time or duty-cycle limitation for IN
|
||||
//
|
||||
// max power: 30dBM
|
||||
//
|
||||
// freq datarates
|
||||
enum {
|
||||
KR920_F1 = 922100000, // SF7-12 (DR0-5)
|
||||
KR920_F2 = 922300000, // SF7-12 (DR0-5)
|
||||
KR920_F3 = 922500000, // SF7-12 (DR0-5)
|
||||
KR920_FBCN = 923100000, // beacon/ping
|
||||
KR920_F14DBM = 922100000, // Allows 14 dBm (not 10) if >= this.
|
||||
KR920_FDOWN = 921900000, // RX2 downlink frequency
|
||||
};
|
||||
enum {
|
||||
KR920_FREQ_MIN = 920900000,
|
||||
KR920_FREQ_MAX = 923300000
|
||||
};
|
||||
enum {
|
||||
KR920_TX_EIRP_MAX_DBM = 14, // 14 dBm for most
|
||||
KR920_TX_EIRP_MAX_DBM_LOW = 10, // 10 dBm for some
|
||||
};
|
||||
enum { DR_PAGE_KR920 = 0x10 * (LMIC_REGION_kr920 - 1) };
|
||||
|
||||
enum { KR920_LMIC_REGION_EIRP = 1 }; // region uses EIRP
|
||||
|
||||
enum { KR920_LBT_US = 128 }; // microseconds of LBT time.
|
||||
|
||||
enum { KR920_LBT_DB_MAX = -80 }; // maximum channel strength in dB; if TX
|
||||
// we measure more than this, we don't tx.
|
||||
|
||||
#endif /* _lorabase_in866_h_ */
|
6
src/lmic/lorabase_us915.h
Executable file → Normal file
6
src/lmic/lorabase_us915.h
Executable file → Normal file
@ -77,6 +77,12 @@ enum {
|
||||
// on an 64-channel bandplan. See code
|
||||
// that computes tx power.
|
||||
};
|
||||
|
||||
enum {
|
||||
US915_LinkAdrReq_POW_MAX_1_0_2 = 0xA,
|
||||
US915_LinkAdrReq_POW_MAX_1_0_3 = 0xE,
|
||||
};
|
||||
|
||||
enum { DR_PAGE_US915 = 0x10 * (LMIC_REGION_us915 - 1) };
|
||||
|
||||
enum { US915_LMIC_REGION_EIRP = 0 }; // region doesn't use EIRP, uses tx power
|
||||
|
64
src/lmic/lorawan_spec_compliance.h
Normal file
64
src/lmic/lorawan_spec_compliance.h
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
|
||||
Module: lorawan_spec_compliance.h
|
||||
|
||||
Function:
|
||||
Details from the LoRaWAN specification for compliance.
|
||||
|
||||
Copyright notice and license info:
|
||||
See LICENSE file accompanying this project.
|
||||
|
||||
Author:
|
||||
Terry Moore, MCCI Corporation March 2019
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _lorawan_spec_COMPLIANCE_H_ /* prevent multiple includes */
|
||||
#define _lorawan_spec_COMPLIANCE_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"{
|
||||
#endif
|
||||
|
||||
enum {
|
||||
// the port for MAC commands
|
||||
LORAWAN_PORT_MAC = 0u,
|
||||
// the first port available for applications
|
||||
LORAWAN_PORT_USER_MIN = 1u,
|
||||
// the last port available for applications
|
||||
LORAWAN_PORT_USER_MAX = 223u,
|
||||
// the base of the reserved port numbers
|
||||
LORAWAN_PORT_RESERVED = 224u,
|
||||
// the port for the compliance protocol
|
||||
LORAWAN_PORT_COMPLIANCE = LORAWAN_PORT_RESERVED + 0u,
|
||||
};
|
||||
|
||||
enum lowawan_compliance_cmd_e {
|
||||
LORAWAN_COMPLIANCE_CMD_DEACTIVATE = 0u,
|
||||
LORAWAN_COMPLIANCE_CMD_ACTIVATE = 1u,
|
||||
LORAWAN_COMPLIANCE_CMD_SET_CONFIRM = 2u,
|
||||
LORAWAN_COMPLIANCE_CMD_SET_UNCONFIRM = 3u,
|
||||
LORAWAN_COMPLIANCE_CMD_ECHO = 4u,
|
||||
LORAWAN_COMPLIANCE_CMD_LINK = 5u,
|
||||
LORAWAN_COMPLIANCE_CMD_JOIN = 6u,
|
||||
LORAWAN_COMPLIANCE_CMD_CW = 7u,
|
||||
LORAWAN_COMPLIANCE_CMD_MFG_BASE = 0x80u,
|
||||
};
|
||||
|
||||
typedef unsigned char lorawan_compliance_cmd_t;
|
||||
|
||||
// info on specific commands
|
||||
enum {
|
||||
LORAWAN_COMPLIANCE_CMD_ACTIVATE_LEN = 4u,
|
||||
LORAWAN_COMPLIANCE_CMD_ACTIVATE_MAGIC = 1u,
|
||||
|
||||
// Maximum crypto frame size; although the spec says 18, it
|
||||
// is also used for testing max packet size.
|
||||
LORAWAN_COMPLIANCE_CMD_ECHO_LEN_MAX = 242u,
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif /* _lorawan_spec_COMPLIANCE_H_ */
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user