28 Commits

Author SHA1 Message Date
3036d16e0d Latest version of issue453 branch 2019-12-03 22:13:45 +01:00
2d09703fc6 More detail LMIC logging 2019-10-16 23:48:12 +02:00
4df057042c Improve compatibility between ESP-IDF v3.3 and v4.x 2019-10-14 22:09:16 +02:00
48668b3295 Compatibility with ESP-IDF v4.x 2019-10-13 23:43:17 +02:00
baa6c93d5f Task notification instead of queue for LMIC task 2019-10-13 20:26:15 +02:00
43080636bc More comments 2019-10-12 00:02:31 +02:00
a8fd68ca5e More logging cases 2019-10-10 23:09:32 +02:00
3ac63d4146 New event handling 2019-10-09 23:06:18 +02:00
dc0bfefe0e Ring buffer for detailed logging 2019-10-08 20:52:08 +02:00
6adf69ea0c Add more comments 2019-10-07 23:59:57 +02:00
c2a5bd8374 Cleanup timers 2019-10-07 23:24:51 +02:00
a601c2b2bf New job scheduling implementation 2019-10-07 23:16:55 +02:00
d7d4a40c4f Get rid of lmic_pins 2019-10-06 21:49:25 +02:00
1e1cadf400 Integrate development version of 3.0.99 2019-10-02 21:37:48 +02:00
cf94bba4af Add library as extra component for examples (cmake) 2019-04-25 19:50:39 +02:00
31b859cdf4 Improve example setup 2019-04-19 12:15:30 +02:00
1c88b1f5be PlatformIO library description 2019-04-17 22:25:26 +02:00
f01b29a3b2 Upgrade to version 2.3.2 of mcci-catena/arduino-lmic 2019-04-16 21:36:03 +02:00
d7e8a920c8 Merge branch 'master' into dev 2019-04-16 21:28:28 +02:00
267e04c833 Add cmake files for examples 2019-03-19 21:49:08 +01:00
f899c38663 Merge branch 'daveol-master' 2019-03-19 21:30:21 +01:00
6afdfe4caf Add nvs_flash requirement for cmake 2019-03-16 20:39:01 +01:00
9bb6788a7b Create CMakeLists for cmake compatibility 2019-03-16 20:06:30 +01:00
58116219bb More transformation to C++ 2018-10-27 23:55:36 +02:00
f03b5c51fd Simplify conditional compilation; add region Japan 2018-10-27 19:19:55 +02:00
99420c6a75 Improve constness 2018-10-27 14:49:08 +02:00
47063256a3 Convert provisioning code to C++ 2018-10-27 14:45:55 +02:00
ee08d2a3c1 Simplify SPI code 2018-10-22 12:57:49 +02:00
72 changed files with 6607 additions and 2065 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ sdkconfig
sdkconfig.old
dev_keys.txt
ttn-esp32
.vscode/

View File

@ -28,7 +28,7 @@
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"cppStandard": "c++11",
"intelliSenseMode": "clang-x64"
}
],

18
CMakeLists.txt Normal file
View File

@ -0,0 +1,18 @@
set(COMPONENT_SRCDIRS
"src"
"src/aes"
"src/hal"
"src/lmic"
)
set(COMPONENT_ADD_INCLUDEDIRS
"include"
)
set(COMPONENT_REQUIRES
nvs_flash
)
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,16 +14,22 @@ 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)"
bool "Australia (921 MHz)"
config TTN_LORA_FREQ_AS_923
bool "Asia (923 Mhz)"
bool "Asia (923 MHz)"
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)"
bool "India (866 MHz)"
endchoice
@ -43,26 +49,6 @@ 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
@ -79,22 +65,22 @@ config TTN_BG_TASK_PRIO
choice TTN_PROVISION_UART
prompt "UART for provisioning"
prompt "AT commands"
default TTN_PROVISION_UART_DEFAULT
help
Select whether to use UART for listening for provisioning commands.
Select whether to listen on UART for AT commands.
- Default is to use UART0 on pins GPIO1(TX) and GPIO3(RX).
- If "Custom" is selected, UART0 or UART1 can be chosen,
and any pins can be selected.
- If "None" is selected, the feature is not available.
- If "None" is selected, AT commands are not available.
config TTN_PROVISION_UART_DEFAULT
bool "Default: UART0, TX=GPIO1, RX=GPIO3, 115,200 baud"
bool "Enabled - default settings: UART0, TX=GPIO1, RX=GPIO3, 115,200 baud"
config TTN_PROVISION_UART_CUSTOM
bool "Custom"
bool "Enabled - custom UART settings"
config TTN_PROVISION_UART_NONE
bool "None"
bool "Disabled"
endchoice
choice TTN_PROVISION_UART_NUM

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "esp_event.h"
#include "nvs_flash.h"
@ -89,7 +90,7 @@ extern "C" void app_main(void)
if (ttn.join())
{
printf("Joined.\n");
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, NULL);
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, nullptr);
}
else
{

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "esp_event.h"
#include "nvs_flash.h"
@ -87,7 +88,7 @@ extern "C" void app_main(void)
if (ttn.join())
{
printf("Joined.\n");
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, NULL);
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, nullptr);
}
else
{

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@
*******************************************************************************/
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
#include "esp_event.h"
#include "nvs_flash.h"
@ -82,7 +83,7 @@ extern "C" void app_main(void)
if (ttn.join())
{
printf("Joined.\n");
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, NULL);
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, nullptr);
}
else
{

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "TheThingsNetwork.h"
@ -99,7 +100,7 @@ extern "C" void app_main(void)
if (ttn.join())
{
printf("Joined.\n");
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, NULL);
xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, nullptr);
}
else
{

View File

@ -130,7 +130,7 @@ public:
bool provisionWithMAC(const char *appEui, const char *appKey);
/**
* @brief Start task that listens on configured UART for provisioning commands.
* @brief Start task that listens on configured UART for AT commands.
*
* Run 'make menuconfig' to configure it.
*/
@ -216,6 +216,17 @@ public:
*/
bool isProvisioned();
/**
* @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.
* Default to 10 dB.
*
* @param rssiCal RSSI calibration value, in dB
*/
void setRSSICal(int8_t rssiCal);
private:
TTNMessageCallback messageCallback;

27
library.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "ttn-esp32",
"description": "The Things Network device library for ESP32 (ESP-IDF) and SX127x based devices",
"keywords": "esp32, ttn, espidf, lorawan, sx1276, sx1272, rfm92, rfm95, lmic",
"authors": {
"name": "Manuel Bl",
"email": "manuel.bleichenbacher@gmail.com",
"url": "https://github.com/manuelbl/ttn-esp32",
"maintainer": true
},
"repository": {
"type": "git",
"url": "https://github.com/manuelbl/ttn-esp32.git",
"branch": "dev"
},
"version": "3.0.99",
"license": "MIT License",
"export": {
"include": [
"include",
"src",
"esp_idf_lmic_config.h"
]
},
"frameworks": "espidf",
"platforms": "espressif32"
}

390
src/TTNLogging.cpp Normal file
View File

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

55
src/TTNLogging.h Normal file
View File

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

View File

@ -2,7 +2,7 @@
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018 Manuel Bleichenbacher
* Copyright (c) 2018-2019 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
@ -16,48 +16,28 @@
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "provisioning.h"
#include "TTNProvisioning.h"
#include "lmic/lmic.h"
#include "hal/hal_esp32.h"
#define UART_NUM CONFIG_TTN_PROVISION_UART_NUM
#define MAX_LINE_LENGTH 128
#if defined(TTN_HAS_AT_COMMANDS)
const uart_port_t UART_NUM = (uart_port_t) CONFIG_TTN_PROVISION_UART_NUM;
const int MAX_LINE_LENGTH = 128;
#endif
static const char *TAG = "ttn_prov";
static const char *NVS_FLASH_PARTITION = "ttn";
static const char *NVS_FLASH_KEY_DEV_EUI = "devEui";
static const char *NVS_FLASH_KEY_APP_EUI = "appEui";
static const char *NVS_FLASH_KEY_APP_KEY = "appKey";
static const char* const TAG = "ttn_prov";
static const char* const NVS_FLASH_PARTITION = "ttn";
static const char* const NVS_FLASH_KEY_DEV_EUI = "devEui";
static const char* const NVS_FLASH_KEY_APP_EUI = "appEui";
static const char* const NVS_FLASH_KEY_APP_KEY = "appKey";
static bool provisioning_decode(bool incl_dev_eui, const char *dev_eui, const char *app_eui, const char *app_key);
static void provisioning_task(void* pvParameter);
static void provisioning_add_line_data(int numBytes);
static void provisioning_detect_line_end(int start_at);
static void provisioning_process_line();
static bool read_nvs_value(nvs_handle handle, const char* key, uint8_t* data, size_t expected_length, bool silent);
static bool write_nvs_value(nvs_handle handle, const char* key, const uint8_t* data, size_t len);
static bool hex_str_to_bin(const char *hex, uint8_t *buf, int len);
static int hex_tuple_to_byte(const char *hex);
static int hex_digit_to_val(char ch);
static void bin_to_hex_str(const uint8_t* buf, int len, char* hex);
static char val_to_hex_digit(int val);
static void swap_bytes(uint8_t* buf, int len);
static bool is_all_zeroes(const uint8_t* buf, int len);
static QueueHandle_t uart_queue = NULL;
static char* line_buf;
static int line_length;
static uint8_t last_line_end_char = 0;
static uint8_t global_dev_eui[8];
static uint8_t global_app_eui[8];
static uint8_t global_app_key[16];
static bool have_keys = false;
static bool quit_task = false;
#if defined(CONFIG_TTN_PROVISION_UART_CONFIG_YES)
static void provisioning_config_uart();
#if defined(TTN_HAS_AT_COMMANDS)
void ttn_provisioning_task_caller(void* pvParameter);
#endif
@ -86,22 +66,40 @@ void os_getDevKey (u1_t* buf)
memcpy(buf, global_app_key, 16);
}
// --- Constructor
TTNProvisioning::TTNProvisioning()
: have_keys(false)
#if defined(TTN_HAS_AT_COMMANDS)
, uart_queue(nullptr), line_buf(nullptr), line_length(0), last_line_end_char(0), quit_task(false)
#endif
{
}
// --- Provisioning task
void provisioning_start_task()
#if defined(TTN_HAS_AT_COMMANDS)
void TTNProvisioning::startTask()
{
#if defined(CONFIG_TTN_PROVISION_UART_CONFIG_YES)
provisioning_config_uart();
#if defined(TTN_CONFIG_UART)
configUART();
#endif
esp_err_t err = uart_driver_install(UART_NUM, 2048, 2048, 20, &uart_queue, 0);
ESP_ERROR_CHECK(err);
xTaskCreate(provisioning_task, "provisioning", 2048, NULL, 1, NULL);
xTaskCreate(ttn_provisioning_task_caller, "ttn_provision", 2048, this, 1, nullptr);
}
void provisioning_task(void* pvParameter)
void ttn_provisioning_task_caller(void* pvParameter)
{
TTNProvisioning* provisioning = (TTNProvisioning*)pvParameter;
provisioning->provisioningTask();
}
void TTNProvisioning::provisioningTask()
{
line_buf = (char*)malloc(MAX_LINE_LENGTH + 1);
line_length = 0;
@ -118,7 +116,7 @@ void provisioning_task(void* pvParameter)
switch (event.type)
{
case UART_DATA:
provisioning_add_line_data(event.size);
addLineData(event.size);
break;
case UART_FIFO_OVF:
@ -134,10 +132,10 @@ void provisioning_task(void* pvParameter)
free(line_buf);
uart_driver_delete(UART_NUM);
vTaskDelete(NULL);
vTaskDelete(nullptr);
}
void provisioning_add_line_data(int numBytes)
void TTNProvisioning::addLineData(int numBytes)
{
int n;
top:
@ -149,7 +147,7 @@ top:
int start_at = line_length;
line_length += n;
provisioning_detect_line_end(start_at);
detectLineEnd(start_at);
if (n < numBytes)
{
@ -158,7 +156,7 @@ top:
}
}
void provisioning_detect_line_end(int start_at)
void TTNProvisioning::detectLineEnd(int start_at)
{
top:
for (int p = start_at; p < line_length; p++)
@ -175,7 +173,7 @@ top:
last_line_end_char = ch;
if (p > 0)
provisioning_process_line();
processLine();
memcpy(line_buf, line_buf + p + 1, line_length - p - 1);
line_length -= p + 1;
@ -191,7 +189,7 @@ top:
line_length = 0; // Line too long; flush it
}
void provisioning_process_line()
void TTNProvisioning::processLine()
{
bool is_ok = true;
bool reset_needed = false;
@ -209,14 +207,14 @@ void provisioning_process_line()
char hexbuf[16];
memcpy(binbuf, global_dev_eui, 8);
swap_bytes(binbuf, 8);
bin_to_hex_str(binbuf, 8, hexbuf);
swapBytes(binbuf, 8);
binToHexStr(binbuf, 8, hexbuf);
uart_write_bytes(UART_NUM, hexbuf, 16);
uart_write_bytes(UART_NUM, "-", 1);
memcpy(binbuf, global_app_eui, 8);
swap_bytes(binbuf, 8);
bin_to_hex_str(binbuf, 8, hexbuf);
swapBytes(binbuf, 8);
binToHexStr(binbuf, 8, hexbuf);
uart_write_bytes(UART_NUM, hexbuf, 16);
uart_write_bytes(UART_NUM, "-00000000000000000000000000000000\r\n", 35);
@ -228,7 +226,7 @@ void provisioning_process_line()
{
line_buf[24] = 0;
line_buf[41] = 0;
is_ok = provisioning_decode_keys(line_buf + 8, line_buf + 25, line_buf + 42);
is_ok = decodeKeys(line_buf + 8, line_buf + 25, line_buf + 42);
reset_needed = is_ok;
}
}
@ -238,7 +236,7 @@ void provisioning_process_line()
if (is_ok)
{
line_buf[25] = 0;
is_ok = provisioning_from_mac(line_buf + 9, line_buf + 26);
is_ok = fromMAC(line_buf + 9, line_buf + 26);
reset_needed = is_ok;
}
}
@ -250,7 +248,7 @@ void provisioning_process_line()
esp_err_t err = esp_efuse_mac_get_default(mac);
ESP_ERROR_CHECK(err);
bin_to_hex_str(mac, 6, hexbuf);
binToHexStr(mac, 6, hexbuf);
for (int i = 0; i < 12; i += 2) {
if (i > 0)
uart_write_bytes(UART_NUM, ":", 1);
@ -266,7 +264,7 @@ void provisioning_process_line()
esp_err_t err = esp_efuse_mac_get_default(mac);
ESP_ERROR_CHECK(err);
bin_to_hex_str(mac, 6, hexbuf);
binToHexStr(mac, 6, hexbuf);
for (int i = 0; i < 12; i += 2) {
uart_write_bytes(UART_NUM, hexbuf + i, 2);
if (i == 4)
@ -285,18 +283,21 @@ void provisioning_process_line()
if (reset_needed)
{
hal_enterCriticalSection();
ttn_hal.enterCriticalSection();
LMIC_reset();
hal_leaveCriticalSection();
onEvent(EV_RESET);
ttn_hal.leaveCriticalSection();
LMIC.client.eventCb(LMIC.client.eventUserData, EV_RESET);
}
uart_write_bytes(UART_NUM, is_ok ? "OK\r\n" : "ERROR\r\n", is_ok ? 4 : 7);
}
#if defined(CONFIG_TTN_PROVISION_UART_CONFIG_YES)
#endif
void provisioning_config_uart()
#if defined(TTN_CONFIG_UART)
void TTNProvisioning::configUART()
{
esp_err_t err;
@ -305,7 +306,9 @@ void provisioning_config_uart()
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 0,
.use_ref_tick = false
};
err = uart_param_config(UART_NUM, &uart_config);
ESP_ERROR_CHECK(err);
@ -319,17 +322,17 @@ void provisioning_config_uart()
// --- Key handling
bool provisioning_have_keys()
bool TTNProvisioning::haveKeys()
{
return have_keys;
}
bool provisioning_decode_keys(const char *dev_eui, const char *app_eui, const char *app_key)
bool TTNProvisioning::decodeKeys(const char *dev_eui, const char *app_eui, const char *app_key)
{
return provisioning_decode(true, dev_eui, app_eui, app_key);
return decode(true, dev_eui, app_eui, app_key);
}
bool provisioning_from_mac(const char *app_eui, const char *app_key)
bool TTNProvisioning::fromMAC(const char *app_eui, const char *app_key)
{
uint8_t mac[6];
esp_err_t err = esp_efuse_mac_get_default(mac);
@ -344,33 +347,33 @@ bool provisioning_from_mac(const char *app_eui, const char *app_key)
global_dev_eui[1] = mac[4];
global_dev_eui[0] = mac[5];
return provisioning_decode(false, NULL, app_eui, app_key);
return decode(false, nullptr, app_eui, app_key);
}
bool provisioning_decode(bool incl_dev_eui, const char *dev_eui, const char *app_eui, const char *app_key)
bool TTNProvisioning::decode(bool incl_dev_eui, const char *dev_eui, const char *app_eui, const char *app_key)
{
uint8_t buf_dev_eui[8];
uint8_t buf_app_eui[8];
uint8_t buf_app_key[16];
if (incl_dev_eui && (strlen(dev_eui) != 16 || !hex_str_to_bin(dev_eui, buf_dev_eui, 8)))
if (incl_dev_eui && (strlen(dev_eui) != 16 || !hexStrToBin(dev_eui, buf_dev_eui, 8)))
{
ESP_LOGW(TAG, "Invalid device EUI: %s", dev_eui);
return false;
}
if (incl_dev_eui)
swap_bytes(buf_dev_eui, 8);
swapBytes(buf_dev_eui, 8);
if (strlen(app_eui) != 16 || !hex_str_to_bin(app_eui, buf_app_eui, 8))
if (strlen(app_eui) != 16 || !hexStrToBin(app_eui, buf_app_eui, 8))
{
ESP_LOGW(TAG, "Invalid application EUI: %s", app_eui);
return false;
}
swap_bytes(buf_app_eui, 8);
swapBytes(buf_app_eui, 8);
if (strlen(app_key) != 32 || !hex_str_to_bin(app_key, buf_app_key, 16))
if (strlen(app_key) != 32 || !hexStrToBin(app_key, buf_app_key, 16))
{
ESP_LOGW(TAG, "Invalid application key: %s", app_key);
return false;
@ -381,11 +384,11 @@ bool provisioning_decode(bool incl_dev_eui, const char *dev_eui, const char *app
memcpy(global_app_eui, buf_app_eui, sizeof(global_app_eui));
memcpy(global_app_key, buf_app_key, sizeof(global_app_key));
have_keys = !is_all_zeroes(global_dev_eui, sizeof(global_dev_eui))
&& !is_all_zeroes(global_app_eui, sizeof(global_app_eui))
&& !is_all_zeroes(global_app_key, sizeof(global_app_key));
have_keys = !isAllZeros(global_dev_eui, sizeof(global_dev_eui))
&& !isAllZeros(global_app_eui, sizeof(global_app_eui))
&& !isAllZeros(global_app_key, sizeof(global_app_key));
if (!provisioning_save_keys())
if (!saveKeys())
return false;
return true;
@ -394,7 +397,7 @@ bool provisioning_decode(bool incl_dev_eui, const char *dev_eui, const char *app
// --- Non-volatile storage
bool provisioning_save_keys()
bool TTNProvisioning::saveKeys()
{
bool result = false;
@ -409,13 +412,13 @@ bool provisioning_save_keys()
if (res != ESP_OK)
goto done;
if (!write_nvs_value(handle, NVS_FLASH_KEY_DEV_EUI, global_dev_eui, sizeof(global_dev_eui)))
if (!writeNvsValue(handle, NVS_FLASH_KEY_DEV_EUI, global_dev_eui, sizeof(global_dev_eui)))
goto done;
if (!write_nvs_value(handle, NVS_FLASH_KEY_APP_EUI, global_app_eui, sizeof(global_app_eui)))
if (!writeNvsValue(handle, NVS_FLASH_KEY_APP_EUI, global_app_eui, sizeof(global_app_eui)))
goto done;
if (!write_nvs_value(handle, NVS_FLASH_KEY_APP_KEY, global_app_key, sizeof(global_app_key)))
if (!writeNvsValue(handle, NVS_FLASH_KEY_APP_KEY, global_app_key, sizeof(global_app_key)))
goto done;
res = nvs_commit(handle);
@ -429,7 +432,7 @@ done:
return result;
}
bool provisioning_restore_keys(bool silent)
bool TTNProvisioning::restoreKeys(bool silent)
{
uint8_t buf_dev_eui[8];
uint8_t buf_app_eui[8];
@ -448,22 +451,22 @@ bool provisioning_restore_keys(bool silent)
if (res != ESP_OK)
goto done;
if (!read_nvs_value(handle, NVS_FLASH_KEY_DEV_EUI, buf_dev_eui, sizeof(global_dev_eui), silent))
if (!readNvsValue(handle, NVS_FLASH_KEY_DEV_EUI, buf_dev_eui, sizeof(global_dev_eui), silent))
goto done;
if (!read_nvs_value(handle, NVS_FLASH_KEY_APP_EUI, buf_app_eui, sizeof(global_app_eui), silent))
if (!readNvsValue(handle, NVS_FLASH_KEY_APP_EUI, buf_app_eui, sizeof(global_app_eui), silent))
goto done;
if (!read_nvs_value(handle, NVS_FLASH_KEY_APP_KEY, buf_app_key, sizeof(global_app_key), silent))
if (!readNvsValue(handle, NVS_FLASH_KEY_APP_KEY, buf_app_key, sizeof(global_app_key), silent))
goto done;
memcpy(global_dev_eui, buf_dev_eui, sizeof(global_dev_eui));
memcpy(global_app_eui, buf_app_eui, sizeof(global_app_eui));
memcpy(global_app_key, buf_app_key, sizeof(global_app_key));
have_keys = !is_all_zeroes(global_dev_eui, sizeof(global_dev_eui))
&& !is_all_zeroes(global_app_eui, sizeof(global_app_eui))
&& !is_all_zeroes(global_app_key, sizeof(global_app_key));
have_keys = !isAllZeros(global_dev_eui, sizeof(global_dev_eui))
&& !isAllZeros(global_app_eui, sizeof(global_app_eui))
&& !isAllZeros(global_app_key, sizeof(global_app_key));
if (have_keys)
{
@ -479,7 +482,7 @@ done:
return true;
}
bool read_nvs_value(nvs_handle handle, const char* key, uint8_t* data, size_t expected_length, bool silent)
bool TTNProvisioning::readNvsValue(nvs_handle handle, const char* key, uint8_t* data, size_t expected_length, bool silent)
{
size_t size = expected_length;
esp_err_t res = nvs_get_blob(handle, key, data, &size);
@ -504,10 +507,10 @@ bool read_nvs_value(nvs_handle handle, const char* key, uint8_t* data, size_t ex
return false;
}
bool write_nvs_value(nvs_handle handle, const char* key, const uint8_t* data, size_t len)
bool TTNProvisioning::writeNvsValue(nvs_handle handle, const char* key, const uint8_t* data, size_t len)
{
uint8_t buf[16];
if (read_nvs_value(handle, key, buf, len, true) && memcmp(buf, data, len) == 0)
if (readNvsValue(handle, key, buf, len, true) && memcmp(buf, data, len) == 0)
return true; // unchanged
esp_err_t res = nvs_set_blob(handle, key, data, len);
@ -519,12 +522,12 @@ bool write_nvs_value(nvs_handle handle, const char* key, const uint8_t* data, si
// --- Helper functions ---
bool hex_str_to_bin(const char *hex, uint8_t *buf, int len)
bool TTNProvisioning::hexStrToBin(const char *hex, uint8_t *buf, int len)
{
const char* ptr = hex;
for (int i = 0; i < len; i++)
{
int val = hex_tuple_to_byte(ptr);
int val = hexTupleToByte(ptr);
if (val < 0)
return false;
buf[i] = val;
@ -533,18 +536,18 @@ bool hex_str_to_bin(const char *hex, uint8_t *buf, int len)
return true;
}
int hex_tuple_to_byte(const char *hex)
int TTNProvisioning::hexTupleToByte(const char *hex)
{
int nibble1 = hex_digit_to_val(hex[0]);
int nibble1 = hexDigitToVal(hex[0]);
if (nibble1 < 0)
return -1;
int nibble2 = hex_digit_to_val(hex[1]);
int nibble2 = hexDigitToVal(hex[1]);
if (nibble2 < 0)
return -1;
return (nibble1 << 4) | nibble2;
}
int hex_digit_to_val(char ch)
int TTNProvisioning::hexDigitToVal(char ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
@ -555,24 +558,24 @@ int hex_digit_to_val(char ch)
return -1;
}
void bin_to_hex_str(const uint8_t* buf, int len, char* hex)
void TTNProvisioning::binToHexStr(const uint8_t* buf, int len, char* hex)
{
for (int i = 0; i < len; i++)
{
uint8_t b = buf[i];
*hex = val_to_hex_digit((b & 0xf0) >> 4);
*hex = valToHexDigit((b & 0xf0) >> 4);
hex++;
*hex = val_to_hex_digit(b & 0x0f);
*hex = valToHexDigit(b & 0x0f);
hex++;
}
}
char val_to_hex_digit(int val)
char TTNProvisioning::valToHexDigit(int val)
{
return "0123456789ABCDEF"[val];
}
void swap_bytes(uint8_t* buf, int len)
void TTNProvisioning::swapBytes(uint8_t* buf, int len)
{
uint8_t* p1 = buf;
uint8_t* p2 = buf + len - 1;
@ -586,7 +589,7 @@ void swap_bytes(uint8_t* buf, int len)
}
}
bool is_all_zeroes(const uint8_t* buf, int len)
bool TTNProvisioning::isAllZeros(const uint8_t* buf, int len)
{
for (int i = 0; i < len; i++)
if (buf[i] != 0)

73
src/TTNProvisioning.h Normal file
View File

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

View File

@ -2,7 +2,7 @@
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018 Manuel Bleichenbacher
* Copyright (c) 2018-2019 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
@ -13,38 +13,73 @@
#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 "provisioning.h"
#include "TheThingsNetwork.h"
#include "TTNProvisioning.h"
#include "TTNLogging.h"
enum ClientAction
/**
* @brief Reason the user code is waiting
*/
enum TTNWaitingReason
{
eActionUnrelated,
eActionJoining,
eActionSending
eWaitingNone,
eWaitingForJoin,
eWaitingForTransmission
};
/**
* @brief Event type
*/
enum TTNEvent {
eEvtNone,
eEvtJoinCompleted,
eEvtJoinFailed,
eEvtMessageReceived,
eEvtTransmissionCompleted,
eEvtTransmissionFailed
};
/**
* @brief Event message sent from LMIC task to waiting client task
*/
struct TTNLmicEvent {
TTNLmicEvent(TTNEvent ev = eEvtNone): event(ev) { }
TTNEvent event;
uint8_t port;
const uint8_t* message;
size_t messageSize;
};
static const char *TAG = "ttn";
static TheThingsNetwork* ttnInstance;
static QueueHandle_t resultQueue;
static ClientAction clientAction = eActionUnrelated;
static QueueHandle_t lmicEventQueue = nullptr;
static TTNWaitingReason waitingReason = eWaitingNone;
static TTNProvisioning provisioning;
#if LMIC_ENABLE_event_logging
static TTNLogging* logging;
#endif
static void eventCallback(void* userData, ev_t event);
static void messageReceivedCallback(void *userData, uint8_t port, const uint8_t *message, size_t messageSize);
static void messageTransmittedCallback(void *userData, int success);
TheThingsNetwork::TheThingsNetwork()
: messageCallback(NULL)
: 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 == NULL);
ASSERT(ttnInstance == nullptr);
ttnInstance = this;
hal_initCriticalSection();
ttn_hal.initCriticalSection();
}
TheThingsNetwork::~TheThingsNetwork()
@ -54,70 +89,86 @@ TheThingsNetwork::~TheThingsNetwork()
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;
ttn_hal.configurePins(spi_host, nss, rxtx, rst, dio0, dio1);
os_init();
#if LMIC_ENABLE_event_logging
logging = TTNLogging::initInstance();
#endif
LMIC_registerEventCb(eventCallback, nullptr);
LMIC_registerRxMessageCb(messageReceivedCallback, nullptr);
os_init_ex(nullptr);
reset();
resultQueue = xQueueCreate(12, sizeof(int));
ASSERT(resultQueue != NULL);
hal_startBgTask();
lmicEventQueue = xQueueCreate(4, sizeof(TTNLmicEvent));
ASSERT(lmicEventQueue != nullptr);
ttn_hal.startLMICTask();
}
void TheThingsNetwork::reset()
{
hal_enterCriticalSection();
ttn_hal.enterCriticalSection();
LMIC_reset();
hal_leaveCriticalSection();
waitingReason = eWaitingNone;
if (lmicEventQueue != nullptr)
{
xQueueReset(lmicEventQueue);
}
ttn_hal.leaveCriticalSection();
}
bool TheThingsNetwork::provision(const char *devEui, const char *appEui, const char *appKey)
{
if (!provisioning_decode_keys(devEui, appEui, appKey))
if (!provisioning.decodeKeys(devEui, appEui, appKey))
return false;
return provisioning_save_keys();
return provisioning.saveKeys();
}
bool TheThingsNetwork::provisionWithMAC(const char *appEui, const char *appKey)
{
if (!provisioning_from_mac(appEui, appKey))
if (!provisioning.fromMAC(appEui, appKey))
return false;
return provisioning_save_keys();
return provisioning.saveKeys();
}
void TheThingsNetwork::startProvisioningTask()
{
#if !defined(CONFIG_TTN_PROVISION_UART_NONE)
provisioning_start_task();
#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(CONFIG_TTN_PROVISION_UART_NONE)
#if defined(TTN_HAS_AT_COMMANDS)
if (isProvisioned())
{
ESP_LOGI(TAG, "Device is already provisioned");
return;
}
while (!provisioning_have_keys())
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_decode_keys(devEui, appEui, appKey))
if (!provisioning.decodeKeys(devEui, appEui, appKey))
return false;
return joinCore();
@ -125,9 +176,9 @@ bool TheThingsNetwork::join(const char *devEui, const char *appEui, const char *
bool TheThingsNetwork::join()
{
if (!provisioning_have_keys())
if (!provisioning.haveKeys())
{
if (!provisioning_restore_keys(false))
if (!provisioning.restoreKeys(false))
return false;
}
@ -136,58 +187,61 @@ bool TheThingsNetwork::join()
bool TheThingsNetwork::joinCore()
{
if (!provisioning_have_keys())
if (!provisioning.haveKeys())
{
ESP_LOGW(TAG, "Device EUI, App EUI and/or App key have not been provided");
return false;
}
hal_enterCriticalSection();
clientAction = eActionJoining;
ttn_hal.enterCriticalSection();
waitingReason = eWaitingForJoin;
LMIC_startJoining();
hal_wakeUp();
hal_leaveCriticalSection();
ttn_hal.wakeUp();
ttn_hal.leaveCriticalSection();
int result = 0;
xQueueReceive(resultQueue, &result, portMAX_DELAY);
return result == EV_JOINED;
TTNLmicEvent event;
xQueueReceive(lmicEventQueue, &event, portMAX_DELAY);
return event.event == eEvtJoinCompleted;
}
TTNResponseCode TheThingsNetwork::transmitMessage(const uint8_t *payload, size_t length, port_t port, bool confirm)
{
hal_enterCriticalSection();
if (LMIC.opmode & OP_TXRXPEND)
ttn_hal.enterCriticalSection();
if (waitingReason != eWaitingNone || (LMIC.opmode & OP_TXRXPEND) != 0)
{
hal_leaveCriticalSection();
ttn_hal.leaveCriticalSection();
return kTTNErrorTransmissionFailed;
}
clientAction = eActionSending;
waitingReason = eWaitingForTransmission;
LMIC.client.txMessageCb = messageTransmittedCallback;
LMIC.client.txMessageUserData = nullptr;
LMIC_setTxData2(port, (xref2u1_t)payload, length, confirm);
hal_wakeUp();
hal_leaveCriticalSection();
ttn_hal.wakeUp();
ttn_hal.leaveCriticalSection();
int result = 0;
xQueueReceive(resultQueue, &result, portMAX_DELAY);
if (result == EV_TXCOMPLETE)
while (true)
{
bool hasRecevied = (LMIC.txrxFlags & (TXRX_DNW1 | TXRX_DNW2)) != 0;
if (hasRecevied && messageCallback != NULL)
TTNLmicEvent result;
xQueueReceive(lmicEventQueue, &result, portMAX_DELAY);
switch (result.event)
{
port_t port = 0;
if ((LMIC.txrxFlags & TXRX_PORT))
port = LMIC.frame[LMIC.dataBeg - 1];
const uint8_t* msg = NULL;
if (LMIC.dataLen > 0)
msg = LMIC.frame + LMIC.dataBeg;
messageCallback(msg, LMIC.dataLen, port);
case eEvtMessageReceived:
if (messageCallback != nullptr)
messageCallback(result.message, result.messageSize, result.port);
break;
case eEvtTransmissionCompleted:
return kTTNSuccessfulTransmission;
case eEvtTransmissionFailed:
return kTTNErrorTransmissionFailed;
default:
ASSERT(0);
}
return kTTNSuccessfulTransmission;
}
return kTTNErrorTransmissionFailed;
}
void TheThingsNetwork::onMessage(TTNMessageCallback callback)
@ -198,55 +252,72 @@ void TheThingsNetwork::onMessage(TTNMessageCallback callback)
bool TheThingsNetwork::isProvisioned()
{
if (provisioning_have_keys())
if (provisioning.haveKeys())
return true;
provisioning_restore_keys(true);
provisioning.restoreKeys(true);
return provisioning_have_keys();
return provisioning.haveKeys();
}
void TheThingsNetwork::setRSSICal(int8_t rssiCal)
{
ttn_hal.rssiCal = rssiCal;
}
// --- LMIC functions ---
// --- Callbacks ---
#if CONFIG_LOG_DEFAULT_LEVEL >= 3
static const char *eventNames[] = {
NULL,
"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"
};
#if CONFIG_LOG_DEFAULT_LEVEL >= 3 || LMIC_ENABLE_event_logging
const char *eventNames[] = { LMIC_EVENT_NAME_TABLE__INIT };
#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");
// Called by LMIC when an LMIC event (join, join failed, reset etc.) occurs
void eventCallback(void* userData, ev_t event)
{
#if LMIC_ENABLE_event_logging
logging->logEvent(event, eventNames[event], 0);
#elif CONFIG_LOG_DEFAULT_LEVEL >= 3
ESP_LOGI(TAG, "event %s", eventNames[event]);
#endif
TTNEvent ttnEvent = eEvtNone;
if (waitingReason == eWaitingForJoin)
{
if (event == EV_JOINED)
{
ttnEvent = eEvtJoinCompleted;
}
else if (event == EV_REJOIN_FAILED || event == EV_RESET)
{
ttnEvent = eEvtJoinFailed;
}
}
if (clientAction == eActionUnrelated)
{
if (ttnEvent == eEvtNone)
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);
TTNLmicEvent result(ttnEvent);
waitingReason = eWaitingNone;
xQueueSend(lmicEventQueue, &result, pdMS_TO_TICKS(100));
}
// Called by LMIC when a message has been received
void messageReceivedCallback(void *userData, uint8_t port, const uint8_t *message, size_t nMessage)
{
TTNLmicEvent result(eEvtMessageReceived);
result.port = port;
result.message = message;
result.messageSize = nMessage;
xQueueSend(lmicEventQueue, &result, pdMS_TO_TICKS(100));
}
// Called by LMIC when a message has been transmitted (or the transmission failed)
void messageTransmittedCallback(void *userData, int success)
{
waitingReason = eWaitingNone;
TTNLmicEvent result(success ? eEvtTransmissionCompleted : eEvtTransmissionFailed);
xQueueSend(lmicEventQueue, &result, pdMS_TO_TICKS(100));
}

3
src/aes/lmic_aes.c Executable file → Normal file
View File

@ -262,9 +262,6 @@ u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) {
u4_t a0, a1, a2, a3;
u4_t t0, t1, t2, t3;
u4_t *ki, *ke;
// ttn-esp32 change: prevent error 'x' may be used uninitialized in this function
a0 = a1 = a2 = a3 = 0;
t0 = t1 = 0;
// load input block
if( (mode & AES_CTR) || ((mode & AES_MIC) && (mode & AES_MICNOAUX)==0) ) { // load CTR block or first MIC block

View File

@ -20,6 +20,11 @@
#define CFG_au921 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
@ -35,25 +40,13 @@
#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'
#if !defined(CONFIG_TTN_PROVISION_UART_NONE)
#define TTN_HAS_AT_COMMANDS 1
#if defined(CONFIG_TTN_PROVISION_UART_CONFIG_YES)
#define TTN_CONFIG_UART 1
#endif
#endif
// 16 μs per tick
// LMIC requires ticks to be 15.5μs - 100 μs long
@ -67,6 +60,8 @@
#include <stdio.h>
#endif
#define LMIC_ENABLE_onEvent 0
#define DISABLE_PING
#define DISABLE_BEACONS

View File

@ -1,470 +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 *TAG = "ttn_hal";
lmic_pinmap lmic_pins;
typedef enum {
DIO0 = 0,
DIO1,
DIO2,
TIMER,
WAKEUP
} event_t;
typedef struct {
ostime_t time;
event_t ev;
} queue_item_t;
// -----------------------------------------------------------------------------
// I/O
static QueueHandle_t dio_queue;
void IRAM_ATTR dio_irq_handler(void *arg)
{
uint64_t now;
timer_get_counter_value(TTN_TIMER_GROUP, TTN_TIMER, &now);
event_t ev = (long)arg;
BaseType_t higher_prio_task_woken = pdFALSE;
queue_item_t item = {
.time = (ostime_t)now,
.ev = ev
};
xQueueSendFromISR(dio_queue, &item, &higher_prio_task_woken);
if (higher_prio_task_woken)
portYIELD_FROM_ISR();
}
static void hal_io_init()
{
// 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(lmic_pins.nss, 0);
gpio_set_direction(lmic_pins.nss, GPIO_MODE_OUTPUT);
if (lmic_pins.rxtx != LMIC_UNUSED_PIN)
{
gpio_pad_select_gpio(lmic_pins.rxtx);
gpio_set_level(lmic_pins.rxtx, 0);
gpio_set_direction(lmic_pins.rxtx, GPIO_MODE_OUTPUT);
}
if (lmic_pins.rst != LMIC_UNUSED_PIN)
{
gpio_pad_select_gpio(lmic_pins.rst);
gpio_set_level(lmic_pins.rst, 0);
gpio_set_direction(lmic_pins.rst, GPIO_MODE_OUTPUT);
}
dio_queue = xQueueCreate(12, sizeof(queue_item_t));
ASSERT(dio_queue != NULL);
gpio_pad_select_gpio(lmic_pins.dio0);
gpio_set_direction(lmic_pins.dio0, GPIO_MODE_INPUT);
gpio_set_intr_type(lmic_pins.dio0, GPIO_INTR_POSEDGE);
gpio_isr_handler_add(lmic_pins.dio0, dio_irq_handler, (void *)0);
gpio_pad_select_gpio(lmic_pins.dio1);
gpio_set_direction(lmic_pins.dio1, GPIO_MODE_INPUT);
gpio_set_intr_type(lmic_pins.dio1, GPIO_INTR_POSEDGE);
gpio_isr_handler_add(lmic_pins.dio1, dio_irq_handler, (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(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(lmic_pins.rst, val);
gpio_set_direction(lmic_pins.rst, GPIO_MODE_OUTPUT);
}
else
{ // keep pin floating
gpio_set_level(lmic_pins.rst, val);
gpio_set_direction(lmic_pins.rst, GPIO_MODE_INPUT);
}
}
s1_t hal_getRssiCal (void) {
return lmic_pins.rssi_cal;
}
// -----------------------------------------------------------------------------
// SPI
#define SPI_QUEUE_SIZE 4
#define SPI_NUM_TRX_SLOTS (SPI_QUEUE_SIZE + 1)
static spi_device_handle_t spi_handle;
static spi_transaction_t spi_trx_queue[SPI_NUM_TRX_SLOTS];
static int spi_trx_queue_head = 0;
static int spi_num_outstanding_trx = 0;
static spi_transaction_t* get_next_spi_trx_desc()
{
spi_transaction_t* trx = spi_trx_queue + spi_trx_queue_head;
memset(trx, 0, sizeof(spi_transaction_t));
return trx;
}
static void collect_spi_result()
{
int head = spi_trx_queue_head;
int tail = head - spi_num_outstanding_trx;
if (tail < 0)
tail += SPI_NUM_TRX_SLOTS;
spi_transaction_t* trx;
esp_err_t err = spi_device_get_trans_result(spi_handle, &trx, 100 / portTICK_PERIOD_MS);
ESP_ERROR_CHECK(err);
ASSERT(trx == spi_trx_queue + tail);
spi_num_outstanding_trx--;
}
static void submit_spi_trx()
{
if (spi_num_outstanding_trx >= SPI_QUEUE_SIZE)
collect_spi_result();
int head = spi_trx_queue_head;
esp_err_t err = spi_device_queue_trans(spi_handle, spi_trx_queue + head, 100 / portTICK_PERIOD_MS);
ESP_ERROR_CHECK(err);
spi_num_outstanding_trx++;
head++;
if (head >= SPI_NUM_TRX_SLOTS)
head = 0;
spi_trx_queue_head = head;
}
static void hal_spi_init()
{
// init device
spi_device_interface_config_t spi_device_intf_config = {
.mode = 1,
.clock_speed_hz = CONFIG_TTN_SPI_FREQ,
.command_bits = 0,
.address_bits = 8,
.spics_io_num = lmic_pins.nss,
.queue_size = SPI_QUEUE_SIZE,
.cs_ena_posttrans = 2
};
esp_err_t ret = spi_bus_add_device(lmic_pins.spi_host, &spi_device_intf_config, &spi_handle);
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "SPI initialized");
}
void hal_spi_write(u1_t cmd, const u1_t *buf, int len)
{
spi_transaction_t* trx = get_next_spi_trx_desc();
trx->addr = cmd;
trx->length = 8 * len;
trx->tx_buffer = buf;
submit_spi_trx();
}
void hal_spi_read(u1_t cmd, u1_t *buf, int len)
{
memset(buf, 0, len);
spi_transaction_t* trx = get_next_spi_trx_desc();
trx->addr = cmd;
trx->length = 8 * len;
trx->rxlength = 8 * len;
trx->tx_buffer = buf;
trx->rx_buffer = buf;
submit_spi_trx();
while (spi_num_outstanding_trx > 0)
collect_spi_result();
}
// -----------------------------------------------------------------------------
// 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.
*/
#define OVERRUN_TRESHOLD 0x10000 // approx 10 seconds
static uint64_t next_timer_event = 0x200000000;
static void IRAM_ATTR hal_timer_irq_handler(void *arg);
static void hal_time_init()
{
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, hal_timer_irq_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);
timer_start(TTN_TIMER_GROUP, TTN_TIMER);
ESP_LOGI(TAG, "Timer initialized");
}
static void hal_prepare_next_alarm(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);
}
next_timer_event = time;
if (now32 > time && now32 - time > OVERRUN_TRESHOLD)
next_timer_event += 0x100000000;
}
static void hal_arm_timer()
{
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_DIS);
timer_set_alarm_value(TTN_TIMER_GROUP, TTN_TIMER, next_timer_event);
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_EN);
}
static void hal_disarm_timer()
{
timer_set_alarm(TTN_TIMER_GROUP, TTN_TIMER, TIMER_ALARM_DIS);
next_timer_event = 0x200000000; // wait indefinitely (almost)
}
static void IRAM_ATTR hal_timer_irq_handler(void *arg)
{
TTN_CLEAR_TIMER_ALARM;
BaseType_t higher_prio_task_woken = pdFALSE;
queue_item_t item = {
.ev = TIMER
};
xQueueSendFromISR(dio_queue, &item, &higher_prio_task_woken);
if (higher_prio_task_woken)
portYIELD_FROM_ISR();
}
typedef enum {
CHECK_IO,
WAIT_FOR_ANY_EVENT,
WAIT_FOR_TIMER
} wait_open_e;
static bool hal_wait(wait_open_e wait_option)
{
TickType_t ticks_to_wait = wait_option == CHECK_IO ? 0 : portMAX_DELAY;
while (true)
{
queue_item_t item;
if (xQueueReceive(dio_queue, &item, ticks_to_wait) == pdFALSE)
return false;
if (item.ev == WAKEUP)
{
if (wait_option != WAIT_FOR_TIMER)
{
hal_disarm_timer();
return true;
}
}
else if (item.ev == TIMER)
{
hal_disarm_timer();
if (wait_option != CHECK_IO)
return true;
}
else // IO interrupt
{
if (wait_option != WAIT_FOR_TIMER)
hal_disarm_timer();
hal_enterCriticalSection();
radio_irq_handler_v2(item.ev, item.time);
hal_leaveCriticalSection();
if (wait_option != 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)
{
hal_prepare_next_alarm(time);
hal_arm_timer();
hal_wait(WAIT_FOR_TIMER);
}
void hal_wakeUp()
{
queue_item_t item = {
.ev = WAKEUP
};
xQueueSend(dio_queue, &item, 0);
}
// check and rewind for target time
u1_t hal_checkTimer(u4_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
}
hal_prepare_next_alarm(time);
return 0;
}
void hal_sleep()
{
if (hal_wait(CHECK_IO))
return;
hal_arm_timer();
hal_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
static SemaphoreHandle_t mutex;
void hal_initCriticalSection()
{
mutex = xSemaphoreCreateRecursiveMutex();
}
void hal_enterCriticalSection()
{
xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
}
void hal_leaveCriticalSection()
{
xSemaphoreGiveRecursive(mutex);
}
// -----------------------------------------------------------------------------
static void hal_bgTask(void* pvParameter) {
os_runloop();
}
void hal_init_ex(const void *pContext)
{
// configure radio I/O and interrupt handler
hal_io_init();
// configure radio SPI
hal_spi_init();
// configure timer and interrupt handler
hal_time_init();
}
void hal_startBgTask() {
xTaskCreate(hal_bgTask, "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);
}

490
src/hal/hal_esp32.cpp Executable file
View File

@ -0,0 +1,490 @@
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2019 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF.
*******************************************************************************/
#include "../lmic/lmic.h"
#include "../hal/hal_esp32.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "driver/timer.h"
#include "esp_log.h"
#define LMIC_UNUSED_PIN 0xff
#define NOTIFY_BIT_DIO 1
#define NOTIFY_BIT_TIMER 2
#define NOTIFY_BIT_WAKEUP 4
static const char* const TAG = "ttn_hal";
HAL_ESP32 ttn_hal;
TaskHandle_t HAL_ESP32::lmicTask = nullptr;
uint32_t HAL_ESP32::dioInterruptTime = 0;
uint8_t HAL_ESP32::dioNum = 0;
// -----------------------------------------------------------------------------
// Constructor
HAL_ESP32::HAL_ESP32()
: rssiCal(10), nextAlarm(0)
{
}
// -----------------------------------------------------------------------------
// I/O
void HAL_ESP32::configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1)
{
spiHost = spi_host;
pinNSS = (gpio_num_t)nss;
pinRxTx = (gpio_num_t)rxtx;
pinRst = (gpio_num_t)rst;
pinDIO0 = (gpio_num_t)dio0;
pinDIO1 = (gpio_num_t)dio1;
// Until the background process has been started, use the current task
// for supporting calls like `hal_waitUntil()`.
lmicTask = xTaskGetCurrentTaskHandle();
}
void IRAM_ATTR HAL_ESP32::dioIrqHandler(void *arg)
{
dioInterruptTime = hal_ticks();
dioNum = (u1_t)(long)arg;
BaseType_t higherPrioTaskWoken = pdFALSE;
xTaskNotifyFromISR(lmicTask, NOTIFY_BIT_DIO, eSetBits, &higherPrioTaskWoken);
if (higherPrioTaskWoken)
portYIELD_FROM_ISR();
}
void HAL_ESP32::ioInit()
{
// pinNSS and pinDIO0 and pinDIO1 are required
ASSERT(pinNSS != LMIC_UNUSED_PIN);
ASSERT(pinDIO0 != LMIC_UNUSED_PIN);
ASSERT(pinDIO1 != LMIC_UNUSED_PIN);
gpio_pad_select_gpio(pinNSS);
gpio_set_level(pinNSS, 0);
gpio_set_direction(pinNSS, GPIO_MODE_OUTPUT);
if (pinRxTx != LMIC_UNUSED_PIN)
{
gpio_pad_select_gpio(pinRxTx);
gpio_set_level(pinRxTx, 0);
gpio_set_direction(pinRxTx, GPIO_MODE_OUTPUT);
}
if (pinRst != LMIC_UNUSED_PIN)
{
gpio_pad_select_gpio(pinRst);
gpio_set_level(pinRst, 0);
gpio_set_direction(pinRst, GPIO_MODE_OUTPUT);
}
// DIO pins with interrupt handlers
gpio_pad_select_gpio(pinDIO0);
gpio_set_direction(pinDIO0, GPIO_MODE_INPUT);
gpio_set_intr_type(pinDIO0, GPIO_INTR_POSEDGE);
gpio_pad_select_gpio(pinDIO1);
gpio_set_direction(pinDIO1, GPIO_MODE_INPUT);
gpio_set_intr_type(pinDIO1, GPIO_INTR_POSEDGE);
ESP_LOGI(TAG, "IO initialized");
}
void hal_pin_rxtx(u1_t val)
{
if (ttn_hal.pinRxTx == LMIC_UNUSED_PIN)
return;
gpio_set_level(ttn_hal.pinRxTx, val);
}
void hal_pin_rst(u1_t val)
{
if (ttn_hal.pinRst == LMIC_UNUSED_PIN)
return;
if (val == 0 || val == 1)
{ // drive pin
gpio_set_level(ttn_hal.pinRst, val);
gpio_set_direction(ttn_hal.pinRst, GPIO_MODE_OUTPUT);
}
else
{ // keep pin floating
gpio_set_level(ttn_hal.pinRst, val);
gpio_set_direction(ttn_hal.pinRst, GPIO_MODE_INPUT);
}
}
s1_t hal_getRssiCal (void)
{
return ttn_hal.rssiCal;
}
ostime_t hal_setModuleActive (bit_t val)
{
return 0;
}
bit_t hal_queryUsingTcxo(void)
{
return false;
}
uint8_t hal_getTxPowerPolicy(u1_t inputPolicy, s1_t requestedPower, u4_t frequency)
{
return LMICHAL_radio_tx_power_policy_paboost;
}
// -----------------------------------------------------------------------------
// SPI
void HAL_ESP32::spiInit()
{
// init device
spi_device_interface_config_t spiConfig;
memset(&spiConfig, 0, sizeof(spiConfig));
spiConfig.mode = 1;
spiConfig.clock_speed_hz = CONFIG_TTN_SPI_FREQ;
spiConfig.command_bits = 0;
spiConfig.address_bits = 8;
spiConfig.spics_io_num = pinNSS;
spiConfig.queue_size = 1;
spiConfig.cs_ena_posttrans = 2;
esp_err_t ret = spi_bus_add_device(spiHost, &spiConfig, &spiHandle);
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "SPI initialized");
}
void hal_spi_write(u1_t cmd, const u1_t *buf, size_t len)
{
ttn_hal.spiWrite(cmd, buf, len);
}
void HAL_ESP32::spiWrite(uint8_t cmd, const uint8_t *buf, size_t len)
{
memset(&spiTransaction, 0, sizeof(spiTransaction));
spiTransaction.addr = cmd;
spiTransaction.length = 8 * len;
spiTransaction.tx_buffer = buf;
esp_err_t err = spi_device_transmit(spiHandle, &spiTransaction);
ESP_ERROR_CHECK(err);
}
void hal_spi_read(u1_t cmd, u1_t *buf, size_t len)
{
ttn_hal.spiRead(cmd, buf, len);
}
void HAL_ESP32::spiRead(uint8_t cmd, uint8_t *buf, size_t len)
{
memset(buf, 0, len);
memset(&spiTransaction, 0, sizeof(spiTransaction));
spiTransaction.addr = cmd;
spiTransaction.length = 8 * len;
spiTransaction.rxlength = 8 * len;
spiTransaction.tx_buffer = buf;
spiTransaction.rx_buffer = buf;
esp_err_t err = spi_device_transmit(spiHandle, &spiTransaction);
ESP_ERROR_CHECK(err);
}
// -----------------------------------------------------------------------------
// TIME
/*
* LIMIC uses a 32 bit time system (ostime_t) counting ticks. In this
* implementation each tick is 16µs. It will wrap arounnd every 19 hours.
*
* The ESP32 has a 64 bit timer counting microseconds. It will wrap around
* every 584,000 years. So we don't need to bother.
*
* Based on this timer, future callbacks can be scheduled. This is used to
* schedule the next LMIC job.
*/
// Convert LMIC tick time (ostime_t) to ESP absolute time.
// `osTime` is assumed to be somewhere between one hour in the past and
// 18 hours into the future.
int64_t HAL_ESP32::osTimeToEspTime(int64_t espNow, uint32_t osTime)
{
int64_t espTime;
uint32_t osNow = (uint32_t)(espNow >> 4);
// unsigned difference:
// 0x00000000 - 0xefffffff: future (0 to about 18 hours)
// 0xf0000000 - 0xffffffff: past (about 1 to 0 hours)
uint32_t osDiff = osTime - osNow;
if (osDiff < 0xf0000000)
{
espTime = espNow + (((int64_t)osDiff) << 4);
}
else
{
// one's complement instead of two's complement:
// off by 1 µs and ignored
osDiff = ~osDiff;
espTime = espNow - (((int64_t)osDiff) << 4);
}
return espTime;
}
void HAL_ESP32::timerInit()
{
esp_timer_create_args_t timerConfig = {
.callback = &timerCallback,
.arg = nullptr,
.dispatch_method = ESP_TIMER_TASK,
.name = "lmic_job"
};
esp_err_t err = esp_timer_create(&timerConfig, &timer);
ESP_ERROR_CHECK(err);
ESP_LOGI(TAG, "Timer initialized");
}
void HAL_ESP32::setNextAlarm(int64_t time)
{
nextAlarm = time;
}
void HAL_ESP32::armTimer(int64_t espNow)
{
if (nextAlarm == 0)
return;
int64_t timeout = nextAlarm - esp_timer_get_time();
if (timeout < 0)
timeout = 10;
esp_timer_start_once(timer, timeout);
}
void HAL_ESP32::disarmTimer()
{
esp_timer_stop(timer);
}
void HAL_ESP32::timerCallback(void *arg)
{
xTaskNotify(lmicTask, NOTIFY_BIT_TIMER, eSetBits);
}
// Wait for the next external event. Either:
// - scheduled timer due to scheduled job or waiting for a given time
// - wake up event from the client code
// - I/O interrupt (DIO0 or DIO1 pin)
bool HAL_ESP32::wait(WaitKind waitKind)
{
TickType_t ticksToWait = waitKind == CHECK_IO ? 0 : portMAX_DELAY;
while (true)
{
uint32_t bits = ulTaskNotifyTake(pdTRUE, ticksToWait);
if (bits == 0)
return false;
if ((bits & NOTIFY_BIT_WAKEUP) != 0)
{
if (waitKind != WAIT_FOR_TIMER)
{
disarmTimer();
return true;
}
}
else if ((bits & NOTIFY_BIT_TIMER) != 0)
{
disarmTimer();
setNextAlarm(0);
if (waitKind != CHECK_IO)
return true;
}
else // IO interrupt
{
if (waitKind != WAIT_FOR_TIMER)
disarmTimer();
enterCriticalSection();
radio_irq_handler_v2(dioNum, dioInterruptTime);
leaveCriticalSection();
if (waitKind != WAIT_FOR_TIMER)
return true;
}
}
}
// Gets current time in LMIC ticks
u4_t hal_ticks()
{
// LMIC tick unit: 16µs
// esp_timer unit: 1µs
return (u4_t)(esp_timer_get_time() >> 4);
}
// Wait until the specified time.
// Called if the LMIC code needs to wait for a precise time.
// All other events are ignored and will be served later.
void hal_waitUntil(u4_t time)
{
ttn_hal.waitUntil(time);
}
void HAL_ESP32::waitUntil(uint32_t osTime)
{
int64_t espNow = esp_timer_get_time();
int64_t espTime = osTimeToEspTime(espNow, osTime);
setNextAlarm(espTime);
armTimer(espNow);
wait(WAIT_FOR_TIMER);
}
// Called by client code to wake up LMIC to do something,
// e.g. send a submitted messages.
void HAL_ESP32::wakeUp()
{
xTaskNotify(lmicTask, NOTIFY_BIT_WAKEUP, eSetBits);
}
// Check if the specified time has been reached or almost reached.
// Otherwise, save it as alarm time.
// LMIC calls this function with the scheduled time of the next job
// in the queue. If the job is not due yet, LMIC will go to sleep.
u1_t hal_checkTimer(uint32_t time)
{
return ttn_hal.checkTimer(time);
}
uint8_t HAL_ESP32::checkTimer(u4_t osTime)
{
int64_t espNow = esp_timer_get_time();
int64_t espTime = osTimeToEspTime(espNow, osTime);
int64_t diff = espTime - espNow;
if (diff < 100)
return 1; // timer has expired or will expire very soon
setNextAlarm(espTime);
return 0;
}
// Go to sleep until next event.
// Called when LMIC is not busy and not job is due to be executed.
void hal_sleep()
{
ttn_hal.sleep();
}
void HAL_ESP32::sleep()
{
if (wait(CHECK_IO))
return;
armTimer(esp_timer_get_time());
wait(WAIT_FOR_ANY_EVENT);
}
// -----------------------------------------------------------------------------
// IRQ
void hal_disableIRQs()
{
// nothing to do as interrupt handlers post message to queue
// and don't access any shared data structures
}
void hal_enableIRQs()
{
// nothing to do as interrupt handlers post message to queue
// and don't access any shared data structures
}
// -----------------------------------------------------------------------------
// Synchronization between application code and background task
void HAL_ESP32::initCriticalSection()
{
mutex = xSemaphoreCreateRecursiveMutex();
}
void HAL_ESP32::enterCriticalSection()
{
xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
}
void HAL_ESP32::leaveCriticalSection()
{
xSemaphoreGiveRecursive(mutex);
}
// -----------------------------------------------------------------------------
void HAL_ESP32::lmicBackgroundTask(void* pvParameter) {
os_runloop();
}
void hal_init_ex(const void *pContext)
{
ttn_hal.init();
}
void HAL_ESP32::init()
{
// configure radio I/O and interrupt handler
ioInit();
// configure radio SPI
spiInit();
// configure timer and alarm callback
timerInit();
}
void HAL_ESP32::startLMICTask() {
xTaskCreate(lmicBackgroundTask, "ttn_lmic", 1024 * 4, nullptr, CONFIG_TTN_BG_TASK_PRIO, &lmicTask);
// enable interrupts
gpio_isr_handler_add(pinDIO0, dioIrqHandler, (void *)0);
gpio_isr_handler_add(pinDIO1, dioIrqHandler, (void *)1);
}
// -----------------------------------------------------------------------------
// Fatal failure
static hal_failure_handler_t* custom_hal_failure_handler = nullptr;
void hal_set_failure_handler(const hal_failure_handler_t* const handler)
{
custom_hal_failure_handler = handler;
}
void hal_failed(const char *file, u2_t line)
{
if (custom_hal_failure_handler != nullptr)
(*custom_hal_failure_handler)(file, line);
ESP_LOGE(TAG, "LMIC failed and stopped: %s:%d", file, line);
// go to sleep forever
while (true)
{
vTaskDelay(portMAX_DELAY);
}
}

View File

@ -2,45 +2,91 @@
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018 Manuel Bleichenbacher
* 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.
* Hardware abstraction layer to run LMIC on a ESP32 using ESP-IDF.
*******************************************************************************/
#ifndef _hal_esp32_h_
#define _hal_esp32_h_
#include <stdint.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_startBgTask();
void hal_wakeUp();
void hal_initCriticalSection();
void hal_enterCriticalSection();
void hal_leaveCriticalSection();
#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>
#ifdef __cplusplus
}
#endif
enum WaitKind {
CHECK_IO,
WAIT_FOR_ANY_EVENT,
WAIT_FOR_TIMER
};
class HAL_ESP32
{
public:
HAL_ESP32();
void configurePins(spi_host_device_t spi_host, uint8_t nss, uint8_t rxtx, uint8_t rst, uint8_t dio0, uint8_t dio1);
void init();
void startLMICTask();
void 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 osTime);
void sleep();
void waitUntil(uint32_t osTime);
spi_host_device_t spiHost;
gpio_num_t pinNSS;
gpio_num_t pinRxTx;
gpio_num_t pinRst;
gpio_num_t pinDIO0;
gpio_num_t pinDIO1;
int8_t rssiCal;
private:
static void lmicBackgroundTask(void* pvParameter);
static void dioIrqHandler(void* arg);
static void timerCallback(void *arg);
static int64_t osTimeToEspTime(int64_t espNow, uint32_t osTime);
void ioInit();
void spiInit();
void timerInit();
void setNextAlarm(int64_t time);
void armTimer(int64_t espNow);
void disarmTimer();
bool wait(WaitKind waitKind);
static TaskHandle_t lmicTask;
static uint32_t dioInterruptTime;
static uint8_t dioNum;
spi_device_handle_t spiHandle;
spi_transaction_t spiTransaction;
SemaphoreHandle_t mutex;
esp_timer_handle_t timer;
int64_t nextAlarm;
};
extern HAL_ESP32 ttn_hal;
#endif // _hal_esp32_h_

61
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.
@ -171,4 +165,43 @@
# endif // defined(LMIC_DISABLE_DR_LEGACY)
#endif // LMIC_DR_LEGACY
// LMIC_ENABLE_DeviceTimeReq
// 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
#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 that this be enabled.
#if !defined(LMIC_ENABLE_long_messages)
# define LMIC_ENABLE_long_messages 1 /* PARAM */
#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
#endif // _lmic_config_h_

92
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
@ -26,30 +26,40 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _hal_hpp_
#define _hal_hpp_
#ifndef _lmic_hal_h_
#define _lmic_hal_h_
#ifndef _oslmic_types_h_
# 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,
* which the platform can't control or change.
*/
void hal_init (void);
/*
* initialize hardware, passing in platform-specific context
* Initialize hardware, passing in platform-specific context
* The pointer is to a HalPinmap_t.
*/
void hal_init_ex (const void *pContext);
/*
* drive radio NSS pin (0=low, 1=high).
*/
void hal_pin_nss (u1_t val);
/*
* drive radio RX/TX pins (0=rx, 1=tx).
* drive radio RX/TX pins (0=rx, 1=tx). Actual polarity
* is determined by the value of HalPinmap_t::rxtx_rx_active.
*/
void hal_pin_rxtx (u1_t val);
@ -58,29 +68,19 @@ void hal_pin_rxtx (u1_t val);
*/
void hal_pin_rst (u1_t val);
// BEGIN ttn-esp32 change
// use higher level SPI functions
/*
* perform SPI write transaction with radio
* Perform SPI write transaction with radio chip
* - write the command byte 'cmd'
* - write 'len' bytes in 'buf'
* - write 'len' bytes out of 'buf'
*/
void hal_spi_write(u1_t cmd, const u1_t* buf, int len);
void hal_spi_write(u1_t cmd, const u1_t* buf, size_t len);
/*
* perform SPI read transaction with radio
* Perform SPI read transaction with radio chip
* - write the command byte 'cmd'
* - read 'len' bytes into 'buf'
*/
void hal_spi_read(u1_t cmd, u1_t* buf, int len);
/*
* perform 8-bit SPI transaction with radio.
* - write given byte 'outval'
* - read byte and return value
*/
//u1_t hal_spi (u1_t outval);
// END ttn-esp32 change
void hal_spi_read(u1_t cmd, u1_t* buf, size_t len);
/*
* disable all CPU interrupts.
@ -94,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.
*/
@ -123,13 +128,48 @@ 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(const hal_failure_handler_t* const);
/*
* get the calibration value for radio_rssi
*/
s1_t hal_getRssiCal (void);
/*
* control the radio state
* - if val == 0, turn tcxo off and otherwise prepare for sleep
* - if val == 1, turn tcxo on and otherwise prep for activity
* - return the number of ticks that we need to wait
*/
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
);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _hal_hpp_
#endif // _lmic_hal_h_

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2016 Matthijs Kooijman.
* Copyright (c) 2016-2018 MCCI Corporation.
* Copyright (c) 2016-2019 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -103,9 +103,9 @@ extern "C"{
// Arduino LMIC version
#define ARDUINO_LMIC_VERSION_CALC(major, minor, patch, local) \
(((major) << 24u) | ((minor) << 16u) | ((patch) << 8u) | (local))
(((major) << 24ul) | ((minor) << 16ul) | ((patch) << 8ul) | ((local) << 0ul))
#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(2, 2, 2, 0)
#define ARDUINO_LMIC_VERSION ARDUINO_LMIC_VERSION_CALC(3, 0, 99, 5) /* v3.0.99.5 */
#define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \
(((v) >> 24u) & 0xFFu)
@ -122,14 +122,19 @@ extern "C"{
//! Only For Antenna Tuning Tests !
//#define CFG_TxContinuousMode 1
enum { MAX_FRAME_LEN = 64 }; //!< Library cap on max frame length
// since this was annouunced 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 { 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
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 { 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
@ -153,14 +158,27 @@ struct band_t {
};
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+MAX_XCHANNELS+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 };
@ -189,14 +207,14 @@ 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
@ -220,52 +238,245 @@ 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;
}
enum {
// This value represents 100% error in LMIC.clockError
MAX_CLOCK_ERROR = 65536,
};
// 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
// without time request, "completing" the pending time request.
typedef void LMIC_ABI_STD lmic_request_network_time_cb_t(void *pUserData, int flagSuccess);
// how the network represents time.
typedef u4_t lmic_gpstime_t;
// rather than deal with 1/256 second tick, we adjust ostime back
// (as it's high res) to match tNetwork.
typedef struct lmic_time_reference_s lmic_time_reference_t;
struct lmic_time_reference_s {
// our best idea of when we sent the uplink (end of packet).
ostime_t tLocal;
// the network's best idea of when we sent the uplink.
lmic_gpstime_t tNetwork;
};
enum lmic_request_time_state_e {
lmic_RequestTimeState_idle = 0, // we're not doing anything
lmic_RequestTimeState_tx, // we want to tx a time request on next uplink
lmic_RequestTimeState_rx, // we have tx'ed, next downlink completes.
lmic_RequestTimeState_success // we sucessfully got time.
};
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 */
/* none at the moment */
};
/*
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
/* (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;
ostime_t globalDutyAvail; // time device can send again
// Channel scheduling
u4_t netid; // current network id (~0 - none)
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.
#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;
#elif CFG_LMIC_US_like
@ -275,83 +486,97 @@ struct lmic_t {
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;
#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
u1_t rxsyms;
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
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 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; // +0x80 = confirmed
u1_t pendTxLen; // count of bytes in pendTxData.
u1_t pendTxData[MAX_LEN_PAYLOAD];
u2_t devNonce; // last generated nonce
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
devaddr_t devaddr;
u4_t seqnoDn; // device level down stream seqno
u4_t seqnoUp;
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
lmic_request_time_state_t txDeviceTimeReqState; // current state, initially idle.
u1_t netDeviceTimeFrac; // updated on any DeviceTimeAns.
#endif
// rx1DrOffset is the offset from uplink to downlink datarate
u1_t rx1DrOffset; // captured from join. zero by default.
// 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)
@ -362,12 +587,12 @@ 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
//! The state of LMIC MAC layer is encapsulated in this variable.
DECLARE_LMIC; //!< \internal
@ -376,16 +601,20 @@ 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);
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);
@ -393,7 +622,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)
@ -405,9 +638,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);
@ -417,11 +647,29 @@ u4_t LMIC_getSeqnoUp (void);
u4_t LMIC_setSeqnoUp (u4_t);
void LMIC_getSessionKeys (u4_t *netid, devaddr_t *devaddr, xref2u1_t nwkKey, xref2u1_t artKey);
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);
// 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!!!

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
@ -51,19 +51,19 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
};
// see table in 2.7.6 -- this assumes UplinkDwellTime = 0.
static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
static CONST_TABLE(u1_t, maxFrameLens_dwell0)[] = {
59+5, // [0]
59+5, // [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.
static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = {
static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = {
0, // [0]
0, // [1]
19+5, // [2]
@ -74,18 +74,20 @@ static CONST_TABLE(u1_t, maxFrameLens_dwell1)[] = {
250+5 // [7]
};
static uint8_t
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
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 +97,7 @@ uint8_t LMICas923_maxFrameLen(uint8_t dr) {
else
return TABLE_GET_U1(maxFrameLens_dwell0, dr);
} else {
return 0xFF;
return 0;
}
}
@ -110,7 +112,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,6 +120,7 @@ static CONST_TABLE(s1_t, TXMAXEIRP)[16] = {
};
static int8_t LMICas923_getMaxEIRP(uint8_t mcmd_txparam) {
// if uninitialized, return default.
if (mcmd_txparam == 0xFF)
return AS923_TX_EIRP_MAX_DBM;
else
@ -127,18 +129,23 @@ static int8_t LMICas923_getMaxEIRP(uint8_t mcmd_txparam) {
(mcmd_txparam & MCMD_TxParam_MaxEIRP_MASK) >>
MCMD_TxParam_MaxEIRP_SHIFT
);
}
}
// translate from an encoded power to an actual power using
// the maxeirp setting.
// translate from an encoded power to an actual power using
// the maxeirp setting; return -128 if not legal.
int8_t LMICas923_pow2dBm(uint8_t mcmd_ladr_p1) {
s1_t const adj =
TABLE_GET_S1(
TXPOWLEVELS,
(mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT
);
return adj;
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,
pindex
);
return LMICas923_getMaxEIRP(LMIC.txParam) + adj;
} else {
return -128;
}
}
// only used in this module, but used by variant macro dr2hsym().
@ -162,7 +169,7 @@ ostime_t LMICas923_dr2hsym(uint8_t dr) {
enum { NUM_DEFAULT_CHANNELS = 2 };
static CONST_TABLE(u4_t, iniChannelFreq)[NUM_DEFAULT_CHANNELS] = {
// Default operational frequencies
AS923_F1 | BAND_CENTI,
AS923_F1 | BAND_CENTI,
AS923_F2 | BAND_CENTI,
};
@ -171,6 +178,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));
@ -217,6 +227,18 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
}
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
// zero the band bits in freq, just in case.
freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel.
if (freq == 0)
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
}
bit_t fEnable = (freq != 0);
if (chidx >= MAX_CHANNELS)
return 0;
if (band == -1) {
@ -226,10 +248,13 @@ bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
freq = (freq&~3) | band;
}
LMIC.channelFreq[chidx] = freq;
LMIC.channelDrMap[chidx] =
drmap == 0 ? DR_RANGE_MAP(AS923_DR_SF12, AS923_DR_SF7B)
LMIC.channelDrMap[chidx] =
drmap == 0 ? DR_RANGE_MAP(AS923_DR_SF12, AS923_DR_SF7B)
: drmap;
LMIC.channelMap |= 1 << chidx; // enabled right away
if (fEnable)
LMIC.channelMap |= 1 << chidx; // enabled right away
else
LMIC.channelMap &= ~(1 << chidx);
return 1;
}
@ -259,7 +284,9 @@ void LMICas923_setRx1Params(void) {
int const txdr = LMIC.dndr;
int effective_rx1DrOffset;
int candidateDr;
LMICeulike_setRx1Freq();
effective_rx1DrOffset = LMIC.rx1DrOffset;
// per section 2.7.7 of regional, lines 1101:1103:
switch (effective_rx1DrOffset) {
@ -267,22 +294,22 @@ void LMICas923_setRx1Params(void) {
case 7: effective_rx1DrOffset = -2; break;
default: /* no change */ break;
}
// per regional 2.2.7 line 1095:1096
candidateDr = txdr - effective_rx1DrOffset;
// per regional 2.2.7 lines 1097:1100
if (LMICas923_getDownlinkDwellBit(LMIC.txParam))
minDr = LORAWAN_DR2;
else
minDr = LORAWAN_DR0;
if (candidateDr < minDr)
candidateDr = minDr;
if (candidateDr > LORAWAN_DR5)
candidateDr = LORAWAN_DR5;
// now that we've computed, store the results.
LMIC.dndr = (uint8_t) candidateDr;
LMIC.rps = dndr2rps(LMIC.dndr);
@ -334,23 +361,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;
// LMIC.txParam is set to 0xFF by the central code at init time.
LMICeulike_initJoinLoop(NUM_DEFAULT_CHANNELS, /* adr dBm */ AS923_TX_EIRP_MAX_DBM);
}
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 +380,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;
}

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
@ -52,38 +52,83 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
MAKERPS(SF9 , BW500, CR_4_5, 0, 0), // [11]
MAKERPS(SF8 , BW500, CR_4_5, 0, 0), // [12]
MAKERPS(SF7 , BW500, CR_4_5, 0, 0), // [13]
ILLEGAL_RPS
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 };
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
LMICau921_getUplinkDwellBit() {
// if uninitialized, return default.
if (LMIC.txParam == 0xFF) {
return AU921_INITIAL_TxParam_UplinkDwellTime;
}
return (LMIC.txParam & MCMD_TxParam_TxDWELL_MASK) != 0;
}
uint8_t LMICau921_maxFrameLen(uint8_t dr) {
if (dr < LENOF_TABLE(maxFrameLens))
return TABLE_GET_U1(maxFrameLens, dr);
else
return 0xFF;
if (LMICau921_getUplinkDwellBit()) {
if (dr < LENOF_TABLE(maxFrameLens_dwell0))
return TABLE_GET_U1(maxFrameLens_dwell0, dr);
else
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 LMICau921_getMaxEIRP(uint8_t mcmd_txparam) {
// if uninitialized, return default.
if (mcmd_txparam == 0xFF)
return AU921_TX_EIRP_MAX_DBM;
else
return TABLE_GET_S1(
TXMAXEIRP,
(mcmd_txparam & MCMD_TxParam_MaxEIRP_MASK) >>
MCMD_TxParam_MaxEIRP_SHIFT
);
}
int8_t LMICau921_pow2dbm(uint8_t mcmd_ladr_p1) {
if ((mcmd_ladr_p1 & MCMD_LinkADRReq_POW_MASK) == MCMD_LinkADRReq_POW_MASK)
return -128;
else {
return ((s1_t)(LMICau921_getMaxEIRP(LMIC.txParam) - (((mcmd_ladr_p1)&MCMD_LinkADRReq_POW_MASK)<<1)));
}
}
static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = {
us2osticksRound(128 << 7), // DR_SF12
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
us2osticksRound(128 << 4), // DR_SF9
us2osticksRound(128 << 3), // DR_SF8
us2osticksRound(128 << 2), // DR_SF7
us2osticksRound(128 << 1), // DR_SF8C
us2osticksRound(128 << 0), // ------
us2osticksRound(128 << 0), // ------
us2osticksRound(128 << 5), // DR_SF12CR
us2osticksRound(128 << 4), // DR_SF11CR
us2osticksRound(128 << 3), // DR_SF10CR
us2osticksRound(128 << 4), // DR_SF11CR
us2osticksRound(128 << 3), // DR_SF10CR
us2osticksRound(128 << 2), // DR_SF9CR
us2osticksRound(128 << 1), // DR_SF8CR
us2osticksRound(128 << 0), // DR_SF7CR
};
// get ostime for symbols based on datarate. This is not like us915,
// get ostime for symbols based on datarate. This is not like us915,
// becuase the times don't match between the upper half and lower half
// of the table.
ostime_t LMICau921_dr2hsym(uint8_t dr) {
@ -109,9 +154,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 +166,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,47 +181,57 @@ 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) {
u1_t chnl = LMIC.txChnl;
LMIC.txpow = AU921_TX_EIRP_MAX_DBM;
LMIC.txpow = LMICau921_getMaxEIRP(LMIC.txParam);
if (chnl < 64) {
LMIC.freq = AU921_125kHz_UPFBASE + chnl*AU921_125kHz_UPFSTEP;
} else {
@ -179,10 +239,23 @@ void LMICau921_updateTx(ostime_t txbeg) {
LMIC.freq = AU921_500kHz_UPFBASE + (chnl - 64)*AU921_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 (LMICau921_getUplinkDwellBit(LMIC.txParam)) {
dwellDelay = AU921_UPLINK_DWELL_TIME_osticks;
}
if (dwellDelay > globalDutyDelay) {
globalDutyDelay = dwellDelay;
}
if (globalDutyDelay != 0) {
LMIC.globalDutyAvail = txbeg + globalDutyDelay;
}
}
@ -213,6 +286,14 @@ void LMICau921_setRx1Params(void) {
LMIC.rps = dndr2rps(LMIC.dndr);
}
void LMICau921_initJoinLoop(void) {
// LMIC.txParam is set to 0xFF by the central code at init time.
LMICuslike_initJoinLoop();
// initialize the adrTxPower.
LMIC.adrTxPow = LMICau921_getMaxEIRP(LMIC.txParam); // dBm
}
//
// END: AU921 related stuff

73
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 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -41,6 +41,8 @@
# include "lmic_bandplan_au921.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,65 @@
#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
//
// 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 +226,7 @@
// 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);
ostime_t LMICcore_RxWindowOffset(ostime_t hsym, u1_t rxsyms_in);
#endif // _lmic_bandplan_h_

20
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 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,4 @@ void LMICas923_updateTx(ostime_t txbeg);
ostime_t LMICas923_nextJoinTime(ostime_t now);
#define LMICbandplan_nextJoinTime(now) LMICas923_nextJoinTime(now)
#endif // _lmic_as923_h_
#endif // _lmic_bandplan_as923_h_

24
src/lmic/lmic_bandplan_au921.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
@ -26,27 +26,33 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _lmic_au921_h_
# define _lmic_au921_h_
#ifndef _lmic_bandplan_au921_h_
# define _lmic_bandplan_au921_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
// return maximum frame length (including PHY header) for this data rate (au921); 0 --> not valid dr.
uint8_t LMICau921_maxFrameLen(uint8_t dr);
#define maxFrameLen(dr) LMICau921_maxFrameLen(dr)
// return maximum frame length (including PHY header) for this data rate; 0 --> not valid dr.
#define LMICbandplan_maxFrameLen(dr) LMICau921_maxFrameLen(dr)
#define pow2dBm(mcmd_ladr_p1) ((s1_t)(30 - (((mcmd_ladr_p1)&MCMD_LADR_POW_MASK)<<1)))
int8_t LMICau921_pow2dbm(uint8_t mcmd_ladr_p1);
#define pow2dBm(mcmd_ladr_p1) LMICau921_pow2dbm(mcmd_ladr_p1)
ostime_t LMICau921_dr2hsym(uint8_t dr);
#define dr2hsym(dr) LMICau921_dr2hsym(dr)
#define LMICbandplan_getInitialDrJoin() (EU868_DR_SF7)
#define LMICbandplan_getInitialDrJoin() (LORAWAN_DR2)
void LMICau921_initJoinLoop(void);
#define LMICbandplan_initJoinLoop() LMICau921_initJoinLoop()
void LMICau921_setBcnRxParams(void);
#define LMICbandplan_setBcnRxParams() LMICau921_setBcnRxParams()
@ -60,4 +66,4 @@ void LMICau921_setRx1Params(void);
void LMICau921_updateTx(ostime_t txbeg);
#define LMICbandplan_updateTx(txbeg) LMICau921_updateTx(txbeg)
#endif // _lmic_au921_h_
#endif // _lmic_bandplan_au921_h_

17
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 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,7 @@ 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()
#endif // _lmic_eu868_h_

23
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 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,7 @@ 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()
#endif // _lmic_bandplan_in866_h_

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* 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 _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)
#endif // _lmic_kr920_h_

23
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 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,13 @@ 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_
#endif // _lmic_bandplan_us915_h_

768
src/lmic/lmic_compliance.c Normal file
View File

@ -0,0 +1,768 @@
/*
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: {
// not clear what this request does.
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_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,
LMIC.client.txMessageCb
);
LMIC_Compliance.eventflags |= LMIC_COMPLIANCE_EVENT_UPLINK_COMPLETE;
fsmEval();
}
}
static void sendUplinkCompleteCb(void *pUserData, int 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_ */

View File

@ -70,7 +70,7 @@ Revision history:
#define LMIC_REGION_au921 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
@ -104,7 +104,7 @@ Revision history:
(1 << LMIC_REGION_au921) | \
/* (1 << LMIC_REGION_cn490) | */ \
(1 << LMIC_REGION_as923) | \
/* (1 << LMIC_REGION_kr921) | */ \
(1 << LMIC_REGION_kr920) | \
(1 << LMIC_REGION_in866) | \
0)
@ -114,6 +114,12 @@ Revision history:
// 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) | \
@ -122,11 +128,13 @@ Revision history:
(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_kr920) << LMIC_REGION_kr920) | \
(defined(CFG_in866) << LMIC_REGION_in866) | \
0)
// the selected region.
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
#if defined(CFG_eu868)
# define CFG_region LMIC_REGION_eu868
#elif defined(CFG_us915)
@ -139,17 +147,25 @@ Revision history:
# define CFG_region LMIC_REGION_au921
#elif defined(CFG_cn490)
# define CFG_region LMIC_REGION_cn490
#elif defined(CFG_as923jp)
# define CFG_as923 1 /* CFG_as923jp implies CFG_as923 */
# define CFG_region LMIC_REGION_as923
# 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
// finally the mask of` US-like and EU-like regions
// 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.
#define CFG_LMIC_EU_like_MASK ( \
(1 << LMIC_REGION_eu868) | \
/* (1 << LMIC_REGION_us915) | */ \
@ -158,10 +174,16 @@ Revision history:
/* (1 << LMIC_REGION_au921) | */ \
/* (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.
#define CFG_LMIC_US_like_MASK ( \
/* (1 << LMIC_REGION_eu868) | */ \
(1 << LMIC_REGION_us915) | \
@ -170,13 +192,25 @@ Revision history:
(1 << LMIC_REGION_au921) | \
/* (1 << LMIC_REGION_cn490) | */ \
/* (1 << LMIC_REGION_as923) | */ \
/* (1 << LMIC_REGION_kr921) | */ \
/* (1 << LMIC_REGION_kr920) | */ \
/* (1 << LMIC_REGION_in866) | */ \
0)
//
// booleans that are true if the configured region is EU-like or US-like.
// TODO(tmm@mcci.com) consider moving this block to a central file as it's not
// user-editable.
//
#define CFG_LMIC_EU_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_EU_like_MASK))
#define CFG_LMIC_US_like (!!(CFG_LMIC_REGION_MASK & CFG_LMIC_US_like_MASK))
//
// The supported LMIC LoRaWAAN spec versions. These need to be numerically ordered,
// so that we can (for example) compare
//
// LMIC_LORAWAN_SPEC_VERSION < LMIC_LORAWAN_SPEC_VERSION_1_0_3.
//
#define LMIC_LORAWAN_SPEC_VERSION_1_0_2 0x01000200u
#define LMIC_LORAWAN_SPEC_VERSION_1_0_3 0x01000300u
#endif /* _LMIC_CONFIG_PRECONDITIONS_H_ */

251
src/lmic/lmic_env.h Normal file
View File

@ -0,0 +1,251 @@
/*
Module: lmic_env.h
Function:
Sets up macros etc. to make things a little easier for portabilty
Copyright notice and license info:
See LICENSE file accompanying this project.
Author:
Terry Moore, MCCI Corporation November 2018
Description:
This file is an adaptation of MCCI's standard IOCTL framework.
We duplicate a bit of functionality that we might get from other
libraries, so that the LMIC library can continue to stand alone.
*/
#ifndef _lmic_env_h_ /* prevent multiple includes */
#define _lmic_env_h_
/*
Macro: LMIC_C_ASSERT()
Function:
Declaration-like macro that will cause a compile error if arg is FALSE.
Definition:
LMIC_C_ASSERT(
BOOL fErrorIfFalse
);
Description:
This macro, if used where an external reference declarataion is
permitted, will either compile cleanly, or will cause a compilation
error. The results of using this macro where a declaration is not
permitted are unspecified.
This is different from #if !(fErrorIfFalse) / #error in that the
expression is evaluated by the compiler rather than by the pre-
processor. Therefore things like sizeof() can be used.
Returns:
No explicit result -- either compiles cleanly or causes a compile
error.
*/
#ifndef LMIC_C_ASSERT
# define LMIC_C_ASSERT(e) \
void LMIC_C_ASSERT__(int LMIC_C_ASSERT_x[(e) ? 1: -1])
#endif
/****************************************************************************\
|
| Define the begin/end declaration tags for C++ co-existance
|
\****************************************************************************/
#ifdef __cplusplus
# define LMIC_BEGIN_DECLS extern "C" {
# define LMIC_END_DECLS }
#else
# define LMIC_BEGIN_DECLS /* nothing */
# define LMIC_END_DECLS /* nothing */
#endif
//----------------------------------------------------------------------------
// Annotations to avoid various "unused" warnings. These must appear as a
// statement in the function body; the macro annotates the variable to quiet
// compiler warnings. The way this is done is compiler-specific, and so these
// definitions are fall-backs, which might be overridden.
//
// Although these are all similar, we don't want extra macro expansions,
// so we define each one explicitly rather than relying on a common macro.
//----------------------------------------------------------------------------
// signal that a parameter is intentionally unused.
#ifndef LMIC_UNREFERENCED_PARAMETER
# define LMIC_UNREFERENCED_PARAMETER(v) do { (void) (v); } while (0)
#endif
// an API parameter is a parameter that is required by an API definition, but
// happens to be unreferenced in this implementation. This is a stronger
// assertion than LMIC_UNREFERENCED_PARAMETER(): this parameter is here
// becuase of an API contract, but we have no use for it in this function.
#ifndef LMIC_API_PARAMETER
# define LMIC_API_PARAMETER(v) do { (void) (v); } while (0)
#endif
// an intentionally-unreferenced variable.
#ifndef LMIC_UNREFERENCED_VARIABLE
# define LMIC_UNREFERENCED_VARIABLE(v) do { (void) (v); } while (0)
#endif
// we have three (!) debug levels (LMIC_DEBUG_LEVEL > 0, LMIC_DEBUG_LEVEL > 1,
// and LMIC_X_DEBUG_LEVEL > 0. In each case we might have parameters or
// or varables that are only refereneced at the target debug level.
// Parameter referenced only if debugging at level > 0.
#ifndef LMIC_DEBUG1_PARAMETER
# if LMIC_DEBUG_LEVEL > 0
# define LMIC_DEBUG1_PARAMETER(v) do { ; } while (0)
# else
# define LMIC_DEBUG1_PARAMETER(v) do { (void) (v); } while (0)
# endif
#endif
// variable referenced only if debugging at level > 0
#ifndef LMIC_DEBUG1_VARIABLE
# if LMIC_DEBUG_LEVEL > 0
# define LMIC_DEBUG1_VARIABLE(v) do { ; } while (0)
# else
# define LMIC_DEBUG1_VARIABLE(v) do { (void) (v); } while (0)
# endif
#endif
// parameter referenced only if debugging at level > 1
#ifndef LMIC_DEBUG2_PARAMETER
# if LMIC_DEBUG_LEVEL > 1
# define LMIC_DEBUG2_PARAMETER(v) do { ; } while (0)
# else
# define LMIC_DEBUG2_PARAMETER(v) do { (void) (v); } while (0)
# endif
#endif
// variable referenced only if debugging at level > 1
#ifndef LMIC_DEBUG2_VARIABLE
# if LMIC_DEBUG_LEVEL > 1
# define LMIC_DEBUG2_VARIABLE(v) do { ; } while (0)
# else
# define LMIC_DEBUG2_VARIABLE(v) do { (void) (v); } while (0)
# endif
#endif
// parameter referenced only if LMIC_X_DEBUG_LEVEL > 0
#ifndef LMIC_X_DEBUG_PARAMETER
# if LMIC_X_DEBUG_LEVEL > 0
# define LMIC_X_DEBUG_PARAMETER(v) do { ; } while (0)
# else
# define LMIC_X_DEBUG_PARAMETER(v) do { (void) (v); } while (0)
# endif
#endif
// variable referenced only if LMIC_X_DEBUG_LEVEL > 0
#ifndef LMIC_X_DEBUG_VARIABLE
# if LMIC_X_DEBUG_LEVEL > 0
# define LMIC_X_DEBUG_VARIABLE(v) do { ; } while (0)
# else
# define LMIC_X_DEBUG_VARIABLE(v) do { (void) (v); } while (0)
# endif
#endif
// parameter referenced only if EV() macro is enabled (which it never is)
// TODO(tmm@mcci.com) take out the EV() framework as it reuqires C++, and
// this code is really C-99 to its bones.
#ifndef LMIC_EV_PARAMETER
# define LMIC_EV_PARAMETER(v) do { (void) (v); } while (0)
#endif
// variable referenced only if EV() macro is defined.
#ifndef LMIC_EV_VARIABLE
# define LMIC_EV_VARIABLE(v) do { (void) (v); } while (0)
#endif
/*
Macro: LMIC_ABI_STD
Index: Macro: LMIC_ABI_VARARGS
Function:
Annotation macros to force a particular binary calling sequence.
Definition:
#define LMIC_ABI_STD compiler-specific
#define LMIC_ABI_VARARGS compiler-specific
Description:
These macros are used when declaring a function type, and indicate
that a particular calling sequence is to be used. They are normally
used between the type portion of the function declaration and the
name of the function. For example:
typedef void LMIC_ABI_STD myCallBack_t(void);
It's important to use this in libraries on platforms with multiple
calling sequences, because different components can be compiled with
different defaults.
Returns:
Not applicable.
*/
/* ABI marker for normal (fixed parameter count) functions -- used for function types */
#ifndef LMIC_ABI_STD
# ifdef _MSC_VER
# define LMIC_ABI_STD __stdcall
# else
# define LMIC_ABI_STD /* nothing */
# endif
#endif
/* ABI marker for VARARG functions -- used for function types */
#ifndef LMIC_ABI_VARARGS
# ifdef _MSC_VER
# define LMIC_ABI_VARARGS __cdecl
# else
# define LMIC_ABI_VARARGS /* nothing */
# 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_ */

124
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 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -49,21 +49,28 @@ CONST_TABLE(u1_t, _DR2RPS_CRC)[] = {
ILLEGAL_RPS
};
static CONST_TABLE(u1_t, maxFrameLens)[] = { 64,64,64,123 };
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 +82,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 +100,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 +114,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 +130,59 @@ 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,
};
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
// zero the band bits in freq, just in case.
freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel.
if (freq == 0)
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
}
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
}
else {
if (band > BAND_AUX) return 0;
freq = (freq&~3) | band;
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;
}
}
// 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;
LMIC.channelMap |= 1 << chidx; // enabled right away
if (fEnable)
LMIC.channelMap |= 1 << chidx; // enabled right away
else
LMIC.channelMap &= ~(1 << chidx);
return 1;
}
@ -213,12 +247,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

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
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
// 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]&~3) == 0)
return 0; // fail - channel is not defined
}
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;
}
LMIC.channelMap = chmap;
return 1;
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)
@ -113,25 +173,26 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
LMIC.txChnl = 0;
if ((++LMIC.txCnt % nDefaultChannels) == 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;
// 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()
//
//
// 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()
//
// TODO(tmm@mcci.com) - see above; please remove regional dependency from this file.
#if CFG_region == LMIC_REGION_as923
// in the join of AS923 v1.1 or older, only DR2 is used.
// 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
{
// TODO(tmm@mcci.com) - see above; please remove regional dependency from this file.
#if CFG_region != LMIC_REGION_as923
else {
LMICcore_setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
#else
// in the join of AS923 v1.1 or older, only DR2 is used.
// no need to change the DR.
LMIC.datarate = AS923_DR_SF10;
#endif
}
#endif
}
// Clear NEXTCHNL because join state engine controls channel hopping
LMIC.opmode &= ~OP_NEXTCHNL;
@ -139,12 +200,12 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
// Duty cycle is based on txend.
ostime_t const time = LMICbandplan_nextJoinTime(os_getTime());
// TODO(tmm@mcci.com): change delay to (0:1) secs + a known t0, but randomized;
// TODO(tmm@mcci.com): change delay to (0:1) secs + a known t0, but randomized;
// starting adding a bias after 1 hour, 25 hours, etc.; and limit the duty
// cycle on power up. For testability, add a way to set the join start time
// externally (a test API) so we can check this feature.
// See https://github.com/mcci-catena/arduino-lmic/issues/2
// Current code doesn't match LoRaWAN 1.0.2 requirements.
// Current code doesn't match LoRaWAN 1.0.2 requirements.
LMIC.txend = time +
(isTESTMODE()
@ -159,4 +220,49 @@ ostime_t LMICeulike_nextJoinState(uint8_t nDefaultChannels) {
}
#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) {
ostime_t const hsym = us2osticksRound(80);
// start a little earlier.
delay -= LMICbandplan_PRERX_FSK * us2osticksRound(160);
// set LMIC.rxtime and LMIC.rxsyms:
LMIC.rxtime = LMIC.txend + LMICcore_adjustForDrift(delay + LMICcore_RxWindowOffset(hsym, LMICbandplan_RXLEN_FSK), hsym);
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
}
#endif // CFG_LMIC_EU_like

29
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)
@ -74,14 +75,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 +96,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_

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
@ -49,21 +49,28 @@ 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 };
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;
else
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().
@ -88,8 +95,8 @@ ostime_t LMICin866_dr2hsym(uint8_t dr) {
enum { NUM_DEFAULT_CHANNELS = 3 };
static CONST_TABLE(u4_t, iniChannelFreq)[NUM_DEFAULT_CHANNELS] = {
// Default operational frequencies
IN866_F1 | BAND_MILLI,
IN866_F2 | BAND_MILLI,
IN866_F1 | BAND_MILLI,
IN866_F2 | BAND_MILLI,
IN866_F3 | BAND_MILLI,
};
@ -98,6 +105,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));
@ -125,17 +135,32 @@ bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) {
}
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
// zero the band bits in freq, just in case.
freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel.
if (freq == 0)
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
}
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;
LMIC.channelMap |= 1 << chidx; // enabled right away
if (fEnable)
LMIC.channelMap |= 1 << chidx; // enabled right away
else
LMIC.channelMap &= ~(1 << chidx);
return 1;
}
@ -169,7 +194,7 @@ ostime_t LMICin866_nextTx(ostime_t now) {
}
}
// no enabled channel found! just use the last channel.
// no enabled channel found! just use the last channel.
return now;
}
@ -187,12 +212,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

274
src/lmic/lmic_kr920.c Normal file
View File

@ -0,0 +1,274 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* 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.
*/
#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]
};
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;
}
bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
// zero the band bits in freq, just in case.
freq &= ~3;
if (chidx < NUM_DEFAULT_CHANNELS) {
// can't disable a default channel.
if (freq == 0)
return 0;
// can't change a default channel.
else if (freq != (LMIC.channelFreq[chidx] & ~3))
return 0;
}
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;
}
// 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.
ostime_t LMICkr920_nextTx(ostime_t now) {
const u1_t band = BAND_MILLI;
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;
}
}
}
// no enabled channel found! just use the last channel.
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

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
@ -55,13 +55,26 @@ 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 };
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)[] = {
@ -98,9 +111,11 @@ bit_t LMIC_setupChannel(u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
return 1;
}
void LMIC_disableChannel(u1_t channel) {
bit_t LMIC_disableChannel(u1_t channel) {
bit_t result = 0;
if (channel < 72 + MAX_XCHANNELS) {
if (ENABLED_CHANNEL(channel)) {
result = 1;
if (IS_CHANNEL_125khz(channel))
LMIC.activeChannels125khz--;
else if (IS_CHANNEL_500khz(channel))
@ -108,11 +123,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 + MAX_XCHANNELS) {
if (!ENABLED_CHANNEL(channel)) {
result = 1;
if (IS_CHANNEL_125khz(channel))
LMIC.activeChannels125khz++;
else if (IS_CHANNEL_500khz(channel))
@ -120,42 +138,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) {
@ -193,16 +221,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

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
@ -91,31 +91,54 @@ void LMICuslike_initDefaultChannels(bit_t fJoin) {
LMIC.activeChannels500khz = 8;
}
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.
*/
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 == 0 || (chmap & 0xFF00) != 0) {
// no bits set, or reserved bitsset , fail.
return 0;
}
} else if (chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON ||
chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF) {
u1_t const en125 = chpage == MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON;
// if disabling all 125kHz chans, must have at least one 500kHz chan
// don't allow reserved bits to be set in chmap.
if ((! en125 && chmap == 0) || (chmap & 0xFF00) != 0)
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,37 +146,48 @@ 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;
// enable or disable all 125kHz channels
for (u1_t chnl = 0; chnl < 64; ++chnl) {
if (en125)
LMIC_enableChannel(chnl);
else
LMIC_disableChannel(chnl);
}
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;
// then apply mask to top 8 channels.
base = 64;
top = 72;
} else {
return 0;
}
// enable or disable all 125kHz channels
for (u1_t chnl = 0; chnl < 64; ++chnl) {
if (en125)
LMIC_enableChannel(chnl);
else
LMIC_disableChannel(chnl);
}
// apply chmap to channels in [base..top-1].
// Use enable/disable channel to keep activeChannel counts in sync.
for (u1_t chnl = base; chnl < top; ++chnl, chmap >>= 1) {
if (chmap & 0x0001)
LMIC_enableChannel(chnl);
else
LMIC_disableChannel(chnl);
// then apply mask to top 8 channels.
base = 64;
top = 72;
}
// apply chmap to channels in [base..top-1].
// Use enable/disable channel to keep activeChannel counts in sync.
for (u1_t chnl = base; chnl < top; ++chnl, chmap >>= 1) {
if (chmap & 0x0001)
LMIC_enableChannel(chnl);
else
LMIC_disableChannel(chnl);
}
}
return 1;
LMICOS_logEventUint32("LMICuslike_mapChannels", (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,16 +195,38 @@ 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);
setNextChannel(64, 64 + 8, LMIC.activeChannels500khz);
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);
setNextChannel(0, 64, LMIC.activeChannels125khz);
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
@ -183,18 +239,13 @@ void LMICuslike_initJoinLoop(void) {
// 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.
@ -233,7 +284,7 @@ ostime_t LMICuslike_nextJoinState(void) {
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
}
@ -260,4 +311,29 @@ ostime_t LMICuslike_nextJoinState(void) {
}
#endif
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

31
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) {
@ -66,7 +69,7 @@ LMICuslike_isValidBeacon1(const uint8_t *d) {
#define LMICbandplan_advanceBeaconChannel() \
do { LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; } while (0)
// TODO(tmm@mcci.com): decide whether we want to do this on every
// TODO(tmm@mcci.com): decide whether we want to do this on every
// reset or just restore the last sub-band selected by the user.
#define LMICbandplan_resetDefaultChannels() \
LMICbandplan_initDefaultChannels(/* normal */ 0)
@ -77,24 +80,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

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

@ -9,7 +9,7 @@ Copyright & License:
See accompanying LICENSE file.
Author:
Terry Moore, MCCI September 2019
Terry Moore, MCCI September 2018
*/

View File

@ -44,7 +44,14 @@ typedef u1_t cr_t;
typedef u1_t sf_t;
typedef u1_t bw_t;
typedef u1_t dr_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 +59,7 @@ enum { ILLEGAL_RPS = 0xFF };
// Global maximum frame length
enum { STD_PREAMBLE_LEN = 8 };
enum { MAX_LEN_FRAME = 64 };
enum { MAX_LEN_FRAME = LMIC_ENABLE_long_messages ? 255 : 64 };
enum { LEN_DEVNONCE = 2 };
enum { LEN_ARTNONCE = 3 };
enum { LEN_NETID = 3 };
@ -97,6 +104,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
@ -221,8 +230,8 @@ enum _dr_configured_t {
#include "lorabase_au921.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
@ -319,6 +328,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"
@ -414,7 +468,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 {
@ -422,16 +475,21 @@ enum {
};
enum {
// Bitfields in frame control octet
FCT_ADREN = 0x80,
FCT_ADRARQ = 0x40,
FCT_ACK = 0x20,
FCT_MORE = 0x10, // also in DN direction: Class B indicator
FCT_OPTLEN = 0x0F,
FCT_ADREN = 0x80,
FCT_ADRACKReq = 0x40,
FCT_ACK = 0x20,
FCT_MORE = 0x10, // also in DN direction: Class B indicator
FCT_OPTLEN = 0x0F,
};
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,63 +498,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_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_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 {
@ -506,65 +579,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,
MCMD_LinkADRReq_Redundancy_ChMaskCntl_MASK= 0x70,
MCMD_LinkADRReq_Redundancy_NbTrans_MASK = 0x0F,
MCMD_LinkADRReq_ChMaskCntl_EULIKE_DIRECT = 0x00, // direct masking for EU
MCMD_LinkADRReq_ChMaskCntl_EULIKE_ALL_ON = 0x60, // EU: enable everything.
MCMD_LinkADRReq_ChMaskCntl_USLIKE_500K = 0x40, // mask is for the 8 us-like 500 kHz channels
MCMD_LinkADRReq_ChMaskCntl_USLIKE_SPECIAL = 0x50, // first special for us-like
MCMD_LinkADRReq_ChMaskCntl_USLIKE_BANK = 0x50, // special: bits are banks.
MCMD_LinkADRReq_ChMaskCntl_USLIKE_125ON = 0x60, // special channel page enable, bits applied to 64..71
MCMD_LinkADRReq_ChMaskCntl_USLIKE_125OFF = 0x70, // special channel page: disble 125K, bits apply to 64..71
MCMD_LinkADRReq_ChMaskCntl_CN470_ALL_ON = 0x60, // 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

9
src/lmic/lorabase_as923.h Executable file → Normal file
View File

@ -93,4 +93,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_ */

5
src/lmic/lorabase_au921.h Executable file → Normal file
View File

@ -76,6 +76,11 @@ enum {
enum {
AU921_TX_EIRP_MAX_DBM = 30 // 30 dBm
};
enum {
// initial value of UplinkDwellTime before TxParamSetupReq received.
AU921_INITIAL_TxParam_UplinkDwellTime = 1,
AU921_UPLINK_DWELL_TIME_osticks = sec2osticks(20),
};
enum { DR_PAGE_AU921 = 0x10 * (LMIC_REGION_au921 - 1) };

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_ */

8
src/lmic/lorabase_us915.h Executable file → Normal file
View File

@ -73,10 +73,16 @@ enum {
US915_FREQ_MAX = 928000000
};
enum {
US915_TX_MAX_DBM = 30 // 30 dBm (but not EIRP): assumes we're
US915_TX_MAX_DBM = 30 // 30 dBm (but not EIRP): assumes we're
// 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_ */

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2016-2017 MCCI Corporation.
* Copyright (c) 2016-2017, 2019 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -68,13 +68,15 @@ static int unlinkjob (osjob_t** pnext, osjob_t* job) {
return 0;
}
static osjob_t** getJobQueue(osjob_t* job) {
return os_jobIsTimed(job) ? &OS.scheduledjobs : &OS.runnablejobs;
}
// clear scheduled job
void os_clearCallback (osjob_t* job) {
hal_disableIRQs();
// if it's not in the scheduled jobs, look in the runnable...
if (! unlinkjob(&OS.scheduledjobs, job))
unlinkjob(&OS.runnablejobs, job);
unlinkjob(getJobQueue(job), job);
hal_enableIRQs();
}
@ -83,11 +85,15 @@ void os_clearCallback (osjob_t* job) {
void os_setCallback (osjob_t* job, osjobcb_t cb) {
osjob_t** pnext;
hal_disableIRQs();
// remove if job was already queued
unlinkjob(&OS.runnablejobs, job);
// fill-in job
job->func = cb;
unlinkjob(getJobQueue(job), job);
// fill-in job. Ascending memory order is write-queue friendly
job->next = NULL;
job->deadline = 0;
job->func = cb;
// add to end of run queue
for(pnext=&OS.runnablejobs; *pnext; pnext=&((*pnext)->next));
*pnext = job;
@ -97,13 +103,21 @@ void os_setCallback (osjob_t* job, osjobcb_t cb) {
// schedule timed job
void os_setTimedCallback (osjob_t* job, ostime_t time, osjobcb_t cb) {
osjob_t** pnext;
// special case time 0 -- it will be one tick late.
if (time == 0)
time = 1;
hal_disableIRQs();
// remove if job was already queued
unlinkjob(&OS.scheduledjobs, job);
unlinkjob(getJobQueue(job), job);
// fill-in job
job->next = NULL;
job->deadline = time;
job->func = cb;
job->next = NULL;
// insert into schedule
for(pnext=&OS.scheduledjobs; *pnext; pnext=&((*pnext)->next)) {
if((*pnext)->deadline - time > 0) { // (cmp diff, not abs!)
@ -141,3 +155,13 @@ void os_runloop_once() {
j->func(j);
}
}
// return true if there are any jobs scheduled within time ticks from now.
// return false if any jobs scheduled are at least time ticks in the future.
bit_t os_queryTimeCriticalJobs(ostime_t time) {
if (OS.scheduledjobs &&
OS.scheduledjobs->deadline - os_getTime() < time)
return 1;
else
return 0;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2018, 2019 MCCI Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -29,29 +30,22 @@
#ifndef _oslmic_h_
#define _oslmic_h_
// Dependencies required for the LoRa MAC in C to run.
// Dependencies required for the LMIC to run.
// These settings can be adapted to the underlying system.
// You should not, however, change the lmic.[hc]
// You should not, however, change the lmic merely for porting purposes.[hc]
#include "config.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C"{
#ifndef _lmic_env_h_
# include "lmic_env.h"
#endif
//================================================================================
//================================================================================
// Target platform as C library
typedef uint8_t bit_t;
typedef uint8_t u1_t;
typedef int8_t s1_t;
typedef uint16_t u2_t;
typedef int16_t s2_t;
typedef uint32_t u4_t;
typedef int32_t s4_t;
typedef unsigned int uint;
typedef const char* str_t;
#ifndef _oslmic_types_h_
# include "oslmic_types.h"
#endif
LMIC_BEGIN_DECLS
#include <string.h>
#include "hal.h"
@ -73,7 +67,6 @@ typedef struct rxsched_t rxsched_t;
typedef struct bcninfo_t bcninfo_t;
typedef const u1_t* xref2cu1_t;
typedef u1_t* xref2u1_t;
typedef s4_t ostime_t;
// int32_t == s4_t is long on some platforms; and someday
// we will want 64-bit ostime_t. So, we will use a macro for the
@ -91,107 +84,7 @@ typedef s4_t ostime_t;
#define SIZEOFEXPR(x) sizeof(x)
//----------------------------------------------------------------------------
// Annotations to avoid various "unused" warnings. These must appear as a
// statement in the function body; the macro annotates the variable to quiet
// compiler warnings. The way this is done is compiler-specific, and so these
// definitions are fall-backs, which might be overridden.
//
// Although these are all similar, we don't want extra macro expansions,
// so we define each one explicitly rather than relying on a common macro.
//----------------------------------------------------------------------------
// signal that a parameter is intentionally unused.
#ifndef LMIC_UNREFERENCED_PARAMETER
# define LMIC_UNREFERENCED_PARAMETER(v) do { (void) (v); } while (0)
#endif
// an API parameter is a parameter that is required by an API definition, but
// happens to be unreferenced in this implementation. This is a stronger
// assertion than LMIC_UNREFERENCED_PARAMETER(): this parameter is here
// becuase of an API contract, but we have no use for it in this function.
#ifndef LMIC_API_PARAMETER
# define LMIC_API_PARAMETER(v) do { (void) (v); } while (0)
#endif
// an intentionally-unreferenced variable.
#ifndef LMIC_UNREFERENCED_VARIABLE
# define LMIC_UNREFERENCED_VARIABLE(v) do { (void) (v); } while (0)
#endif
// we have three (!) debug levels (LMIC_DEBUG_LEVEL > 0, LMIC_DEBUG_LEVEL > 1,
// and LMIC_X_DEBUG_LEVEL > 0. In each case we might have parameters or
// or varables that are only refereneced at the target debug level.
// Parameter referenced only if debugging at level > 0.
#ifndef LMIC_DEBUG1_PARAMETER
# if LMIC_DEBUG_LEVEL > 0
# define LMIC_DEBUG1_PARAMETER(v) do { ; } while (0)
# else
# define LMIC_DEBUG1_PARAMETER(v) do { (void) (v); } while (0)
# endif
#endif
// variable referenced only if debugging at level > 0
#ifndef LMIC_DEBUG1_VARIABLE
# if LMIC_DEBUG_LEVEL > 0
# define LMIC_DEBUG1_VARIABLE(v) do { ; } while (0)
# else
# define LMIC_DEBUG1_VARIABLE(v) do { (void) (v); } while (0)
# endif
#endif
// parameter referenced only if debugging at level > 1
#ifndef LMIC_DEBUG2_PARAMETER
# if LMIC_DEBUG_LEVEL > 1
# define LMIC_DEBUG2_PARAMETER(v) do { ; } while (0)
# else
# define LMIC_DEBUG2_PARAMETER(v) do { (void) (v); } while (0)
# endif
#endif
// variable referenced only if debugging at level > 1
#ifndef LMIC_DEBUG2_VARIABLE
# if LMIC_DEBUG_LEVEL > 1
# define LMIC_DEBUG2_VARIABLE(v) do { ; } while (0)
# else
# define LMIC_DEBUG2_VARIABLE(v) do { (void) (v); } while (0)
# endif
#endif
// parameter referenced only if LMIC_X_DEBUG_LEVEL > 0
#ifndef LMIC_X_DEBUG_PARAMETER
# if LMIC_X_DEBUG_LEVEL > 0
# define LMIC_X_DEBUG_PARAMETER(v) do { ; } while (0)
# else
# define LMIC_X_DEBUG_PARAMETER(v) do { (void) (v); } while (0)
# endif
#endif
// variable referenced only if LMIC_X_DEBUG_LEVEL > 0
#ifndef LMIC_X_DEBUG_VARIABLE
# if LMIC_X_DEBUG_LEVEL > 0
# define LMIC_X_DEBUG_VARIABLE(v) do { ; } while (0)
# else
# define LMIC_X_DEBUG_VARIABLE(v) do { (void) (v); } while (0)
# endif
#endif
// parameter referenced only if EV() macro is enabled (which it never is)
// TODO(tmm@mcci.com) take out the EV() framework as it reuqires C++, and
// this code is really C-99 to its bones.
#ifndef LMIC_EV_PARAMETER
# define LMIC_EV_PARAMETER(v) do { (void) (v); } while (0)
#endif
// variable referenced only if EV() macro is defined.
#ifndef LMIC_EV_VARIABLE
# define LMIC_EV_VARIABLE(v) do { (void) (v); } while (0)
#endif
#define ON_LMIC_EVENT(ev) onEvent(ev)
#define DECL_ON_LMIC_EVENT void onEvent(ev_t e)
#define DECL_ON_LMIC_EVENT LMIC_DECLARE_FUNCTION_WEAK(void, onEvent, (ev_t e))
extern u4_t AESAUX[];
extern u4_t AESKEY[];
@ -226,11 +119,19 @@ void radio_monitor_rssi(ostime_t n, oslmic_radio_rssi_t *pRssi);
//================================================================================
#ifndef RX_RAMPUP
// RX_RAMPUP specifies the extra time we must allow to set up an RX event due
// to platform issues. It's specified in units of ostime_t. It must reflect
// platform jitter and latency, as well as the speed of the LMIC when running
// on this plaform.
#define RX_RAMPUP (us2osticks(2000))
#endif
#ifndef TX_RAMPUP
// TX_RAMPUP specifies the extra time we must allow to set up a TX event) due
// to platform issues. It's specified in units of ostime_t. It must reflect
// platform jitter and latency, as well as the speed of the LMIC when running
// on this plaform.
#define TX_RAMPUP (us2osticks(2000))
#endif
@ -255,7 +156,13 @@ void radio_monitor_rssi(ostime_t n, oslmic_radio_rssi_t *pRssi);
struct osjob_t; // fwd decl.
typedef void (*osjobcb_t) (struct osjob_t*);
//! the function type for osjob_t callbacks
typedef void (osjobcbfn_t)(struct osjob_t*);
//! the pointer-to-function for osjob_t callbacks
typedef osjobcbfn_t *osjobcb_t;
struct osjob_t {
struct osjob_t* next;
ostime_t deadline;
@ -263,6 +170,11 @@ struct osjob_t {
};
TYPEDEF_xref2osjob_t;
//! determine whether a job is timed or immediate. os_setTimedCallback()
// must treat incoming == 0 as being 1 instead.
static inline int os_jobIsTimed(xref2osjob_t job) {
return (job->deadline != 0);
}
#ifndef HAS_os_calls
@ -296,6 +208,10 @@ void os_radio (u1_t mode);
#ifndef os_getBattLevel
u1_t os_getBattLevel (void);
#endif
#ifndef os_queryTimeCriticalJobs
//! Return non-zero if any jobs are scheduled between now and now+time.
bit_t os_queryTimeCriticalJobs(ostime_t time);
#endif
#ifndef os_rlsbf4
//! Read 32-bit quantity from given pointer in little endian byte order.
@ -416,8 +332,18 @@ extern xref2u1_t AESaux;
u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len);
#endif
#ifdef __cplusplus
} // extern "C"
#endif
// ======================================================================
// Simple logging support. Vanishes unless enabled.
#if LMIC_ENABLE_event_logging
extern void LMICOS_logEvent(const char *pMessage);
extern void LMICOS_logEventUint32(const char *pMessage, uint32_t datum);
#else // ! LMIC_ENABLE_event_logging
# define LMICOS_logEvent(m) do { ; } while (0)
# define LMICOS_logEventUint32(m, d) do { ; } while (0)
#endif // ! LMIC_ENABLE_event_logging
LMIC_END_DECLS
#endif // _oslmic_h_

47
src/lmic/oslmic_types.h Normal file
View File

@ -0,0 +1,47 @@
/*
Module: oslmic_types.h
Function:
Basic types from oslmic.h, shared by all layers.
Copyright & License:
See accompanying LICENSE file.
Author:
Terry Moore, MCCI November 2018
(based on oslmic.h from IBM).
*/
#ifndef _oslmic_types_h_
# define _oslmic_types_h_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
//================================================================================
//================================================================================
// Target platform as C library
typedef uint8_t bit_t;
typedef uint8_t u1_t;
typedef int8_t s1_t;
typedef uint16_t u2_t;
typedef int16_t s2_t;
typedef uint32_t u4_t;
typedef int32_t s4_t;
typedef unsigned int uint;
typedef const char* str_t;
// the HAL needs to give us ticks, so it ought to know the right type.
typedef s4_t ostime_t;
#ifdef __cplusplus
}
#endif
/* end of oslmic_types.h */
#endif /* _oslmic_types_h_ */

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014-2016 IBM Corporation.
* Copyright (c) 2016-2018 MCCI Corporation.
* Copyright (c) 2016-2019 MCCI Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -32,101 +32,106 @@
// ----------------------------------------
// Registers Mapping
// // -type- 1272 vs 1276
#define RegFifo 0x00 // common
#define RegOpMode 0x01 // common
#define FSKRegBitrateMsb 0x02
#define FSKRegBitrateLsb 0x03
#define FSKRegFdevMsb 0x04
#define FSKRegFdevLsb 0x05
#define RegFrfMsb 0x06 // common
#define RegFrfMid 0x07 // common
#define RegFrfLsb 0x08 // common
#define RegPaConfig 0x09 // common
#define RegPaRamp 0x0A // common
#define RegOcp 0x0B // common
#define RegLna 0x0C // common
#define FSKRegRxConfig 0x0D
#define RegOpMode 0x01 // common see below
#define FSKRegBitrateMsb 0x02 // -
#define FSKRegBitrateLsb 0x03 // -
#define FSKRegFdevMsb 0x04 // -
#define FSKRegFdevLsb 0x05 // -
#define RegFrfMsb 0x06 // common FSK: 1272: 915; 1276: 434 MHz
#define RegFrfMid 0x07 // common ditto
#define RegFrfLsb 0x08 // common ditto
#define RegPaConfig 0x09 // common see below, many diffs
#define RegPaRamp 0x0A // common see below: bits 6..4 are diff
#define RegOcp 0x0B // common -
#define RegLna 0x0C // common bits 4..0 are diff.
#define FSKRegRxConfig 0x0D // -
#define LORARegFifoAddrPtr 0x0D
#define FSKRegRssiConfig 0x0E
#define FSKRegRssiConfig 0x0E // -
#define LORARegFifoTxBaseAddr 0x0E
#define FSKRegRssiCollision 0x0F
#define FSKRegRssiCollision 0x0F // -
#define LORARegFifoRxBaseAddr 0x0F
#define FSKRegRssiThresh 0x10
#define FSKRegRssiThresh 0x10 // -
#define LORARegFifoRxCurrentAddr 0x10
#define FSKRegRssiValue 0x11
#define FSKRegRssiValue 0x11 // -
#define LORARegIrqFlagsMask 0x11
#define FSKRegRxBw 0x12
#define FSKRegRxBw 0x12 // -
#define LORARegIrqFlags 0x12
#define FSKRegAfcBw 0x13
#define FSKRegAfcBw 0x13 // -
#define LORARegRxNbBytes 0x13
#define FSKRegOokPeak 0x14
#define FSKRegOokPeak 0x14 // -
#define LORARegRxHeaderCntValueMsb 0x14
#define FSKRegOokFix 0x15
#define FSKRegOokFix 0x15 // -
#define LORARegRxHeaderCntValueLsb 0x15
#define FSKRegOokAvg 0x16
#define FSKRegOokAvg 0x16 // -
#define LORARegRxPacketCntValueMsb 0x16
#define LORARegRxpacketCntValueLsb 0x17
#define LORARegModemStat 0x18
#define LORARegPktSnrValue 0x19
#define FSKRegAfcFei 0x1A
#define FSKRegAfcFei 0x1A // -
#define LORARegPktRssiValue 0x1A
#define FSKRegAfcMsb 0x1B
#define FSKRegAfcMsb 0x1B // -
#define LORARegRssiValue 0x1B
#define FSKRegAfcLsb 0x1C
#define FSKRegAfcLsb 0x1C // -
#define LORARegHopChannel 0x1C
#define FSKRegFeiMsb 0x1D
#define FSKRegFeiMsb 0x1D // -
#define LORARegModemConfig1 0x1D
#define FSKRegFeiLsb 0x1E
#define FSKRegFeiLsb 0x1E // -
#define LORARegModemConfig2 0x1E
#define FSKRegPreambleDetect 0x1F
#define FSKRegPreambleDetect 0x1F // -
#define LORARegSymbTimeoutLsb 0x1F
#define FSKRegRxTimeout1 0x20
#define FSKRegRxTimeout1 0x20 // -
#define LORARegPreambleMsb 0x20
#define FSKRegRxTimeout2 0x21
#define FSKRegRxTimeout2 0x21 // -
#define LORARegPreambleLsb 0x21
#define FSKRegRxTimeout3 0x22
#define FSKRegRxTimeout3 0x22 // -
#define LORARegPayloadLength 0x22
#define FSKRegRxDelay 0x23
#define FSKRegRxDelay 0x23 // -
#define LORARegPayloadMaxLength 0x23
#define FSKRegOsc 0x24
#define FSKRegOsc 0x24 // -
#define LORARegHopPeriod 0x24
#define FSKRegPreambleMsb 0x25
#define FSKRegPreambleMsb 0x25 // -
#define LORARegFifoRxByteAddr 0x25
#define FSKRegPreambleLsb 0x26 // -
#define LORARegModemConfig3 0x26
#define FSKRegPreambleLsb 0x26
#define FSKRegSyncConfig 0x27
#define FSKRegSyncConfig 0x27 // -
#define LORARegFeiMsb 0x28
#define FSKRegSyncValue1 0x28
#define FSKRegSyncValue1 0x28 // -
#define LORAFeiMib 0x29
#define FSKRegSyncValue2 0x29
#define FSKRegSyncValue2 0x29 // -
#define LORARegFeiLsb 0x2A
#define FSKRegSyncValue3 0x2A
#define FSKRegSyncValue4 0x2B
#define FSKRegSyncValue3 0x2A // -
#define FSKRegSyncValue4 0x2B // -
#define LORARegRssiWideband 0x2C
#define FSKRegSyncValue5 0x2C
#define FSKRegSyncValue6 0x2D
#define FSKRegSyncValue7 0x2E
#define FSKRegSyncValue8 0x2F
#define FSKRegPacketConfig1 0x30
#define FSKRegPacketConfig2 0x31
#define FSKRegSyncValue5 0x2C // -
#define FSKRegSyncValue6 0x2D // -
#define FSKRegSyncValue7 0x2E // -
#define FSKRegSyncValue8 0x2F // -
#define LORARegIffReq1 0x2F
#define FSKRegPacketConfig1 0x30 // -
#define LORARegIffReq2 0x30
#define FSKRegPacketConfig2 0x31 // -
#define LORARegDetectOptimize 0x31
#define FSKRegPayloadLength 0x32
#define FSKRegNodeAdrs 0x33
#define FSKRegPayloadLength 0x32 // -
#define FSKRegNodeAdrs 0x33 // -
#define LORARegInvertIQ 0x33
#define FSKRegBroadcastAdrs 0x34
#define FSKRegFifoThresh 0x35
#define FSKRegSeqConfig1 0x36
#define FSKRegSeqConfig2 0x37
#define FSKRegBroadcastAdrs 0x34 // -
#define FSKRegFifoThresh 0x35 // -
#define FSKRegSeqConfig1 0x36 // -
#define LORARegHighBwOptimize1 0x36
#define FSKRegSeqConfig2 0x37 // -
#define LORARegDetectionThreshold 0x37
#define FSKRegTimerResol 0x38
#define FSKRegTimer1Coef 0x39
#define FSKRegTimerResol 0x38 // -
#define FSKRegTimer1Coef 0x39 // -
#define LORARegSyncWord 0x39
#define FSKRegTimer2Coef 0x3A
#define FSKRegImageCal 0x3B
#define FSKRegTemp 0x3C
#define FSKRegLowBat 0x3D
#define FSKRegIrqFlags1 0x3E
#define FSKRegIrqFlags2 0x3F
#define FSKRegTimer2Coef 0x3A // -
#define LORARegHighBwOptimize2 0x3A
#define FSKRegImageCal 0x3B // -
#define FSKRegTemp 0x3C // -
#define FSKRegLowBat 0x3D // -
#define FSKRegIrqFlags1 0x3E // -
#define FSKRegIrqFlags2 0x3F // -
#define RegDioMapping1 0x40 // common
#define RegDioMapping2 0x41 // common
#define RegVersion 0x42 // common
@ -135,13 +140,22 @@
// #define RegAgcThresh2 0x45 // common
// #define RegAgcThresh3 0x46 // common
// #define RegPllHop 0x4B // common
#define RegPaDac 0x4D // common
// #define RegTcxo 0x58 // common
// #define RegPll 0x5C // common
// #define RegPllLowPn 0x5E // common
// #define RegFormerTemp 0x6C // common
// #define RegBitRateFrac 0x70 // common
#if defined(CFG_sx1276_radio)
#define RegTcxo 0x4B // common different addresses, same bits
#define RegPaDac 0x4D // common differnet addresses, same bits
#elif defined(CFG_sx1272_radio)
#define RegTcxo 0x58 // common
#define RegPaDac 0x5A // common
#endif
#define RegTcxo_TcxoInputOn (1u << 4)
// ----------------------------------------
// spread factors and mode for RegModemConfig2
#define SX1272_MC2_FSK 0x00
@ -179,6 +193,34 @@
#define SX1276_MC1_IMPLICIT_HEADER_MODE_ON 0x01
#ifdef CFG_sx1276_radio
# define SX127X_MC1_IMPLICIT_HEADER_MODE_ON SX1276_MC1_IMPLICIT_HEADER_MODE_ON
#else
# define SX127X_MC1_IMPLICIT_HEADER_MODE_ON SX1272_MC1_IMPLICIT_HEADER_MODE_ON
#endif
// transmit power configuration for RegPaConfig
#define SX1276_PAC_PA_SELECT_PA_BOOST 0x80
#define SX1276_PAC_PA_SELECT_RFIO_PIN 0x00
#define SX1276_PAC_MAX_POWER_MASK 0x70
// the bits to change for max power.
#define SX127X_PADAC_POWER_MASK 0x07
#define SX127X_PADAC_POWER_NORMAL 0x04
#define SX127X_PADAC_POWER_20dBm 0x07
// convert milliamperes to equivalent value for
// RegOcp; delivers conservative value.
#define SX127X_OCP_MAtoBITS(mA) \
((mA) < 45 ? 0 : \
(mA) <= 120 ? ((mA) - 45) / 5 : \
(mA) < 130 ? 0xF : \
(mA) < 240 ? ((mA) - 130) / 10 + 0x10 : \
27)
// bit in RegOcp that enables overcurrent protect.
#define SX127X_OCP_ENA 0x20
// sx1276 RegModemConfig2
#define SX1276_MC2_RX_PAYLOAD_CRCON 0x04
@ -199,10 +241,13 @@
//-----------------------------------------
// Parameters for RSSI monitoring
#define SX127X_FREQ_LF_MAX 525000000 // per datasheet 6.3
// per datasheet 5.5.3:
#define SX127X_RSSI_ADJUST_LF -164 // add to rssi value to get dB (LF)
#define SX127X_RSSI_ADJUST_HF -157 // add to rssi value to get dB (HF)
// per datasheet 5.5.3 and 5.5.5:
#define SX1272_RSSI_ADJUST -139 // add to rssi value to get dB (LF)
// per datasheet 5.5.3 and 5.5.5:
#define SX1276_RSSI_ADJUST_LF -164 // add to rssi value to get dB (LF)
#define SX1276_RSSI_ADJUST_HF -157 // add to rssi value to get dB (HF)
// per datasheet 2.5.2 (but note that we ought to ask Semtech to confirm, because
// datasheet is unclear).
@ -221,6 +266,40 @@
#define OPMODE_RX_SINGLE 0x06
#define OPMODE_CAD 0x07
// ----------------------------------------
// FSK opmode bits
// bits 6:5 are the same for 1272 and 1276
#define OPMODE_FSK_SX127x_ModulationType_FSK (0u << 5)
#define OPMODE_FSK_SX127x_ModulationType_OOK (1u << 5)
#define OPMODE_FSK_SX127x_ModulationType_MASK (3u << 5)
// bits 4:3 are different for 1272
#define OPMODE_FSK_SX1272_ModulationShaping_FSK_None (0u << 3)
#define OPMODE_FSK_SX1272_ModulationShaping_FSK_BT1_0 (1u << 3)
#define OPMODE_FSK_SX1272_ModulationShaping_FSK_BT0_5 (2u << 3)
#define OPMODE_FSK_SX1272_ModulationShaping_FSK_BT0_3 (3u << 3)
#define OPMODE_FSK_SX1272_ModulationShaping_OOK_None (0u << 3)
#define OPMODE_FSK_SX1272_ModulationShaping_OOK_BR (1u << 3)
#define OPMODE_FSK_SX1272_ModulationShaping_OOK_2BR (2u << 3)
#define OPMODE_FSK_SX1272_ModulationShaping_MASK (3u << 3)
// SX1276
#define OPMODE_FSK_SX1276_LowFrequencyModeOn (1u << 3)
// define the opmode bits apporpriate for the 127x in use.
#if defined(CFG_sx1272_radio)
# define OPMODE_FSK_SX127X_SETUP (OPMODE_FSK_SX127x_ModulationType_FSK | \
OPMODE_FSK_SX1272_ModulationShaping_FSK_BT0_5)
#elif defined(CFG_sx1276_radio)
# define OPMODE_FSK_SX127X_SETUP (OPMODE_FSK_SX127x_ModulationType_FSK)
#endif
// ----------------------------------------
// LoRa opmode bits
#define OPMODE_LORA_SX127x_AccessSharedReg (1u << 6)
#define OPMODE_LORA_SX1276_LowFrequencyModeOn (1u << 3)
// ----------------------------------------
// Bits masking the corresponding IRQs from the radio
#define IRQ_LORA_RXTOUT_MASK 0x80
@ -255,7 +334,7 @@
#define MAP_DIO0_LORA_TXDONE 0x40 // 01------
#define MAP_DIO1_LORA_RXTOUT 0x00 // --00----
#define MAP_DIO1_LORA_NOP 0x30 // --11----
#define MAP_DIO2_LORA_NOP 0xC0 // ----11--
#define MAP_DIO2_LORA_NOP 0x0C // ----11--
#define MAP_DIO0_FSK_READY 0x00 // 00------ (packet sent / payload ready)
#define MAP_DIO1_FSK_NOP 0x30 // --11----
@ -274,90 +353,78 @@
#define RF_IMAGECAL_IMAGECAL_RUNNING 0x20
#define RF_IMAGECAL_IMAGECAL_DONE 0x00 // Default
// RADIO STATE
// (initialized by radio_init(), used by radio_rand1())
static u1_t randbuf[16];
// LNA gain constant. Bits 4..0 have different meaning for 1272 and 1276, but
// by chance, the bit patterns we use are the same.
#ifdef CFG_sx1276_radio
#define LNA_RX_GAIN (0x20|0x1)
#define LNA_RX_GAIN (0x20|0x3)
#elif CFG_sx1272_radio
#define LNA_RX_GAIN (0x20|0x03)
#else
#error Missing CFG_sx1272_radio/CFG_sx1276_radio
#endif
// RADIO STATE
// (initialized by radio_init(), used by radio_rand1())
static u1_t randbuf[16];
static void writeReg (u1_t addr, u1_t data ) {
// ttn-esp32 change: higher level SPI interface
hal_spi_write(addr | 0x80, &data, 1);
/*
hal_pin_nss(0);
hal_spi(addr | 0x80);
hal_spi(data);
hal_pin_nss(1);
*/
}
static u1_t readReg (u1_t addr) {
// ttn-esp32 change: higher level SPI interface
u1_t buf[1];
hal_spi_read(addr & 0x7f, buf, 1);
return buf[0];
/*
hal_pin_nss(0);
hal_spi(addr & 0x7F);
u1_t val = hal_spi(0x00);
hal_pin_nss(1);
return val;
*/
}
static void writeBuf (u1_t addr, xref2u1_t buf, u1_t len) {
// ttn-esp32 change: higher level SPI interface
hal_spi_write(addr | 0x80, buf, len);
/*
hal_pin_nss(0);
hal_spi(addr | 0x80);
for (u1_t i=0; i<len; i++) {
hal_spi(buf[i]);
}
hal_pin_nss(1);
*/
}
static void readBuf (u1_t addr, xref2u1_t buf, u1_t len) {
// ttn-esp32 change: higher level SPI interface
hal_spi_read(addr & 0x7f, buf, len);
/*
hal_pin_nss(0);
hal_spi(addr & 0x7F);
for (u1_t i=0; i<len; i++) {
buf[i] = hal_spi(0x00);
}
hal_pin_nss(1);
*/
}
static void requestModuleActive(bit_t state) {
ostime_t const ticks = hal_setModuleActive(state);
if (ticks)
hal_waitUntil(os_getTime() + ticks);;
}
static void writeOpmode(u1_t mode) {
u1_t const maskedMode = mode & OPMODE_MASK;
if (maskedMode != OPMODE_SLEEP)
requestModuleActive(1);
writeReg(RegOpMode, mode);
if (maskedMode == OPMODE_SLEEP)
requestModuleActive(0);
}
static void opmode (u1_t mode) {
writeReg(RegOpMode, (readReg(RegOpMode) & ~OPMODE_MASK) | mode);
writeOpmode((readReg(RegOpMode) & ~OPMODE_MASK) | mode);
}
static void opmodeLora() {
u1_t u = OPMODE_LORA;
#ifdef CFG_sx1276_radio
u |= 0x8; // TBD: sx1276 high freq
if (LMIC.freq <= SX127X_FREQ_LF_MAX) {
u |= OPMODE_FSK_SX1276_LowFrequencyModeOn;
}
#endif
writeReg(RegOpMode, u);
writeOpmode(u);
}
static void opmodeFSK() {
u1_t u = 0;
u1_t u = OPMODE_FSK_SX127X_SETUP;
#ifdef CFG_sx1276_radio
u |= 0x8; // TBD: sx1276 high freq
if (LMIC.freq <= SX127X_FREQ_LF_MAX) {
u |= OPMODE_FSK_SX1276_LowFrequencyModeOn;
}
#endif
writeReg(RegOpMode, u);
writeOpmode(u);
}
// configure LoRa modem (cfg1, cfg2)
@ -367,7 +434,9 @@ static void configLoraModem () {
#ifdef CFG_sx1276_radio
u1_t mc1 = 0, mc2 = 0, mc3 = 0;
switch (getBw(LMIC.rps)) {
bw_t const bw = getBw(LMIC.rps);
switch (bw) {
case BW125: mc1 |= SX1276_MC1_BW_125; break;
case BW250: mc1 |= SX1276_MC1_BW_250; break;
case BW500: mc1 |= SX1276_MC1_BW_500; break;
@ -397,10 +466,34 @@ static void configLoraModem () {
writeReg(LORARegModemConfig2, mc2);
mc3 = SX1276_MC3_AGCAUTO;
if ((sf == SF11 || sf == SF12) && getBw(LMIC.rps) == BW125) {
if ( ((sf == SF11 || sf == SF12) && bw == BW125) ||
((sf == SF12) && bw == BW250) ) {
mc3 |= SX1276_MC3_LOW_DATA_RATE_OPTIMIZE;
}
writeReg(LORARegModemConfig3, mc3);
// Errata 2.1: Sensitivity optimization with 500 kHz bandwidth
u1_t rHighBwOptimize1;
u1_t rHighBwOptimize2;
rHighBwOptimize1 = 0x03;
rHighBwOptimize2 = 0;
if (bw == BW500) {
if (LMIC.freq > SX127X_FREQ_LF_MAX) {
rHighBwOptimize1 = 0x02;
rHighBwOptimize2 = 0x64;
} else {
rHighBwOptimize1 = 0x02;
rHighBwOptimize2 = 0x7F;
}
}
writeReg(LORARegHighBwOptimize1, rHighBwOptimize1);
if (rHighBwOptimize2 != 0)
writeReg(LORARegHighBwOptimize2, rHighBwOptimize2);
#elif CFG_sx1272_radio
u1_t mc1 = (getBw(LMIC.rps)<<6);
@ -448,69 +541,220 @@ static void configChannel () {
writeReg(RegFrfLsb, (u1_t)(frf>> 0));
}
// On the SX1276, we have several possible configs.
// 1) using RFO, MaxPower==0: in that case power is -4 to 11 dBm
// 2) using RFO, MaxPower==7: in that case, power is 0 to 14 dBm
// (can't select 15 dBm).
// note we can use -4..11 w/o Max and then 12..14 w/Max, and
// we really don't need to ask anybody.
// 3) using PA_BOOST, PaDac = 4: in that case power range is 2 to 17 dBm;
// use this for 15..17 if authorized.
// 4) using PA_BOOST, PaDac = 7, OutputPower=0xF: in that case, power is 20 dBm
// (and perhaps 0xE is 19, 0xD is 18 dBm, but datasheet isn't clear.)
// and duty cycle must be <= 1%.
//
// In addition, there are some boards for which PA_BOOST can only be used if the
// channel frequency is greater than SX127X_FREQ_LF_MAX.
//
// The SX1272 is similar but has no MaxPower bit:
// 1) using RFO: power is -1 to 13 dBm (datasheet implies max OutputPower value is 14 for 13 dBm)
// 2) using PA_BOOST, PaDac = 0x84: power is 2 to 17 dBm;
// use this for 14..17 if authorized
// 3) using PA_BOOST, PaDac = 0x87, OutputPower = 0xF: power is 20dBm
// and duty cycle must be <= 1%
//
// The general policy is to use the lowest power variant that will get us where we
// need to be.
//
static void configPower () {
// our input paramter -- might be different than LMIC.txpow!
s1_t const req_pw = (s1_t)LMIC.radio_txpow;
// the effective power
s1_t eff_pw;
// the policy; we're going to compute this.
u1_t policy;
// what we'll write to RegPaConfig
u1_t rPaConfig;
// what we'll write to RegPaDac
u1_t rPaDac;
// what we'll write to RegOcp
u1_t rOcp;
#ifdef CFG_sx1276_radio
// PA_BOOST output is assumed but not 20 dBm.
s1_t pw = (s1_t)LMIC.txpow;
if(pw > 17) {
pw = 17;
} else if(pw < 2) {
pw = 2;
if (req_pw >= 20) {
policy = LMICHAL_radio_tx_power_policy_20dBm;
eff_pw = 20;
} else if (req_pw >= 14) {
policy = LMICHAL_radio_tx_power_policy_paboost;
if (req_pw > 17) {
eff_pw = 17;
} else {
eff_pw = req_pw;
}
} else {
policy = LMICHAL_radio_tx_power_policy_rfo;
if (req_pw < -4) {
eff_pw = -4;
} else {
eff_pw = req_pw;
}
}
policy = hal_getTxPowerPolicy(policy, eff_pw, LMIC.freq);
switch (policy) {
default:
case LMICHAL_radio_tx_power_policy_rfo:
rPaDac = SX127X_PADAC_POWER_NORMAL;
rOcp = SX127X_OCP_MAtoBITS(80);
if (eff_pw > 14)
eff_pw = 14;
if (eff_pw > 11) {
// some Semtech code uses this down to eff_pw == 0.
rPaConfig = eff_pw | SX1276_PAC_MAX_POWER_MASK;
} else {
if (eff_pw < -4)
eff_pw = -4;
rPaConfig = eff_pw + 4;
}
break;
// some radios (HopeRF RFM95W) don't support RFO well,
// so the policy might *raise* rfo to paboost. That means
// we have to re-check eff_pw, which might be too small.
// (And, of course, it might also be too large.)
case LMICHAL_radio_tx_power_policy_paboost:
// It seems that SX127x doesn't like eff_pw 10 when in FSK mode.
if (getSf(LMIC.rps) == FSK && eff_pw < 11) {
eff_pw = 11;
}
rPaDac = SX127X_PADAC_POWER_NORMAL;
rOcp = SX127X_OCP_MAtoBITS(100);
if (eff_pw > 17)
eff_pw = 17;
else if (eff_pw < 2)
eff_pw = 2;
rPaConfig = (eff_pw - 2) | SX1276_PAC_PA_SELECT_PA_BOOST;
break;
case LMICHAL_radio_tx_power_policy_20dBm:
rPaDac = SX127X_PADAC_POWER_20dBm;
rOcp = SX127X_OCP_MAtoBITS(130);
rPaConfig = 0xF | SX1276_PAC_PA_SELECT_PA_BOOST;
break;
}
// 0x80 forces use of PA_BOOST; but we don't
// turn on 20 dBm mode. So powers are:
// 0000 => 2dBm, 0001 => 3dBm, ... 1111 => 17dBm
// But we also enforce that the high-power mode
// is off by writing RegPaDac.
writeReg(RegPaConfig, (u1_t)(0x80|(pw - 2)));
writeReg(RegPaDac, readReg(RegPaDac)|0x4);
#elif CFG_sx1272_radio
// set PA config (2-17 dBm using PA_BOOST)
s1_t pw = (s1_t)LMIC.txpow;
if(pw > 17) {
pw = 17;
} else if(pw < 2) {
pw = 2;
if (req_pw >= 20) {
policy = LMICHAL_radio_tx_power_policy_20dBm;
eff_pw = 20;
} else if (eff_pw >= 14) {
policy = LMICHAL_radio_tx_power_policy_paboost;
if (eff_pw > 17) {
eff_pw = 17;
} else {
eff_pw = req_pw;
}
} else {
policy = LMICHAL_radio_tx_power_policy_rfo;
if (req_pw < -1) {
eff_pw = -1;
} else {
eff_pw = req_pw;
}
}
policy = hal_getTxPowerPolicy(policy, eff_pw, LMIC.freq);
switch (policy) {
default:
case LMICHAL_radio_tx_power_policy_rfo:
rPaDac = SX127X_PADAC_POWER_NORMAL;
rOcp = SX127X_OCP_MAtoBITS(50);
if (eff_pw > 13)
eff_pw = 13;
rPaConfig = eff_pw + 1;
break;
case LMICHAL_radio_tx_power_policy_paboost:
rPaDac = SX127X_PADAC_POWER_NORMAL;
rOcp = SX127X_OCP_MAtoBITS(100);
if (eff_pw > 17)
eff_pw = 17;
rPaConfig = (eff_pw - 2) | SX1272_PAC_PA_SELECT_PA_BOOST;
break;
case LMICHAL_radio_tx_power_policy_20dBm:
rPaDac = SX127X_PADAC_POWER_20dBm;
rOcp = SX127X_OCP_MAtoBITS(130);
rPaConfig = 0xF | SX1276_PAC_PA_SELECT_PA_BOOST;
break;
}
writeReg(RegPaConfig, (u1_t)(0x80|(pw-2)));
#else
#error Missing CFG_sx1272_radio/CFG_sx1276_radio
#endif /* CFG_sx1272_radio */
writeReg(RegPaConfig, rPaConfig);
writeReg(RegPaDac, (readReg(RegPaDac) & ~SX127X_PADAC_POWER_MASK) | rPaDac);
writeReg(RegOcp, rOcp | SX127X_OCP_ENA);
}
static void txfsk () {
// select FSK modem (from sleep mode)
writeReg(RegOpMode, 0x10); // FSK, BT=0.5
ASSERT(readReg(RegOpMode) == 0x10);
// enter standby mode (required for FIFO loading))
opmode(OPMODE_STANDBY);
static void setupFskRxTx(bit_t fDisableAutoClear) {
// set bitrate
writeReg(FSKRegBitrateMsb, 0x02); // 50kbps
writeReg(FSKRegBitrateLsb, 0x80);
// set frequency deviation
writeReg(FSKRegFdevMsb, 0x01); // +/- 25kHz
writeReg(FSKRegFdevLsb, 0x99);
// frame and packet handler settings
writeReg(FSKRegPreambleMsb, 0x00);
writeReg(FSKRegPreambleLsb, 0x05);
writeReg(FSKRegSyncConfig, 0x12);
writeReg(FSKRegPacketConfig1, 0xD0);
writeReg(FSKRegPacketConfig2, 0x40);
// set sync config
writeReg(FSKRegSyncConfig, 0x12); // no auto restart, preamble 0xAA, enable, fill FIFO, 3 bytes sync
// set packet config
writeReg(FSKRegPacketConfig1, fDisableAutoClear ? 0xD8 : 0xD0); // var-length, whitening, crc, no auto-clear, no adr filter
writeReg(FSKRegPacketConfig2, 0x40); // packet mode
// set sync value
writeReg(FSKRegSyncValue1, 0xC1);
writeReg(FSKRegSyncValue2, 0x94);
writeReg(FSKRegSyncValue3, 0xC1);
}
static void txfsk () {
// select FSK modem (from sleep mode)
opmodeFSK();
// enter standby mode (required for FIFO loading))
opmode(OPMODE_STANDBY);
// set bitrate etc
setupFskRxTx(/* don't autoclear CRC */ 0);
// frame and packet handler settings
writeReg(FSKRegPreambleMsb, 0x00);
writeReg(FSKRegPreambleLsb, 0x05);
// configure frequency
configChannel();
// configure output power
configPower();
#ifdef CFG_sx1276_radio
// select Gausian filter BT=0.5, default ramp.
writeReg(RegPaRamp, 0x29);
#endif
// set the IRQ mapping DIO0=PacketSent DIO1=NOP DIO2=NOP
writeReg(RegDioMapping1, MAP_DIO0_FSK_READY|MAP_DIO1_FSK_NOP|MAP_DIO2_FSK_TXNOP);
// initialize the payload size and address pointers
// TODO(tmm@mcci.com): datasheet says this is not used in variable packet length mode
writeReg(FSKRegPayloadLength, LMIC.dataLen+1); // (insert length byte into payload))
// download length byte and buffer to the radio FIFO
@ -537,7 +781,11 @@ static void txlora () {
// configure frequency
configChannel();
// configure output power
#ifdef CFG_sx1272_radio
writeReg(RegPaRamp, (readReg(RegPaRamp) & 0xF0) | 0x08); // set PA ramp-up time 50 uSec
#elif defined(CFG_sx1276_radio)
writeReg(RegPaRamp, 0x08); // set PA ramp-up time 50 uSec, clear FSK bits
#endif
configPower();
// set sync word
writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE);
@ -595,7 +843,7 @@ static void starttx () {
oslmic_radio_rssi_t rssi;
radio_monitor_rssi(LMIC.lbt_ticks, &rssi);
#if LMIC_X_DEBUG_LEVEL > 0
LMIC_X_DEBUG_PRINTF("LBT rssi max:min=%d:%d %d times in %d\n", rssi.max_rssi, rssi.min_rssi, rssi.n_rssi, LMIC.lbt_ticks);
LMIC_X_DEBUG_PRINTF("LBT rssi max:min=%d:%d %d times in %d\n", rssi.max_rssi, rssi.min_rssi, rssi.n_rssi, LMIC.lbt_ticks);
#endif
if (rssi.max_rssi >= LMIC.lbt_dbmax) {
@ -642,8 +890,8 @@ static void rxlora (u1_t rxmode) {
// set LNA gain
writeReg(RegLna, LNA_RX_GAIN);
// set max payload size
writeReg(LORARegPayloadMaxLength, 64);
#if !defined(DISABLE_INVERT_IQ_ON_RX)
writeReg(LORARegPayloadMaxLength, MAX_LEN_FRAME);
#if !defined(DISABLE_INVERT_IQ_ON_RX) /* DEPRECATED(tmm@mcci.com); #250. remove test, always include code in V3 */
// use inverted I/Q signal (prevent mote-to-mote communication)
// XXX: use flag to switch on/off inversion
@ -653,6 +901,18 @@ static void rxlora (u1_t rxmode) {
writeReg(LORARegInvertIQ, readReg(LORARegInvertIQ)|(1<<6));
}
#endif
// Errata 2.3 - receiver spurious reception of a LoRa signal
bw_t const bw = getBw(LMIC.rps);
u1_t const rDetectOptimize = (readReg(LORARegDetectOptimize) & 0x78) | 0x03;
if (bw < BW500) {
writeReg(LORARegDetectOptimize, rDetectOptimize);
writeReg(LORARegIffReq1, 0x40);
writeReg(LORARegIffReq2, 0x40);
} else {
writeReg(LORARegDetectOptimize, rDetectOptimize | 0x80);
}
// set symbol timeout (for single rx)
writeReg(LORARegSymbTimeoutLsb, LMIC.rxsyms);
// set sync word
@ -668,13 +928,16 @@ static void rxlora (u1_t rxmode) {
// enable antenna switch for RX
hal_pin_rxtx(0);
writeReg(LORARegFifoAddrPtr, 0);
writeReg(LORARegFifoRxBaseAddr, 0);
// now instruct the radio to receive
if (rxmode == RXMODE_SINGLE) { // single rx
hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time
opmode(OPMODE_RX_SINGLE);
#if LMIC_DEBUG_LEVEL > 0
ostime_t now = os_getTime();
LMIC_DEBUG_PRINTF("start single rx: now-rxtime: %"LMIC_PRId_ostime_t"\n", now - LMIC.rxtime);
ostime_t now = os_getTime();
LMIC_DEBUG_PRINTF("start single rx: now-rxtime: %"LMIC_PRId_ostime_t"\n", now - LMIC.rxtime);
#endif
} else { // continous rx (scan or rssi)
opmode(OPMODE_RX);
@ -711,33 +974,19 @@ static void rxfsk (u1_t rxmode) {
// configure frequency
configChannel();
// set LNA gain
//writeReg(RegLna, 0x20|0x03); // max gain, boost enable
writeReg(RegLna, LNA_RX_GAIN);
writeReg(RegLna, LNA_RX_GAIN); // max gain, boost enable.
// configure receiver
writeReg(FSKRegRxConfig, 0x1E); // AFC auto, AGC, trigger on preamble?!?
// set receiver bandwidth
writeReg(FSKRegRxBw, 0x0B); // 50kHz SSb
writeReg(FSKRegRxBw, 0x0B); // 50kHz SSb
// set AFC bandwidth
writeReg(FSKRegAfcBw, 0x12); // 83.3kHz SSB
writeReg(FSKRegAfcBw, 0x12); // 83.3kHz SSB
// set preamble detection
writeReg(FSKRegPreambleDetect, 0xAA); // enable, 2 bytes, 10 chip errors
// set sync config
writeReg(FSKRegSyncConfig, 0x12); // no auto restart, preamble 0xAA, enable, fill FIFO, 3 bytes sync
// set packet config
writeReg(FSKRegPacketConfig1, 0xD8); // var-length, whitening, crc, no auto-clear, no adr filter
writeReg(FSKRegPacketConfig2, 0x40); // packet mode
// set sync value
writeReg(FSKRegSyncValue1, 0xC1);
writeReg(FSKRegSyncValue2, 0x94);
writeReg(FSKRegSyncValue3, 0xC1);
// set preamble timeout
writeReg(FSKRegRxTimeout2, 0xFF);//(LMIC.rxsyms+1)/2);
// set bitrate
writeReg(FSKRegBitrateMsb, 0x02); // 50kbps
writeReg(FSKRegBitrateLsb, 0x80);
// set frequency deviation
writeReg(FSKRegFdevMsb, 0x01); // +/- 25kHz
writeReg(FSKRegFdevLsb, 0x99);
// set bitrate, autoclear CRC
setupFskRxTx(1);
// configure DIO mapping DIO0=PayloadReady DIO1=NOP DIO2=TimeOut
writeReg(RegDioMapping1, MAP_DIO0_FSK_READY|MAP_DIO1_FSK_NOP|MAP_DIO2_FSK_TIMEOUT);
@ -765,6 +1014,8 @@ static void startrx (u1_t rxmode) {
int radio_init () {
hal_disableIRQs();
requestModuleActive(1);
// manually reset radio
#ifdef CFG_sx1276_radio
hal_pin_rst(0); // drive RST pin low
@ -788,6 +1039,10 @@ int radio_init () {
#else
#error Missing CFG_sx1272_radio/CFG_sx1276_radio
#endif
// set the tcxo input, if needed
if (hal_queryUsingTcxo())
writeReg(RegTcxo, readReg(RegTcxo) | RegTcxo_TcxoInputOn);
// seed 15-byte randomness via noise rssi
rxlora(RXMODE_RSSI);
while( (readReg(RegOpMode) & OPMODE_MASK) != OPMODE_RX ); // continuous rx
@ -866,11 +1121,15 @@ void radio_monitor_rssi(ostime_t nTicks, oslmic_radio_rssi_t *pRssi) {
// while we're waiting for the PLLs to spin up, determine which
// band we're in and choose the base RSSI.
#if defined(CFG_sx1276_radio)
if (LMIC.freq > SX127X_FREQ_LF_MAX) {
rssiAdjust = SX127X_RSSI_ADJUST_HF;
rssiAdjust = SX1276_RSSI_ADJUST_HF;
} else {
rssiAdjust = SX127X_RSSI_ADJUST_LF;
rssiAdjust = SX1276_RSSI_ADJUST_LF;
}
#elif defined(CFG_sx1272_radio)
rssiAdjust = SX1272_RSSI_ADJUST;
#endif
rssiAdjust += hal_getRssiCal();
// zero the results
@ -905,7 +1164,7 @@ void radio_monitor_rssi(ostime_t nTicks, oslmic_radio_rssi_t *pRssi) {
rssiMin = rssiNow;
rssiSum += rssiNow;
++rssiN;
// TODO(tmm@mcci.com) move this to os_getTime().
// TODO(tmm@mcci.com) move this to os_getTime().
hal_enableIRQs();
now = os_getTime();
hal_disableIRQs();
@ -960,6 +1219,8 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
#endif
if( (readReg(RegOpMode) & OPMODE_LORA) != 0) { // LORA modem
u1_t flags = readReg(LORARegIrqFlags);
LMIC.saveIrqFlags = flags;
LMICOS_logEventUint32("radio_irq_handler_v2: LoRa", flags);
LMIC_X_DEBUG_PRINTF("IRQ=%02x\n", flags);
if( flags & IRQ_LORA_TXDONE_MASK ) {
// save exact tx time
@ -971,7 +1232,7 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
}
LMIC.rxtime = now;
// read the PDU and inform the MAC that we received something
LMIC.dataLen = (readReg(LORARegModemConfig1) & SX1272_MC1_IMPLICIT_HEADER_MODE_ON) ?
LMIC.dataLen = (readReg(LORARegModemConfig1) & SX127X_MC1_IMPLICIT_HEADER_MODE_ON) ?
readReg(LORARegPayloadLength) : readReg(LORARegRxNbBytes);
// set FIFO read address pointer
writeReg(LORARegFifoAddrPtr, readReg(LORARegFifoRxCurrentAddr));
@ -986,8 +1247,8 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
// indicate timeout
LMIC.dataLen = 0;
#if LMIC_DEBUG_LEVEL > 0
ostime_t now2 = os_getTime();
LMIC_DEBUG_PRINTF("rxtimeout: entry: %"LMIC_PRId_ostime_t" rxtime: %"LMIC_PRId_ostime_t" entry-rxtime: %"LMIC_PRId_ostime_t" now-entry: %"LMIC_PRId_ostime_t" rxtime-txend: %"LMIC_PRId_ostime_t"\n", entry,
ostime_t now2 = os_getTime();
LMIC_DEBUG_PRINTF("rxtimeout: entry: %"LMIC_PRId_ostime_t" rxtime: %"LMIC_PRId_ostime_t" entry-rxtime: %"LMIC_PRId_ostime_t" now-entry: %"LMIC_PRId_ostime_t" rxtime-txend: %"LMIC_PRId_ostime_t"\n", entry,
LMIC.rxtime, entry - LMIC.rxtime, now2 - entry, LMIC.rxtime-LMIC.txend);
#endif
}
@ -998,6 +1259,9 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
} else { // FSK modem
u1_t flags1 = readReg(FSKRegIrqFlags1);
u1_t flags2 = readReg(FSKRegIrqFlags2);
LMICOS_logEventUint32("radio_irq_handler_v2: FSK", (flags2 << UINT32_C(8)) | flags1);
if( flags2 & IRQ_FSK2_PACKETSENT_MASK ) {
// save exact tx time
LMIC.txend = now;
@ -1015,10 +1279,15 @@ void radio_irq_handler_v2 (u1_t dio, ostime_t now) {
// indicate timeout
LMIC.dataLen = 0;
} else {
ASSERT(0);
// ASSERT(0);
// we're not sure why we're here... treat as timeout.
LMIC.dataLen = 0;
}
// in FSK, we need to put the radio in standby first.
opmode(OPMODE_STANDBY);
}
// go from stanby to sleep
// go from standby to sleep
opmode(OPMODE_SLEEP);
// run os job (use preset func ptr)
os_setCallback(&LMIC.osjob, LMIC.osjob.func);

View File

@ -1,36 +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 _provision_task_h_
#define _provision_task_h_
#include "lmic/oslmic.h"
#ifdef __cplusplus
extern "C" {
#endif
void provisioning_start_task();
bool provisioning_have_keys();
bool provisioning_decode_keys(const char *dev_eui, const char *app_eui, const char *app_key);
bool provisioning_from_mac(const char *app_eui, const char *app_key);
bool provisioning_save_keys();
bool provisioning_restore_keys(bool silent);
#ifdef __cplusplus
}
#endif
#endif