Compare commits

...

88 Commits

Author SHA1 Message Date
Manuel Bl
1057aa09de IRAM_ATTR for get_current_time() 2023-08-07 23:03:22 +02:00
Leif Jakob
61771c0bfa make this function IRAM_ATTR because it's used from IRQ -> otherwise "Cache disabled but cached memory region accessed" if flash is busy 2023-08-07 19:55:31 +02:00
Manuel Bl
89ad39d463 No LMIC clear if it cannot be restored from NVS 2023-06-14 22:07:08 +02:00
Manuel Bl
c32c6b4cd6 Robust struct init for C++ 2023-06-12 22:43:30 +02:00
Manuel Bl
569afe84e9 Weak references for more options to control low speed pins 2023-04-26 10:00:57 +02:00
Leif Jakob
4e0587bb8e use weak references for HAL functions that control low-speed pins - allows override with own symbols 2023-04-25 09:41:37 +02:00
Manuel Bleichenbacher
877784640d Increase version number to 4.2.0-1 2022-09-27 19:20:53 +02:00
Manuel Bleichenbacher
79727fdae2 Merge branch 'FrancoLiberali-master' 2022-09-21 21:00:15 +02:00
Manuel Bleichenbacher
50d45e7fb7 Upgrade to latest LMIC code (v4.2.0-1) 2022-09-21 20:57:23 +02:00
Manuel Bleichenbacher
bbdb44cdd6 Add backward compatibility with v4.x 2022-09-21 20:57:23 +02:00
niki aigner
e7d9caefe1 fix compilation issues 2022-09-21 20:57:23 +02:00
Manuel Bleichenbacher
68d5669833 Upgrade to latest LMIC code (v4.2.0-1) 2022-09-21 20:48:49 +02:00
Manuel Bleichenbacher
46ad09736b Fix for compilation issue in ESP-IDF v5.x 2022-09-21 20:32:46 +02:00
Manuel Bleichenbacher
1b7279518b Add backward compatibility with v4.x 2022-09-21 20:29:52 +02:00
niki aigner
c1517d3c82 fix compilation issues 2022-09-20 18:56:29 +02:00
Franco Liberali
ffcdd12d48 fix configPower for sx1272 2022-07-29 15:24:20 +02:00
Manuel Bleichenbacher
0a7353c146 Assert NSS from SPI callbacks 2022-04-15 12:17:26 +02:00
Manuel Bleichenbacher
54a91a27db Improve SPI robustness (fix incompatibility with ESP32-S2) 2022-03-26 20:06:03 +01:00
Manuel Bleichenbacher
ee8eead337 Improve compatibility with future ESP-IDF versions 2022-01-30 20:27:18 +01:00
Manuel Bl
f06be4c069 Update README with link to Wiki 2021-09-29 21:31:48 +02:00
Manuel Bl
5a7b3b0fcb Fix references in documentation 2021-09-29 19:44:16 +02:00
Manuel Bl
9fd24c65ec Enhanced deep sleep example 2021-09-29 19:40:57 +02:00
Manuel Bl
31b00f05a8 Mention deep sleep in README 2021-09-29 18:57:05 +02:00
Manuel Bl
ba481ceac5 Power off for C++ 2021-09-29 18:02:52 +02:00
Manuel Bl
b626ccb61a Improve robustness of restore from NVS 2021-09-29 17:46:04 +02:00
Manuel Bl
f433e826a7 Save and restore from power off 2021-09-29 16:48:59 +02:00
Manuel Bl
0130928601 Fix ttn_wait_for_idle for MAC cmd processing 2021-09-28 22:38:05 +02:00
Manuel Bl
e71d584fca Save and restore for deep sleep 2021-09-28 17:35:05 +02:00
Manuel Bl
de4297a8f4 Delete Makefiles 2021-09-28 10:17:37 +02:00
Manuel Bl
ee91ccc613 Rename ttn_join() 2021-09-28 10:14:32 +02:00
Manuel Bl
7df10bd6bc Wait for idle 2021-09-26 20:07:57 +02:00
Manuel Bl
bd728887cf Prepare for deep sleep functions 2021-09-26 16:35:42 +02:00
Manuel Bleichenbacher
1df2c50f6f Fix ttn_get_rf_settings() 2021-08-20 20:41:34 +02:00
Manuel Bleichenbacher
8fa345a5d4 Adapted code formatting 2021-07-31 17:03:00 +02:00
Manuel Bleichenbacher
e34dbcb467 Link to new API documentation 2021-07-31 12:51:41 +02:00
Manuel Bleichenbacher
72b878f1d9 Update README with info for v4.0 2021-07-31 12:36:47 +02:00
Manuel Bleichenbacher
17e432d714 Improved API documentation 2021-07-30 23:04:06 +02:00
Manuel Bleichenbacher
5c1db030f2 Improve documentation output 2021-07-29 23:06:13 +02:00
Manuel Bleichenbacher
eb5792fbb9 Default to subband 2 2021-07-28 23:52:10 +02:00
Manuel Bleichenbacher
37d7f8d517 Methods for setting data rate and TX power 2021-07-28 23:48:54 +02:00
Manuel Bleichenbacher
62e829b0d3 Example for C 2021-07-27 23:01:04 +02:00
Manuel Bleichenbacher
6df4b40122 Allow all zeroes for AppEUI/JoinEUI 2021-07-27 22:37:22 +02:00
Manuel Bleichenbacher
7fa43dbbdb Allow disabling subband seletion 2021-07-27 22:08:00 +02:00
Manuel Bleichenbacher
ba908c0b93 Configue sub-band 2021-07-26 22:29:20 +02:00
Manuel Bleichenbacher
281ba52155 C implementation for TheThingsNetwork 2021-07-25 23:53:54 +02:00
Manuel Bleichenbacher
36edf92944 Remove TTNProvisioning.h 2021-07-25 22:15:21 +02:00
Manuel Bleichenbacher
f421db44d7 Convert logging to C 2021-07-25 20:49:02 +02:00
Manuel Bleichenbacher
8e2886db27 Convert hal_esp32 to C 2021-07-25 20:03:16 +02:00
Manuel Bleichenbacher
973a7c41c8 Convert TTNProvisioning to C 2021-07-25 17:00:08 +02:00
Manuel Bleichenbacher
99bab17d4b Upgrade to mcci-catena/arduino-lmic 4.0.1-pre 2021-07-25 14:39:11 +02:00
Manuel Bl
1913840679 Prepare for release 3.3 2021-01-05 21:57:24 +01:00
Manuel Bl
5c695fd223 Upgrade to version 3.3 of arduino-lmic 2021-01-05 21:57:11 +01:00
Manuel Bl
3086b1bb58 Static word-aligned buffer for SPI 2021-01-05 21:26:36 +01:00
Manuel Bl
f57c505d7e Merge branch 'iram_isr_fix' of https://github.com/vladcebo/ttn-esp32 into vladcebo-iram_isr_fix 2021-01-05 21:04:57 +01:00
Manuel Bl
ba22c5a745 Fix excessiv writing of keys to NVS 2021-01-05 20:55:14 +01:00
Cebotari Vladislav
5b22f228da
Prevent hal_ticks() from going into .text region
This will fix an issue when the flash is disabled (due to OTA update for example) and the cpu crashes with cache disabled access from ISR (when TTN executes)
2021-01-02 17:32:30 +00:00
Markus Frey
84b2f88f0c Fix excessive writing of keys to NVS 2020-09-27 16:21:04 +02:00
Manuel Bl
fffe7bd8b3 Improve documentation 2020-08-02 19:53:02 +02:00
Manuel Bl
bb41c4960f README updated for 3.2.0 2020-08-02 10:19:58 +02:00
Manuel Bl
f70ad2a996 Version 2.3 2020-08-01 18:32:10 +02:00
Manuel Bl
fdacae73f8 Add rssi() function 2020-08-01 18:30:47 +02:00
Manuel Bl
9489f34542 RF parameter monitoring 2020-08-01 18:21:42 +02:00
Manuel Bl
2a31ef0096 Update example build configuration 2020-08-01 10:28:25 +02:00
Manuel Bl
e15d936bb4 Merge send_recv example into hello_world 2020-08-01 10:23:30 +02:00
Manuel Bl
a8cd58214d Modify example for PlatformIO.ini 2020-08-01 10:13:11 +02:00
Manuel Bl
b3e0b567b3 Merge branch 'cc32d9-dev' into dev 2020-07-31 21:54:26 +02:00
Manuel Bl
0853fe05ec Rename reset options 2020-07-31 21:54:09 +02:00
Manuel Bl
625968cd99 Add shutdown function 2020-07-31 20:14:40 +02:00
Manuel Bl
8d7d157445 Add shutdown function 2020-07-31 20:14:31 +02:00
Manuel Bl
b73ed23a97 Enable/disable ADR 2020-07-31 17:48:11 +02:00
Manuel Bl
513ac44268 Upgrade to LMIC version 3.2.0 2020-07-31 16:32:28 +02:00
cc32d9
45778d2186 New config option: TTN_RADIO_RST_KEEP_ASSERTED 2020-05-25 23:00:00 +02:00
Manuel Bl
e29477fa7e Update to latest LMIC version (3.0.99.10) 2020-01-01 17:00:47 +01:00
Manuel Bl
3036d16e0d Latest version of issue453 branch 2019-12-03 22:13:45 +01:00
Manuel Bl
2d09703fc6 More detail LMIC logging 2019-10-16 23:48:12 +02:00
Manuel Bl
4df057042c Improve compatibility between ESP-IDF v3.3 and v4.x 2019-10-14 22:09:16 +02:00
Manuel Bl
48668b3295 Compatibility with ESP-IDF v4.x 2019-10-13 23:43:17 +02:00
Manuel Bl
baa6c93d5f Task notification instead of queue for LMIC task 2019-10-13 20:26:15 +02:00
Manuel Bl
43080636bc More comments 2019-10-12 00:02:31 +02:00
Manuel Bl
a8fd68ca5e More logging cases 2019-10-10 23:09:32 +02:00
Manuel Bl
3ac63d4146 New event handling 2019-10-09 23:06:18 +02:00
Manuel Bl
dc0bfefe0e Ring buffer for detailed logging 2019-10-08 20:52:08 +02:00
Manuel Bl
6adf69ea0c Add more comments 2019-10-07 23:59:57 +02:00
Manuel Bl
c2a5bd8374 Cleanup timers 2019-10-07 23:24:51 +02:00
Manuel Bl
a601c2b2bf New job scheduling implementation 2019-10-07 23:16:55 +02:00
Manuel Bl
d7d4a40c4f Get rid of lmic_pins 2019-10-06 21:49:25 +02:00
Manuel Bl
1e1cadf400 Integrate development version of 3.0.99 2019-10-02 21:37:48 +02:00
Manuel Bleichenbacher
cf94bba4af Add library as extra component for examples (cmake) 2019-04-25 19:50:39 +02:00
113 changed files with 15072 additions and 2807 deletions

3
.clang-format Normal file
View File

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

View File

@ -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

View File

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

View File

@ -9,6 +9,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
View File

@ -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

View File

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

View File

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

View File

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

5
doc/.gitignore vendored Normal file
View File

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

2619
doc/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

21
doc/doc.dox Normal file
View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

5
doc/generate.sh Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)

View File

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

View File

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

View File

@ -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())
{

View File

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

View File

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

View File

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

View File

@ -1,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)

View File

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

View File

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

View File

@ -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);

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -0,0 +1,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));
}

View File

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

View File

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

View File

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

View File

@ -1,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)

View File

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

View File

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

View File

@ -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);

View File

@ -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)

View File

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

View File

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

View File

@ -2,37 +2,403 @@
*
* 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.
*******************************************************************************/
/**
* @file TheThingsNetwork.h
*/
#ifndef _THETHINGSNETWORK_H_
#define _THETHINGSNETWORK_H_
#include <stdint.h>
#include "driver/spi_master.h"
#include "ttn.h"
/**
* @brief Constant for indicating that a pin is not connected
* @brief Integer data type for specified the port of an uplink or downlink message.
*
* @deprecated Use @ref ttn_port_t instead.
*/
#define TTN_NOT_CONNECTED 0xff
typedef uint8_t port_t;
typedef ttn_port_t port_t;
/**
* @brief Response codes
*/
enum TTNResponseCode
{
kTTNErrorTransmissionFailed = -1,
kTTNErrorUnexpected = -10,
kTTNSuccessfulTransmission = 1,
kTTNSuccessfulReceive = 2
/** @brief Transmission failed error */
kTTNErrorTransmissionFailed = TTN_ERROR_TRANSMISSION_FAILED,
/** @brief Unexpected or internal error */
kTTNErrorUnexpected = TTN_ERROR_UNEXPECTED,
/** @brief Successful transmission of an uplink message */
kTTNSuccessfulTransmission = TTN_SUCCESSFUL_TRANSMISSION,
/** @brief Successful receipt of a downlink message */
kTTNSuccessfulReceive = TTN_SUCCESSFUL_RECEIVE
};
/**
* @brief RX/TX window
*/
enum TTNRxTxWindow
{
/**
* @brief Outside RX/TX window
*/
kTTNIdleWindow = TTN_WINDOW_IDLE,
/**
* @brief Transmission window (up to RX1 window)
*/
kTTNTxWindow = TTN_WINDOW_TX,
/**
* @brief Reception window 1 (up to RX2 window)
*/
kTTNRx1Window = TTN_WINDOW_RX1,
/**
* @brief Reception window 2
*/
kTTNRx2Window = TTN_WINDOW_RX2
};
/**
* @brief Spreading Factor
*/
enum TTNSpreadingFactor
{
/**
* @brief Unused / undefined spreading factor
*/
kTTNSFNone = TTN_SF_NONE,
/**
* @brief Frequency Shift Keying (FSK)
*/
kTTNFSK = TTN_FSK,
/**
* @brief Spreading Factor 7 (SF7)
*/
kTTNSF7 = TTN_SF7,
/**
* @brief Spreading Factor 8 (SF8)
*/
kTTNSF8 = TTN_SF8,
/**
* @brief Spreading Factor 9 (SF9)
*/
kTTNSF9 = TTN_SF9,
/**
* @brief Spreading Factor 10 (SF10)
*/
kTTNSF10 = TTN_SF10,
/**
* @brief Spreading Factor 11 (SF11)
*/
kTTNSF11 = TTN_SF11,
/**
* @brief Spreading Factor 12 (SF12)
*/
kTTNSF12 = TTN_SF12
};
/**
* @brief Bandwidth
*/
enum TTNBandwidth
{
/**
* @brief Undefined/unused bandwidth
*/
kTTNBWNone = TTN_BW_NONE,
/**
* @brief Bandwidth of 125 kHz
*/
kTTNBW125 = TTN_BW_125,
/**
* @brief Bandwidth of 250 kHz
*/
kTTNBW250 = TTN_BW_250,
/**
* @brief Bandwidth of 500 kHz
*/
kTTNBW500 = TTN_BW_500
};
/**
* @brief Data Rate
*
* Note that the spreading factor, bandwidth, bit rate and maximum message
* size associated with each data rate depends on the region.
*/
enum TTNDataRate
{
/**
* @brief Data rate for region AS923 using SF12 and 125 kHz bandwidth.
*/
kTTNDataRate_AS923_SF12 = 0,
/**
* @brief Data rate for region AS923 using SF11 and 125 kHz bandwidth.
*/
kTTNDataRate_AS923_SF11 = 1,
/**
* @brief Data rate for region AS923 using SF10 and 125 kHz bandwidth.
*/
kTTNDataRate_AS923_SF10 = 2,
/**
* @brief Data rate for region AS923 using SF9 and 125 kHz bandwidth.
*/
kTTNDataRate_AS923_SF9 = 3,
/**
* @brief Data rate for region AS923 using SF8 and 125 kHz bandwidth.
*/
kTTNDataRate_AS923_SF8 = 4,
/**
* @brief Data rate for region AS923 using SF7 and 125 kHz bandwidth.
*/
kTTNDataRate_AS923_SF7_BW125 = 5,
/**
* @brief Data rate for region AS923 using SF7 and 250 kHz bandwidth.
*/
kTTNDataRate_AS923_SF7_BW250 = 6,
/**
* @brief Data rate for region AS923 using FSK and 50 kpbs.
*/
kTTNDataRate_AS923_FSK = 7,
/**
* @brief Data rate for region AU915 using SF12 and 125 kHz bandwidth.
*/
kTTNDataRate_AU915_SF12 = 0,
/**
* @brief Data rate for region AU915 using SF11 and 125 kHz bandwidth.
*/
kTTNDataRate_AU915_SF11 = 1,
/**
* @brief Data rate for region AU915 using SF10 and 125 kHz bandwidth.
*/
kTTNDataRate_AU915_SF10 = 2,
/**
* @brief Data rate for region AU915 using SF9 and 125 kHz bandwidth.
*/
kTTNDataRate_AU915_SF9 = 3,
/**
* @brief Data rate for region AU915 using SF8 and 125 kHz bandwidth.
*/
kTTNDataRate_AU915_SF8 = 4,
/**
* @brief Data rate for region AU915 using SF7 and 125 kHz bandwidth.
*/
kTTNDataRate_AU915_SF7 = 5,
/**
* @brief Data rate for region AU915 using SF8 and 500 kHz bandwidth.
*/
kTTNDataRate_AU915_SF8_BW500 = 6,
/**
* @brief Data rate for region AU915 using SF12 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_AU915_SF12_BW500 = 8,
/**
* @brief Data rate for region AU915 using SF11 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_AU915_SF11_BW500 = 9,
/**
* @brief Data rate for region AU915 using SF10 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_AU915_SF10_BW500 = 10,
/**
* @brief Data rate for region AU915 using SF9 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_AU915_SF9_BW500 = 11,
/**
* @brief Data rate for region AU915 using SF8 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_AU915_SF8_BW500_DR12 = 12,
/**
* @brief Data rate for region AU915 using SF7 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_AU915_SF7_BW500 = 13,
/**
* @brief Data rate for region EU868 using SF12 and 125 kHz bandwidth.
*/
kTTNDataRate_EU868_SF12 = 0,
/**
* @brief Data rate for region EU868 using SF11 and 125 kHz bandwidth.
*/
kTTNDataRate_EU868_SF11 = 1,
/**
* @brief Data rate for region EU868 using SF10 and 125 kHz bandwidth.
*/
kTTNDataRate_EU868_SF10 = 2,
/**
* @brief Data rate for region EU868 using SF9 and 125 kHz bandwidth.
*/
kTTNDataRate_EU868_SF9 = 3,
/**
* @brief Data rate for region EU868 using SF8 and 125 kHz bandwidth.
*/
kTTNDataRate_EU868_SF8 = 4,
/**
* @brief Data rate for region EU868 using SF7 and 125 kHz bandwidth.
*/
kTTNDataRate_EU868_SF7_BW125 = 5,
/**
* @brief Data rate for region EU868 using SF7 and 250 kHz bandwidth.
*/
kTTNDataRate_EU868_SF7_BW250 = 6,
/**
* @brief Data rate for region EU868 using FSK and 50 kpbs.
*/
kTTNDataRate_EU868_FSK = 7,
/**
* @brief Data rate for region IN866 using SF12 and 125 kHz bandwidth.
*/
kTTNDataRate_IN866_SF12 = 0,
/**
* @brief Data rate for region IN866 using SF11 and 125 kHz bandwidth.
*/
kTTNDataRate_IN866_SF11 = 1,
/**
* @brief Data rate for region IN866 using SF10 and 125 kHz bandwidth.
*/
kTTNDataRate_IN866_SF10 = 2,
/**
* @brief Data rate for region IN866 using SF9 and 125 kHz bandwidth.
*/
kTTNDataRate_IN866_SF9 = 3,
/**
* @brief Data rate for region IN866 using SF8 and 125 kHz bandwidth.
*/
kTTNDataRate_IN866_SF8 = 4,
/**
* @brief Data rate for region IN866 using SF7 and 125 kHz bandwidth.
*/
kTTNDataRate_IN866_SF7 = 5,
/**
* @brief Data rate for region IN866 using FSK and 50 kpbs.
*/
kTTNDataRate_IN866_FSK = 7,
/**
* @brief Data rate for region KR920 using SF12 and 125 kHz bandwidth.
*/
kTTNDataRate_KR920_SF12 = 0,
/**
* @brief Data rate for region KR920 using SF11 and 125 kHz bandwidth.
*/
kTTNDataRate_KR920_SF11 = 1,
/**
* @brief Data rate for region KR920 using SF10 and 125 kHz bandwidth.
*/
kTTNDataRate_KR920_SF10 = 2,
/**
* @brief Data rate for region KR920 using SF9 and 125 kHz bandwidth.
*/
kTTNDataRate_KR920_SF9 = 3,
/**
* @brief Data rate for region KR920 using SF8 and 125 kHz bandwidth.
*/
kTTNDataRate_KR920_SF8 = 4,
/**
* @brief Data rate for region KR920 using SF7 and 125 kHz bandwidth.
*/
kTTNDataRate_KR920_SF7 = 5,
/**
* @brief Data rate for region US915 using SF10 and 125 kHz bandwidth.
*/
kTTNDataRate_US915_SF10 = 0,
/**
* @brief Data rate for region US915 using SF9 and 125 kHz bandwidth.
*/
kTTNDataRate_US915_SF9 = 1,
/**
* @brief Data rate for region US915 using SF8 and 125 kHz bandwidth.
*/
kTTNDataRate_US915_SF8 = 2,
/**
* @brief Data rate for region US915 using SF7 and 125 kHz bandwidth.
*/
kTTNDataRate_US915_SF7 = 3,
/**
* @brief Data rate for region US915 using SF8 and 500 kHz bandwidth.
*/
kTTNDataRate_US915_SF8_BW500 = 4,
/**
* @brief Data rate for region US915 using SF12 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_US915_SF12_BW500 = 8,
/**
* @brief Data rate for region US915 using SF11 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_US915_SF11_BW500 = 9,
/**
* @brief Data rate for region US915 using SF10 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_US915_SF10_BW500 = 10,
/**
* @brief Data rate for region US915 using SF9 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_US915_SF9_BW500 = 11,
/**
* @brief Data rate for region US915 using SF8 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_US915_SF8_BW500_DR12 = 12,
/**
* @brief Data rate for region US915 using SF7 and 500 kHz bandwidth.
*
* Reserved for future applications.
*/
kTTNDataRate_US915_SF7_BW500 = 13,
/**
* @brief Default data rate for joining
*/
kTTNDRJoinDdefault
};
/**
* @brief RF settings for TX or RX
*/
struct TTNRFSettings
{
/**
* @brief Spreading Factor (SF)
*/
TTNSpreadingFactor spreadingFactor;
/**
* @brief Bandwidth (BW)
*/
TTNBandwidth bandwidth;
/**
* @brief Frequency, in Hz
*/
uint32_t frequency;
};
/**
@ -42,140 +408,334 @@ enum TTNResponseCode
* @param length number of received bytes
* @param port port the message was received on
*/
typedef void (*TTNMessageCallback)(const uint8_t* payload, size_t length, port_t port);
typedef void (*TTNMessageCallback)(const uint8_t *payload, size_t length, ttn_port_t port);
/**
* @brief TTN device
*
* The 'TheThingsNetwork' class enables ESP32 devices with SX1272/73/76/77/78/79 LoRaWAN chips
* This class enables ESP32 devices with SX1272/73/76/77/78/79 LoRaWAN chips
* to communicate via The Things Network.
*
* Only one instance of this class must be created.
* Only one instance of this class may be created.
*/
class TheThingsNetwork
{
public:
/**
* @brief Construct a new The Things Network device instance.
* @brief Constructs a new The Things Network device instance.
*/
TheThingsNetwork();
TheThingsNetwork()
{
ttn_init();
}
/**
* @brief Destroy the The Things Network device instance.
* @brief Destroys the The Things Network device instance.
*/
~TheThingsNetwork();
/**
* @brief Reset the LoRaWAN radio.
*
* Does not clear provisioned keys.
*/
void reset();
~TheThingsNetwork()
{
}
/**
* @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.
*
* The SPI bus must be first configured using spi_bus_initialize(). Then it is passed as the first parameter.
* Additionally, 'gpio_install_isr_service()' must be called to initialize the GPIO ISR handler service.
*
* @param spi_host The SPI bus/peripherial to use (SPI_HOST, HSPI_HOST or VSPI_HOST).
* @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 (TTN_NOT_CONNECTED if not connected)
* @param rst The GPIO pin number connected to the radio chip's RST pin (TTN_NOT_CONNECTED if not connected)
* @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 configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1);
void configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1)
{
ttn_configure_pins(spi_host, nss, rxtx, rst, dio0, dio1);
}
/**
* @brief Sets the information needed to activate the device via OTAA, without actually activating.
* @brief Sets the frequency sub-band to be used.
*
* The provided device EUI, app EUI and app key are saved in non-volatile memory. Before
* this function is called, 'nvs_flash_init' must have been called once.
* 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.
*
* Call join() without arguments to activate.
* The sub-band must be set before joining or sending the first message.
*
* @param devEui Device EUI (16 character string with hexadecimal data)
* @param appEui Application EUI of the device (16 character string with hexadecimal data)
* @param appKey App Key of the device (32 character string with hexadecimal data)
* @return true if the provisioning was successful
* @return false if the provisioning failed
* 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)
*/
bool provision(const char *devEui, const char *appEui, const char *appKey);
void setSubband(int band)
{
ttn_set_subband(band);
}
/**
* @brief Sets the information needed to activate the device via OTAA, using the MAC to generate the device EUI
* and without actually activating.
* @brief Sets the keys needed to activate the device via OTAA, without activating it.
*
* The generated device EUI and the provided app EUI and app key are saved in non-volatile memory. Before
* this function is called, 'nvs_flash_init' must have been called once.
* 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.
*
* The device EUI is generated by retrieving the ESP32's WiFi MAC address and expanding it into a device EUI
* In order to reduce flash wear, this function detects if the keys have not changed
* and will not write them again.
*
* Call @ref join() to activate the device.
*
* @param devEui DevEUI (16 character string with hexadecimal data)
* @param appEui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param appKey AppKey of the device (32 character string with hexadecimal data)
* @return `true` if the provisioning was successful, `false` if the provisioning failed
*/
bool provision(const char *devEui, const char *appEui, const char *appKey)
{
return ttn_provision(devEui, appEui, appKey);
}
/**
* @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 join() to activate the device.
*
* @param devEui DevEUI (16 character string with hexadecimal data)
* @param appEui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param appKey AppKey of the device (32 character string with hexadecimal data)
* @return `true` if the provisioning was successful, `false` if the provisioning failed
*/
bool provisionTransiently(const char *devEui, const char *appEui, const char *appKey)
{
return ttn_provision_transiently(devEui, appEui, appKey);
}
/**
* @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 Device EUI field in the TTN console.
* This hexadecimal data can be entered into the DevEUI field in the TTN console.
*
* Generating the device EUI from the MAC address allows to flash the same app EUI and app key to a batch of
* devices. However, using the same app key for multiple devices is insecure. Only use this approach if
* 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 join() without arguments to activate.
* Call @ref join() to activate.
*
* @param appEui Application EUI of the device (16 character string with hexadecimal data)
* @param appEui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param appKey AppKey of the device (32 character string with hexadecimal data)
* @return true if the provisioning was successful
* @return false if the provisioning failed
* @return `true` if the provisioning was successful, `false` if the provisioning failed
*/
bool provisionWithMAC(const char *appEui, const char *appKey);
bool provisionWithMAC(const char *appEui, const char *appKey)
{
return ttn_provision_with_mac(appEui, appKey);
}
/**
* @brief Start task that listens on configured UART for AT commands.
* @brief Starts task listening on configured UART for AT commands.
*
* Run 'make menuconfig' to configure it.
* Run `make menuconfig` to configure it.
*/
void startProvisioningTask();
void startProvisioningTask()
{
ttn_start_provisioning_task();
}
/**
* @brief Wait until the device EUI, app EUI and app key have been provisioned
* via the provisioning task.
* @brief Waits until the DevEUI, AppEUI/JoinEUI and AppKey have been provisioned
* by the provisioning task.
*
* If device is already provisioned (stored data in NVS, call to 'provision()'
* or call to 'join(const char*, const char*, const char*)', this function
* If the device has already been provisioned (stored data in NVS, call of provision()
* or call of @ref join(const char*, const char*, const char*), this function
* immediately returns.
*/
void waitForProvisioning();
void waitForProvisioning()
{
ttn_wait_for_provisioning();
}
/**
* @brief Activate the device via OTAA.
* @brief Activates the device via OTAA using previously provisioned keys.
*
* The app EUI, app key and dev EUI must already have been provisioned by a call to 'provision()'.
* Before this function is called, 'nvs_flash_init' must have been called once.
* The DevEUI, AppEUI/JoinEUI and AppKey must have already been provisioned by a call
* to @ref provision() or @ref provisionWithMAC().
* 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 succeful
* @return false if the activation failed
* @return `true` if the activation was succesful, `false` if the activation failed
*/
bool join();
bool join()
{
return ttn_join();
}
/**
* @brief Set the device EUI, app EUI and app key and activate the device via OTAA.
* @brief Activates the device via OTAA using the provided keys.
*
* The device EUI, app EUI and app key are NOT saved in non-volatile memory.
* 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 devEui Device EUI (16 character string with hexadecimal data)
* @param appEui Application EUI of the device (16 character string with hexadecimal data)
* @param devEui DevEUI (16 character string with hexadecimal data)
* @param appEui AppEUI/JoinEUI of the device (16 character string with hexadecimal data)
* @param appKey AppKey of the device (32 character string with hexadecimal data)
* @return true if the activation was succeful
* @return false if the activation failed
* @return `true` if the activation was succesful, `false` if the activation failed
*/
bool join(const char *devEui, const char *appEui, const char *appKey);
bool join(const char *devEui, const char *appEui, const char *appKey)
{
return ttn_join_with_keys(devEui, appEui, appKey);
}
/**
* @brief Transmit a message
* @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 join() or @ref join(const char*, const char*, const char*)
* 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 resumeAfterDeepSleep()
{
return ttn_resume_after_deep_sleep();
}
/**
* @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 join() or @ref join(const char*, const char*, const char*)
* 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 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 resumeAfterPowerOff(int off_duration)
{
return ttn_resume_after_power_off(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 busyDuration() to check
* that the TTN device is idle and ready to go to deep sleep.
*
* To restart communication, @ref resumeAfterDeepSleep() must be called.
*/
void prepareForDeepSleep()
{
ttn_prepare_for_deep_sleep();
}
/**
* @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 busyDuration() to check
* that the TTN device is idle and ready to be powered off.
*
* To restart communication, @ref resumeAfterPowerOff(int) must be called.
*
* Before this function is called, `nvs_flash_init()` must have been called once.
*/
void prepareForPowerOff()
{
ttn_prepare_for_power_off();
}
/**
* @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 waitForIdle()
{
ttn_wait_for_idle();
}
/**
* @brief Returns the minimum duration the TTN device is 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 busyDuration()
{
return ttn_busy_duration();
}
/**
* @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 join() or @ref join(const char*, const char*, const char*)
* must be called.
*/
void shutdown()
{
ttn_shutdown();
}
/**
* @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
@ -183,43 +743,164 @@ public:
*
* @param payload bytes to be transmitted
* @param length number of bytes to be transmitted
* @param port port (default to 1)
* @param confirm flag indicating if a confirmation should be requested. Default to 'false'
* @return TkTTNSuccessfulTransmission Successful transmission
* @return kTTNErrorTransmissionFailed Transmission failed
* @return TkTTNErrorUnexpected Unexpected error
* @param port port (defaults to 1)
* @param confirm flag indicating if a confirmation should be requested. Defaults to `false`
* @return @ref kTTNSuccessfulTransmission for successful transmission, @ref kTTNErrorTransmissionFailed for failed
* transmission, @ref kTTNErrorUnexpected for unexpected error
*/
TTNResponseCode transmitMessage(const uint8_t *payload, size_t length, port_t port = 1, bool confirm = false);
TTNResponseCode transmitMessage(const uint8_t *payload, size_t length, ttn_port_t port = 1, bool confirm = false)
{
return static_cast<TTNResponseCode>(ttn_transmit_message(payload, length, port, confirm));
}
/**
* @brief Set the function to be called when a message is received
* @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 'transmitMessage' or 'poll'. The callback is called
* in the task that called any of these functions and it occurs before these functions
* return control to the caller.
* Messages are received as a result of a call to @ref transmitMessage(). 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 onMessage(TTNMessageCallback callback);
void onMessage(TTNMessageCallback callback)
{
ttn_on_message(callback);
}
/**
* @brief Checks if device EUI, app EUI and app key have been stored in non-volatile storage
* or have been provided as by a call to 'join(const char*, const char*, const char*)'.
* @brief Checks if DevEUI, AppEUI/JoinEUI and AppKey have been stored in non-volatile storage
* or have been provided by a call to @ref join(const char*, const char*, const char*)
* or to @ref provisionTransiently(const char*, const char*, const char*).
*
* @return true if they are stored, complete and of the correct size
* @return false otherwise
* @return `true` if they are stored, complete and of the correct size, `false` otherwise
*/
bool isProvisioned();
bool isProvisioned()
{
return ttn_is_provisioned();
}
private:
TTNMessageCallback messageCallback;
/**
* @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 rssiCal RSSI calibration value, in dB
*/
void setRSSICal(int8_t rssiCal)
{
ttn_set_rssi_cal(rssiCal);
}
bool joinCore();
/**
* Returns whether Adaptive Data Rate (ADR) is enabled.
*
* @return `true` if enabled, `false` if disabled
*/
bool adrEnabled()
{
return ttn_adr_enabled();
}
/**
* @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 setAdrEnabled(bool enabled)
{
ttn_set_adr_enabled(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 TTNDataRate)
*/
void setDataRate(TTNDataRate data_rate)
{
ttn_set_data_rate(static_cast<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 setMaxTxPower(int tx_pow)
{
ttn_set_max_tx_pow(tx_pow);
}
/**
* @brief Gets current RX/TX window
* @return window
*/
TTNRxTxWindow rxTxWindow()
{
return static_cast<TTNRxTxWindow>(ttn_rx_tx_window());
}
/**
* @brief Gets the RF settings for the specified window
* @param window RX/TX window (valid values are @ref kTTNTxWindow, @ref kTTNRx1Window and @ref kTTNRx2Window)
*/
TTNRFSettings getRFSettings(TTNRxTxWindow window);
/**
* @brief Gets the RF settings of the last (or ongoing) transmission.
* @return RF settings
*/
TTNRFSettings txSettings()
{
return getRFSettings(kTTNTxWindow);
}
/**
* @brief Gets the RF settings of the last (or ongoing) reception of RX window 1.
* @return RF settings
*/
TTNRFSettings rx1Settings()
{
return getRFSettings(kTTNRx1Window);
}
/**
* @brief Gets the RF settings of the last (or ongoing) reception of RX window 2.
* @return RF settings
*/
TTNRFSettings rx2Settings()
{
return getRFSettings(kTTNRx2Window);
}
/**
* @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 rssi()
{
return ttn_rssi();
}
};
#endif

822
include/ttn.h Normal file
View File

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

View File

@ -11,15 +11,14 @@
"repository": {
"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",

View File

@ -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

View File

@ -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
View File

4
src/aes/other.c Executable file → Normal file
View File

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

View File

@ -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
View File

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

View File

@ -1,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);
}

View File

@ -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
View 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
View 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

File diff suppressed because it is too large Load Diff

512
src/lmic/lmic.h Executable file → Normal file
View 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
View 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,37 +340,110 @@ 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)
void LMICas923_setBcnRxParams(void) {
@ -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
View 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
View 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
View 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_

View 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
View 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
View 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_

View 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
View 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_

View File

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

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

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

771
src/lmic/lmic_compliance.c Normal file
View 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
View 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
View 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
View 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
View 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,33 +223,112 @@ 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);
}
#if !defined(DISABLE_BEACONS)
@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

0
src/lmic/lmic_util.h Executable file → Normal file
View File

303
src/lmic/lorabase.h Executable file → Normal file
View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyritght (c) 2017 MCCI Corporation.
* Copyright (c) 2017-2021 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -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
View File

@ -68,6 +68,7 @@ enum {
AS923_FREQ_MAX = 928000000
};
enum {
AS923_JP_TX_EIRP_MAX_DBM = 13, // 13 dBm = 19.95mW < 20mW
AS923_TX_EIRP_MAX_DBM = 16 // 16 dBm
};
enum { DR_PAGE_AS923 = 0x10 * (LMIC_REGION_as923 - 1) };
@ -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
View 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
View File

0
src/lmic/lorabase_in866.h Executable file → Normal file
View File

84
src/lmic/lorabase_kr920.h Normal file
View 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
View 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

View 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