mirror of https://github.com/arendst/Tasmota.git
Add support for LoRaWanBridge
This commit is contained in:
parent
6337c59fab
commit
f10218a257
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -5,16 +5,20 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [13.4.0.3]
|
## [13.4.0.3]
|
||||||
### Added
|
### Added
|
||||||
- Zigbee added for attributes of type `uint48` used by energy monitoring
|
- Zigbee support for attributes of type `uint48` used by energy monitoring (#20992)
|
||||||
|
- Support for LoRaWanBridge
|
||||||
|
|
||||||
### Breaking Changed
|
### Breaking Changed
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- LVGL library from v9.0.0 to v9.1.0
|
- ESP32 LVGL library from v9.0.0 to v9.1.0 (#21008)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Berry fix walrus bug when assigning to self
|
- BTHome, prep BLE5 (#20989)
|
||||||
|
- Scripter google char memory leak (#20995)
|
||||||
|
- HASPmota demo and robotocondensed fonts (#21014)
|
||||||
|
- Berry walrus bug when assigning to self (#21015)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
@ -29,6 +33,8 @@ All notable changes to this project will be documented in this file.
|
||||||
- Berry `string.startswith`, `string.endswith` and `%q` format (#20909)
|
- Berry `string.startswith`, `string.endswith` and `%q` format (#20909)
|
||||||
- LVGL `lv.draw_label_dsc` and `lv_bar.get_indic_area` (#20936)
|
- LVGL `lv.draw_label_dsc` and `lv_bar.get_indic_area` (#20936)
|
||||||
- HASPmota support for scale, percentages (#20974)
|
- HASPmota support for scale, percentages (#20974)
|
||||||
|
- Support for ESP32-S3 120Mhz (#20973)
|
||||||
|
- Support for MCP23S08 (#20971)
|
||||||
|
|
||||||
### Breaking Changed
|
### Breaking Changed
|
||||||
- Drop support for old (insecure) fingerprint format (#20842)
|
- Drop support for old (insecure) fingerprint format (#20842)
|
||||||
|
@ -40,16 +46,18 @@ All notable changes to this project will be documented in this file.
|
||||||
- LVGL improved readability of montserrat-10 (#20900)
|
- LVGL improved readability of montserrat-10 (#20900)
|
||||||
- HASPmota moved to a distinct library `lv_haspmota` (#20929)
|
- HASPmota moved to a distinct library `lv_haspmota` (#20929)
|
||||||
- HASPmota solidify server-side (#20938)
|
- HASPmota solidify server-side (#20938)
|
||||||
|
- Refactor Platformio script `post_esp32.py` (#20966)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Berry bug when parsing ternary operator (#20839)
|
- Berry bug when parsing ternary operator (#20839)
|
||||||
- HASPmota widgets line, btnmatrix, qrcode, bar, checkbox (#20881)
|
- HASPmota widgets line, btnmatrix, qrcode, bar, checkbox (#20881)
|
||||||
- Filesystem save of JSON settings data
|
- Filesystem save of JSON settings data
|
||||||
- Berry fix walrus with member or index (#20939)
|
- Berry fix walrus with member or index (#20939)
|
||||||
|
- TuyaV2 suppressed dimmer updates from MQTT (#20950)
|
||||||
|
|
||||||
## [13.4.0.1] 20240229
|
## [13.4.0.1] 20240229
|
||||||
### Added
|
### Added
|
||||||
- Experimental support for LoRa
|
- Support for LoRa
|
||||||
- HASPmota `p<x>b<y>.delete` to delete an object (#20735)
|
- HASPmota `p<x>b<y>.delete` to delete an object (#20735)
|
||||||
- LVGL and HASPmota typicons font (#20742)
|
- LVGL and HASPmota typicons font (#20742)
|
||||||
- HASPmota more attributes (#20744)
|
- HASPmota more attributes (#20744)
|
||||||
|
|
|
@ -118,12 +118,15 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
|
||||||
|
|
||||||
## Changelog v13.4.0.3
|
## Changelog v13.4.0.3
|
||||||
### Added
|
### Added
|
||||||
- Experimental support for LoRa
|
- Support for LoRa and LoRaWanBridge
|
||||||
- Support for AMS5915/AMS6915 temperature and pressure sensors [#20814](https://github.com/arendst/Tasmota/issues/20814)
|
- Support for AMS5915/AMS6915 temperature and pressure sensors [#20814](https://github.com/arendst/Tasmota/issues/20814)
|
||||||
- Show calculated heat index if temperature and humidity is available with ``#define USE_HEAT_INDEX`` [#4771](https://github.com/arendst/Tasmota/issues/4771)
|
- Show calculated heat index if temperature and humidity is available with ``#define USE_HEAT_INDEX`` [#4771](https://github.com/arendst/Tasmota/issues/4771)
|
||||||
- IR support data larger than 64 bits [#20831](https://github.com/arendst/Tasmota/issues/20831)
|
- IR support data larger than 64 bits [#20831](https://github.com/arendst/Tasmota/issues/20831)
|
||||||
- TasMesh support for LWT messages [#20392](https://github.com/arendst/Tasmota/issues/20392)
|
- TasMesh support for LWT messages [#20392](https://github.com/arendst/Tasmota/issues/20392)
|
||||||
- QMC5883l check for overflow and scale reading [#20643](https://github.com/arendst/Tasmota/issues/20643)
|
- QMC5883l check for overflow and scale reading [#20643](https://github.com/arendst/Tasmota/issues/20643)
|
||||||
|
- Support for MCP23S08 [#20971](https://github.com/arendst/Tasmota/issues/20971)
|
||||||
|
- Support for ESP32-S3 120Mhz [#20973](https://github.com/arendst/Tasmota/issues/20973)
|
||||||
|
- Zigbee support for attributes of type `uint48` used by energy monitoring [#20992](https://github.com/arendst/Tasmota/issues/20992)
|
||||||
- Berry explicit error log when memory allocation fails [#20807](https://github.com/arendst/Tasmota/issues/20807)
|
- Berry explicit error log when memory allocation fails [#20807](https://github.com/arendst/Tasmota/issues/20807)
|
||||||
- Berry `path.rename()` [#20840](https://github.com/arendst/Tasmota/issues/20840)
|
- Berry `path.rename()` [#20840](https://github.com/arendst/Tasmota/issues/20840)
|
||||||
- Berry `string.startswith`, `string.endswith` and `%q` format [#20909](https://github.com/arendst/Tasmota/issues/20909)
|
- Berry `string.startswith`, `string.endswith` and `%q` format [#20909](https://github.com/arendst/Tasmota/issues/20909)
|
||||||
|
@ -140,6 +143,8 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- ESP32 Core3 platform update from 2024.01.12 to 2024.02.10 [#20730](https://github.com/arendst/Tasmota/issues/20730)
|
- ESP32 Core3 platform update from 2024.01.12 to 2024.02.10 [#20730](https://github.com/arendst/Tasmota/issues/20730)
|
||||||
|
- ESP32 LVGL library from v9.0.0 to v9.1.0 [#21008](https://github.com/arendst/Tasmota/issues/21008)
|
||||||
|
- Refactor Platformio script `post_esp32.py` [#20966](https://github.com/arendst/Tasmota/issues/20966)
|
||||||
- NeoPool webUI pH alarms (4 & 5) completed (#20743)[#20743](https://github.com/arendst/Tasmota/issues/20743)
|
- NeoPool webUI pH alarms (4 & 5) completed (#20743)[#20743](https://github.com/arendst/Tasmota/issues/20743)
|
||||||
- Prevent shutter MQTT broadcast with activated ShutterLock [#20827](https://github.com/arendst/Tasmota/issues/20827)
|
- Prevent shutter MQTT broadcast with activated ShutterLock [#20827](https://github.com/arendst/Tasmota/issues/20827)
|
||||||
- Berry class `int64` made immutable [#20727](https://github.com/arendst/Tasmota/issues/20727)
|
- Berry class `int64` made immutable [#20727](https://github.com/arendst/Tasmota/issues/20727)
|
||||||
|
@ -157,11 +162,16 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
|
||||||
### Fixed
|
### Fixed
|
||||||
- Filesystem save of JSON settings data
|
- Filesystem save of JSON settings data
|
||||||
- Shutter inverted using internal commands [#20752](https://github.com/arendst/Tasmota/issues/20752)
|
- Shutter inverted using internal commands [#20752](https://github.com/arendst/Tasmota/issues/20752)
|
||||||
|
- TuyaV2 suppressed dimmer updates from MQTT [#20950](https://github.com/arendst/Tasmota/issues/20950)
|
||||||
|
- Scripter google char memory leak [#20995](https://github.com/arendst/Tasmota/issues/20995)
|
||||||
- ESP32 PWM activity on unconfigured PWM GPIOs [#20732](https://github.com/arendst/Tasmota/issues/20732)
|
- ESP32 PWM activity on unconfigured PWM GPIOs [#20732](https://github.com/arendst/Tasmota/issues/20732)
|
||||||
|
- BTHome, prep BLE5 [#20989](https://github.com/arendst/Tasmota/issues/20989)
|
||||||
- Berry Memory leak in `import re` [#20823](https://github.com/arendst/Tasmota/issues/20823)
|
- Berry Memory leak in `import re` [#20823](https://github.com/arendst/Tasmota/issues/20823)
|
||||||
- Berry bug when parsing ternary operator [#20839](https://github.com/arendst/Tasmota/issues/20839)
|
- Berry bug when parsing ternary operator [#20839](https://github.com/arendst/Tasmota/issues/20839)
|
||||||
- Berry fix walrus with member or index [#20939](https://github.com/arendst/Tasmota/issues/20939)
|
- Berry walrus with member or index [#20939](https://github.com/arendst/Tasmota/issues/20939)
|
||||||
|
- Berry walrus bug when assigning to self [#21015](https://github.com/arendst/Tasmota/issues/21015)
|
||||||
- HASPmota PSRAM memory leak [#20818](https://github.com/arendst/Tasmota/issues/20818)
|
- HASPmota PSRAM memory leak [#20818](https://github.com/arendst/Tasmota/issues/20818)
|
||||||
- HASPmota widgets line, btnmatrix, qrcode, bar, checkbox [#20881](https://github.com/arendst/Tasmota/issues/20881)
|
- HASPmota widgets line, btnmatrix, qrcode, bar, checkbox [#20881](https://github.com/arendst/Tasmota/issues/20881)
|
||||||
|
- HASPmota demo and robotocondensed fonts [#21014](https://github.com/arendst/Tasmota/issues/21014)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
|
@ -8,70 +8,154 @@
|
||||||
|
|
||||||
#ifdef USE_SPI
|
#ifdef USE_SPI
|
||||||
#ifdef USE_SPI_LORA
|
#ifdef USE_SPI_LORA
|
||||||
|
|
||||||
|
#ifdef USE_LORAWAN_BRIDGE
|
||||||
|
#define USE_LORAWAN
|
||||||
|
#endif
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* LoRa defines and global struct
|
* LoRa defines and global struct
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
//#define USE_LORA_DEBUG
|
//#define USE_LORA_DEBUG
|
||||||
|
|
||||||
#define LORA_MAX_PACKET_LENGTH 252 // Max packet length allowed (keeping room for control bytes)
|
#define XDRV_73_KEY "drvset73"
|
||||||
#define TAS_LORA_REMOTE_COMMAND 0x17 // Header defining remote LoRaCommand
|
|
||||||
|
|
||||||
#ifndef TAS_LORA_FREQUENCY
|
#ifndef TAS_LORA_FREQUENCY
|
||||||
#define TAS_LORA_FREQUENCY 868.0 // Allowed values range from 150.0 to 960.0 MHz
|
#define TAS_LORA_FREQUENCY 868.0 // Allowed values range from 150.0 to 960.0 MHz
|
||||||
#endif
|
#endif
|
||||||
#ifndef TAS_LORA_BANDWIDTH
|
#ifndef TAS_LORA_BANDWIDTH
|
||||||
#define TAS_LORA_BANDWIDTH 125.0 // Allowed values are 7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125.0, 250.0 and 500.0 kHz
|
#define TAS_LORA_BANDWIDTH 125.0 // Allowed values are 7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125.0, 250.0 and 500.0 kHz
|
||||||
#endif
|
#endif
|
||||||
#ifndef TAS_LORA_SPREADING_FACTOR
|
#ifndef TAS_LORA_SPREADING_FACTOR
|
||||||
#define TAS_LORA_SPREADING_FACTOR 9 // Allowed values range from 5 to 12
|
#define TAS_LORA_SPREADING_FACTOR 9 // Allowed values range from 5 to 12
|
||||||
#endif
|
#endif
|
||||||
#ifndef TAS_LORA_CODING_RATE
|
#ifndef TAS_LORA_CODING_RATE
|
||||||
#define TAS_LORA_CODING_RATE 7 // Allowed values range from 5 to 8
|
#define TAS_LORA_CODING_RATE 7 // Allowed values range from 5 to 8
|
||||||
#endif
|
#endif
|
||||||
#ifndef TAS_LORA_SYNC_WORD
|
#ifndef TAS_LORA_SYNC_WORD
|
||||||
#define TAS_LORA_SYNC_WORD 0x12 // Allowed values range from 1 to 255
|
#define TAS_LORA_SYNC_WORD 0x12 // Allowed values range from 1 to 255
|
||||||
#endif
|
#endif
|
||||||
#ifndef TAS_LORA_OUTPUT_POWER
|
#ifndef TAS_LORA_OUTPUT_POWER
|
||||||
#define TAS_LORA_OUTPUT_POWER 10 // Allowed values range from 1 to 20
|
#define TAS_LORA_OUTPUT_POWER 10 // Allowed values range from 1 to 20
|
||||||
#endif
|
#endif
|
||||||
#ifndef TAS_LORA_PREAMBLE_LENGTH
|
#ifndef TAS_LORA_PREAMBLE_LENGTH
|
||||||
#define TAS_LORA_PREAMBLE_LENGTH 8 // Allowed values range from 1 to 65535
|
#define TAS_LORA_PREAMBLE_LENGTH 8 // Allowed values range from 1 to 65535
|
||||||
#endif
|
#endif
|
||||||
#ifndef TAS_LORA_CURRENT_LIMIT
|
#ifndef TAS_LORA_CURRENT_LIMIT
|
||||||
#define TAS_LORA_CURRENT_LIMIT 60.0 // Overcurrent Protection - OCP in mA
|
#define TAS_LORA_CURRENT_LIMIT 60.0 // Overcurrent Protection - OCP in mA
|
||||||
#endif
|
#endif
|
||||||
#ifndef TAS_LORA_HEADER
|
#ifndef TAS_LORA_HEADER
|
||||||
#define TAS_LORA_HEADER 0 // Explicit (0) or Implicit (1 to 4) Header
|
#define TAS_LORA_HEADER 0 // Explicit (0) or Implicit (1 to 4) Header
|
||||||
#endif
|
#endif
|
||||||
#ifndef TAS_LORA_CRC_BYTES
|
#ifndef TAS_LORA_CRC_BYTES
|
||||||
#define TAS_LORA_CRC_BYTES 2 // No (0) or Number (1 to 4) of CRC bytes
|
#define TAS_LORA_CRC_BYTES 2 // No (0) or Number (1 to 4) of CRC bytes
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef TAS_LORAWAN_FREQUENCY
|
||||||
|
#define TAS_LORAWAN_FREQUENCY 868.1 // Allowed values range from 150.0 to 960.0 MHz
|
||||||
|
#endif
|
||||||
|
#ifndef TAS_LORAWAN_BANDWIDTH
|
||||||
|
#define TAS_LORAWAN_BANDWIDTH 125.0 // Allowed values are 7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125.0, 250.0 and 500.0 kHz
|
||||||
|
#endif
|
||||||
|
#ifndef TAS_LORAWAN_SPREADING_FACTOR
|
||||||
|
#define TAS_LORAWAN_SPREADING_FACTOR 9 // Allowed values range from 5 to 12
|
||||||
|
#endif
|
||||||
|
#ifndef TAS_LORAWAN_CODING_RATE
|
||||||
|
#define TAS_LORAWAN_CODING_RATE 5 // Allowed values range from 5 to 8
|
||||||
|
#endif
|
||||||
|
#ifndef TAS_LORAWAN_SYNC_WORD
|
||||||
|
#define TAS_LORAWAN_SYNC_WORD 0x34 // Allowed values range from 1 to 255
|
||||||
|
#endif
|
||||||
|
#ifndef TAS_LORAWAN_OUTPUT_POWER
|
||||||
|
#define TAS_LORAWAN_OUTPUT_POWER 10 // Allowed values range from 1 to 20
|
||||||
|
#endif
|
||||||
|
#ifndef TAS_LORAWAN_PREAMBLE_LENGTH
|
||||||
|
#define TAS_LORAWAN_PREAMBLE_LENGTH 8 // Allowed values range from 1 to 65535
|
||||||
|
#endif
|
||||||
|
#ifndef TAS_LORAWAN_CURRENT_LIMIT
|
||||||
|
#define TAS_LORAWAN_CURRENT_LIMIT 60.0 // Overcurrent Protection - OCP in mA
|
||||||
|
#endif
|
||||||
|
#ifndef TAS_LORAWAN_HEADER
|
||||||
|
#define TAS_LORAWAN_HEADER 0 // Explicit (0) or Implicit (1 to 4) Header
|
||||||
|
#endif
|
||||||
|
#ifndef TAS_LORAWAN_CRC_BYTES
|
||||||
|
#define TAS_LORAWAN_CRC_BYTES 2 // No (0) or Number (1 to 4) of CRC bytes
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TAS_LORA_MAX_PACKET_LENGTH 252 // Max packet length allowed (keeping room for control bytes)
|
||||||
|
#define TAS_LORA_REMOTE_COMMAND 0x17 // Header defining remote LoRaCommand
|
||||||
|
|
||||||
|
#define TAS_LORAWAN_JOINNONCE 0x00E50631 // Tasmota node 1 JoinNonce
|
||||||
|
#define TAS_LORAWAN_NETID 0x00000000 // Tasmota private network
|
||||||
|
#define TAS_LORAWAN_RECEIVE_DELAY1 1000 // LoRaWan Receive delay 1
|
||||||
|
#define TAS_LORAWAN_RECEIVE_DELAY2 1000 // LoRaWan Receive delay 2
|
||||||
|
#define TAS_LORAWAN_JOIN_ACCEPT_DELAY1 5000 // LoRaWan Join accept delay 1
|
||||||
|
#define TAS_LORAWAN_JOIN_ACCEPT_DELAY2 1000 // LoRaWan Join accept delay 2
|
||||||
|
#define TAS_LORAWAN_ENDNODES 4 // Max number od supported endnodes
|
||||||
|
#define TAS_LORAWAN_AES128_KEY_SIZE 16 // Size in bytes
|
||||||
|
|
||||||
|
enum TasLoraFlags { TAS_LORAWAN_BRIDGE_ENABLED,
|
||||||
|
TAS_LORAWAN_JOIN_ENABLED,
|
||||||
|
TAS_LORAWAN_DECODE_ENABLED
|
||||||
|
};
|
||||||
|
|
||||||
|
enum TasLoraWanFlags { TAS_LORAWAN_LINK_ADR_REQ
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t DevEUIh;
|
||||||
|
uint32_t DevEUIl;
|
||||||
|
uint32_t FCntUp;
|
||||||
|
uint32_t FCntDown;
|
||||||
|
String name;
|
||||||
|
uint16_t DevNonce;
|
||||||
|
uint16_t flags;
|
||||||
|
uint8_t AppKey[TAS_LORAWAN_AES128_KEY_SIZE];
|
||||||
|
} tEndNode;
|
||||||
|
|
||||||
|
// Global structure containing driver saved variables
|
||||||
|
struct {
|
||||||
|
uint32_t crc32; // To detect file changes
|
||||||
|
float frequency; // 868.0 MHz
|
||||||
|
float bandwidth; // 125.0 kHz
|
||||||
|
float current_limit; // 60.0 mA (Overcurrent Protection (OCP))
|
||||||
|
uint16_t preamble_length; // 8 symbols
|
||||||
|
uint8_t sync_word; // 0x12
|
||||||
|
uint8_t spreading_factor; // 9
|
||||||
|
uint8_t coding_rate; // 7
|
||||||
|
uint8_t output_power; // 10 dBm
|
||||||
|
uint8_t implicit_header; // 0
|
||||||
|
uint8_t crc_bytes; // 2 bytes
|
||||||
|
uint8_t flags;
|
||||||
|
#ifdef USE_LORAWAN_BRIDGE
|
||||||
|
tEndNode end_node[TAS_LORAWAN_ENDNODES]; // End node parameters
|
||||||
|
#endif // USE_LORAWAN_BRIDGE
|
||||||
|
} LoraSettings;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool (* Config)(void);
|
bool (* Config)(void);
|
||||||
bool (* Available)(void);
|
bool (* Available)(void);
|
||||||
int (* Receive)(char*);
|
int (* Receive)(char*);
|
||||||
bool (* Send)(uint8_t*, uint32_t);
|
bool (* Send)(uint8_t*, uint32_t, bool);
|
||||||
|
uint32_t receive_time;
|
||||||
float rssi;
|
float rssi;
|
||||||
float snr;
|
float snr;
|
||||||
float frequency; // 868.0 MHz
|
uint8_t packet_size; // Max is 255 (LORA_MAX_PACKET_LENGTH)
|
||||||
float bandwidth; // 125.0 kHz
|
volatile bool receivedFlag; // flag to indicate that a packet was received
|
||||||
float current_limit; // 60.0 mA (Overcurrent Protection (OCP))
|
|
||||||
uint16_t preamble_length; // 8 symbols
|
|
||||||
uint8_t sync_word; // 0x12
|
|
||||||
uint8_t spreading_factor; // 9
|
|
||||||
uint8_t coding_rate; // 7
|
|
||||||
uint8_t output_power; // 10 dBm
|
|
||||||
uint8_t implicit_header; // 0
|
|
||||||
uint8_t crc_bytes; // 2 bytes
|
|
||||||
uint8_t packet_size; // Max is 255 (LORA_MAX_PACKET_LENGTH)
|
|
||||||
volatile bool receivedFlag; // flag to indicate that a packet was received
|
|
||||||
volatile bool enableInterrupt; // disable interrupt when it's not needed
|
|
||||||
bool sendFlag;
|
bool sendFlag;
|
||||||
bool raw;
|
bool raw;
|
||||||
bool present;
|
bool present;
|
||||||
} Lora;
|
} Lora;
|
||||||
|
|
||||||
|
#ifdef USE_LORAWAN_BRIDGE
|
||||||
|
struct {
|
||||||
|
uint32_t device_address;
|
||||||
|
uint32_t send_buffer_step;
|
||||||
|
size_t send_buffer_len;
|
||||||
|
uint8_t send_buffer[64];
|
||||||
|
bool rx;
|
||||||
|
} Lorawan;
|
||||||
|
#endif // USE_LORAWAN_BRIDGE
|
||||||
|
|
||||||
#endif // USE_SPI_LORA
|
#endif // USE_SPI_LORA
|
||||||
#endif // USE_SPI
|
#endif // USE_SPI
|
||||||
|
|
|
@ -30,125 +30,76 @@
|
||||||
SX1262 LoRaRadio = nullptr; // Select LoRa support
|
SX1262 LoRaRadio = nullptr; // Select LoRa support
|
||||||
|
|
||||||
void LoraOnReceiveSx126x(void) {
|
void LoraOnReceiveSx126x(void) {
|
||||||
if (!Lora.enableInterrupt) { return; } // check if the interrupt is enabled
|
// This is called after EVERY type of enabled interrupt so chk for valid receivedFlag in LoraAvailableSx126x()
|
||||||
|
if (!Lora.sendFlag && !Lora.receivedFlag && !Lora.receive_time) {
|
||||||
|
Lora.receive_time = millis();
|
||||||
|
}
|
||||||
Lora.receivedFlag = true; // we got a packet, set the flag
|
Lora.receivedFlag = true; // we got a packet, set the flag
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoraAvailableSx126x(void) {
|
bool LoraAvailableSx126x(void) {
|
||||||
return (Lora.receivedFlag); // check if the flag is set
|
if (Lora.receivedFlag && Lora.sendFlag) {
|
||||||
|
Lora.receivedFlag = false; // Reset receive flag as it was caused by send interrupt
|
||||||
|
Lora.sendFlag = false;
|
||||||
|
LoRaRadio.startReceive(); // Put module back to listen mode
|
||||||
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("S6X: Receive restarted"));
|
||||||
|
}
|
||||||
|
return (Lora.receivedFlag); // Check if the receive flag is set
|
||||||
}
|
}
|
||||||
|
|
||||||
int LoraReceiveSx126x(char* data) {
|
int LoraReceiveSx126x(char* data) {
|
||||||
Lora.enableInterrupt = false; // disable the interrupt service routine while processing the data
|
Lora.receivedFlag = false; // Reset flag
|
||||||
Lora.receivedFlag = false; // reset flag
|
int packet_size = LoRaRadio.getPacketLength();
|
||||||
|
int state = LoRaRadio.readData((uint8_t*)data, TAS_LORA_MAX_PACKET_LENGTH -1);
|
||||||
int packet_size = 0;
|
// LoRaWan downlink frames are sent without CRC, which will raise error on SX126x. We can ignore that error
|
||||||
int state = LoRaRadio.readData((uint8_t*)data, LORA_MAX_PACKET_LENGTH -1);
|
if (RADIOLIB_ERR_CRC_MISMATCH == state) {
|
||||||
|
state = RADIOLIB_ERR_NONE;
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("S6X: Ignoring CRC error"));
|
||||||
|
}
|
||||||
if (RADIOLIB_ERR_NONE == state) {
|
if (RADIOLIB_ERR_NONE == state) {
|
||||||
if (!Lora.sendFlag) {
|
Lora.rssi = LoRaRadio.getRSSI();
|
||||||
packet_size = LoRaRadio.getPacketLength();
|
Lora.snr = LoRaRadio.getSNR();
|
||||||
#ifdef USE_LORA_DEBUG
|
} else {
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Packet %d, Rcvd %32_H"), packet_size, data);
|
packet_size = 0; // Some other error occurred
|
||||||
#endif
|
AddLog(LOG_LEVEL_DEBUG, PSTR("S6X: Receive error %d"), state);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (RADIOLIB_ERR_CRC_MISMATCH == state) {
|
|
||||||
// packet was received, but is malformed
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: CRC error"));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// some other error occurred
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Receive error %d"), state);
|
|
||||||
}
|
|
||||||
|
|
||||||
LoRaRadio.startReceive(); // put module back to listen mode
|
|
||||||
Lora.sendFlag = false;
|
|
||||||
Lora.enableInterrupt = true; // we're ready to receive more packets, enable interrupt service routine
|
|
||||||
|
|
||||||
Lora.rssi = LoRaRadio.getRSSI();
|
|
||||||
Lora.snr = LoRaRadio.getSNR();
|
|
||||||
return packet_size;
|
return packet_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoraSendSx126x(uint8_t* data, uint32_t len) {
|
bool LoraSendSx126x(uint8_t* data, uint32_t len, bool invert) {
|
||||||
Lora.sendFlag = true;
|
Lora.sendFlag = true; // Use this flag as LoRaRadio.transmit enable send interrupt
|
||||||
#ifdef USE_LORA_DEBUG
|
if (invert) {
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Len %d, Send %*_H"), len, len + 2, data);
|
LoRaRadio.invertIQ(true);
|
||||||
#endif
|
|
||||||
/*
|
|
||||||
int state = LoRaRadio.startTransmit(data, len);
|
|
||||||
return (RADIOLIB_ERR_NONE == state);
|
|
||||||
*/
|
|
||||||
// https://learn.circuit.rocks/battery-powered-lora-sensor-node
|
|
||||||
uint32_t retry_CAD = 0;
|
|
||||||
uint32_t retry_send = 0;
|
|
||||||
bool send_success = false;
|
|
||||||
while (!send_success) {
|
|
||||||
#ifdef USE_LORA_DEBUG
|
|
||||||
time_t lora_time = millis();
|
|
||||||
#endif
|
|
||||||
// Check 200ms for an opportunity to send
|
|
||||||
while (LoRaRadio.scanChannel() != RADIOLIB_CHANNEL_FREE) {
|
|
||||||
retry_CAD++;
|
|
||||||
if (retry_CAD == 20) {
|
|
||||||
// LoRa channel is busy too long, give up
|
|
||||||
#ifdef USE_LORA_DEBUG
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Channel is too busy, give up"));
|
|
||||||
#endif
|
|
||||||
retry_send++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef USE_LORA_DEBUG
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: CAD finished after %ldms tried %d times"), (millis() - lora_time), retry_CAD);
|
|
||||||
#endif
|
|
||||||
if (retry_CAD < 20) {
|
|
||||||
// Channel is free, start sending
|
|
||||||
#ifdef USE_LORA_DEBUG
|
|
||||||
lora_time = millis();
|
|
||||||
#endif
|
|
||||||
int status = LoRaRadio.transmit(data, len);
|
|
||||||
#ifdef USE_LORA_DEBUG
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Transmit finished after %ldms with status %d"), (millis() - lora_time), status);
|
|
||||||
#endif
|
|
||||||
if (status == RADIOLIB_ERR_NONE) {
|
|
||||||
send_success = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
retry_send++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (retry_send == 3) {
|
|
||||||
#ifdef USE_LORA_DEBUG
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Failed 3 times to send data, giving up"));
|
|
||||||
#endif
|
|
||||||
send_success = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return send_success;
|
int state = LoRaRadio.transmit(data, len);
|
||||||
|
if (invert) {
|
||||||
|
LoRaRadio.invertIQ(false);
|
||||||
|
}
|
||||||
|
return (RADIOLIB_ERR_NONE == state);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoraConfigSx126x(void) {
|
bool LoraConfigSx126x(void) {
|
||||||
LoRaRadio.setCodingRate(Lora.coding_rate);
|
LoRaRadio.setCodingRate(LoraSettings.coding_rate);
|
||||||
LoRaRadio.setSyncWord(Lora.sync_word);
|
LoRaRadio.setSyncWord(LoraSettings.sync_word);
|
||||||
LoRaRadio.setPreambleLength(Lora.preamble_length);
|
LoRaRadio.setPreambleLength(LoraSettings.preamble_length);
|
||||||
LoRaRadio.setCurrentLimit(Lora.current_limit);
|
LoRaRadio.setCurrentLimit(LoraSettings.current_limit);
|
||||||
LoRaRadio.setCRC(Lora.crc_bytes);
|
LoRaRadio.setCRC(LoraSettings.crc_bytes);
|
||||||
LoRaRadio.setSpreadingFactor(Lora.spreading_factor);
|
LoRaRadio.setSpreadingFactor(LoraSettings.spreading_factor);
|
||||||
LoRaRadio.setBandwidth(Lora.bandwidth);
|
LoRaRadio.setBandwidth(LoraSettings.bandwidth);
|
||||||
LoRaRadio.setFrequency(Lora.frequency);
|
LoRaRadio.setFrequency(LoraSettings.frequency);
|
||||||
LoRaRadio.setOutputPower(Lora.output_power);
|
LoRaRadio.setOutputPower(LoraSettings.output_power);
|
||||||
if (Lora.implicit_header) {
|
if (LoraSettings.implicit_header) {
|
||||||
LoRaRadio.implicitHeader(Lora.implicit_header);
|
LoRaRadio.implicitHeader(LoraSettings.implicit_header);
|
||||||
} else {
|
} else {
|
||||||
LoRaRadio.explicitHeader();
|
LoRaRadio.explicitHeader();
|
||||||
}
|
}
|
||||||
|
LoRaRadio.invertIQ(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoraInitSx126x(void) {
|
bool LoraInitSx126x(void) {
|
||||||
LoRaRadio = new Module(Pin(GPIO_LORA_CS), Pin(GPIO_LORA_DI1), Pin(GPIO_LORA_RST), Pin(GPIO_LORA_BUSY));
|
LoRaRadio = new Module(Pin(GPIO_LORA_CS), Pin(GPIO_LORA_DI1), Pin(GPIO_LORA_RST), Pin(GPIO_LORA_BUSY));
|
||||||
if (RADIOLIB_ERR_NONE == LoRaRadio.begin(Lora.frequency)) {
|
if (RADIOLIB_ERR_NONE == LoRaRadio.begin(LoraSettings.frequency)) {
|
||||||
LoraConfigSx126x();
|
LoraConfigSx126x();
|
||||||
LoRaRadio.setDio1Action(LoraOnReceiveSx126x);
|
LoRaRadio.setDio1Action(LoraOnReceiveSx126x);
|
||||||
if (RADIOLIB_ERR_NONE == LoRaRadio.startReceive()) {
|
if (RADIOLIB_ERR_NONE == LoRaRadio.startReceive()) {
|
||||||
|
|
|
@ -27,30 +27,17 @@
|
||||||
* - LoRa_DIO0
|
* - LoRa_DIO0
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
//#define USE_LORA_SX127X_CAD
|
|
||||||
|
|
||||||
#include <LoRa.h> // extern LoRaClass LoRa;
|
#include <LoRa.h> // extern LoRaClass LoRa;
|
||||||
|
|
||||||
#ifdef USE_LORA_SX127X_CAD
|
|
||||||
void LoraOnCadDoneSx127x(boolean signalDetected) {
|
|
||||||
if (signalDetected) { // detect preamble
|
|
||||||
#ifdef USE_LORA_DEBUG
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Signal detected"));
|
|
||||||
#endif
|
|
||||||
LoRa.receive(); // put the radio into continuous receive mode
|
|
||||||
} else {
|
|
||||||
LoRa.channelActivityDetection(); // try next activity dectection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // USE_LORA_SX127X_CAD
|
|
||||||
|
|
||||||
// this function is called when a complete packet is received by the module
|
|
||||||
void LoraOnReceiveSx127x(int packet_size) {
|
void LoraOnReceiveSx127x(int packet_size) {
|
||||||
|
// This function is called when a complete packet is received by the module
|
||||||
#ifdef USE_LORA_DEBUG
|
#ifdef USE_LORA_DEBUG
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Packet size %d"), packet_size);
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("S7X: Packet size %d"), packet_size);
|
||||||
#endif
|
#endif
|
||||||
if (0 == packet_size) { return; } // if there's no packet, return
|
if (0 == packet_size) { return; } // if there's no packet, return
|
||||||
if (!Lora.enableInterrupt) { return; } // check if the interrupt is enabled
|
if (!Lora.receive_time) {
|
||||||
|
Lora.receive_time = millis();
|
||||||
|
}
|
||||||
Lora.packet_size = packet_size; // we got a packet, set the flag
|
Lora.packet_size = packet_size; // we got a packet, set the flag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,73 +46,64 @@ bool LoraAvailableSx127x(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int LoraReceiveSx127x(char* data) {
|
int LoraReceiveSx127x(char* data) {
|
||||||
Lora.enableInterrupt = false; // disable the interrupt service routine while processing the data
|
|
||||||
|
|
||||||
int packet_size = 0;
|
int packet_size = 0;
|
||||||
while (LoRa.available()) { // read packet up to LORA_MAX_PACKET_LENGTH
|
while (LoRa.available()) { // read packet up to LORA_MAX_PACKET_LENGTH
|
||||||
char sdata = LoRa.read();
|
char sdata = LoRa.read();
|
||||||
if (packet_size < LORA_MAX_PACKET_LENGTH -1) {
|
if (packet_size < TAS_LORA_MAX_PACKET_LENGTH -1) {
|
||||||
data[packet_size++] = sdata;
|
data[packet_size++] = sdata;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if (Lora.sendFlag) { packet_size = 0; }
|
|
||||||
// Lora.sendFlag = false;
|
|
||||||
|
|
||||||
Lora.packet_size = 0; // reset flag
|
|
||||||
Lora.enableInterrupt = true; // we're ready to receive more packets, enable interrupt service routine
|
|
||||||
|
|
||||||
Lora.rssi = LoRa.packetRssi();
|
Lora.rssi = LoRa.packetRssi();
|
||||||
Lora.snr = LoRa.packetSnr();
|
Lora.snr = LoRa.packetSnr();
|
||||||
#ifdef USE_LORA_SX127X_CAD
|
Lora.packet_size = 0; // reset flag
|
||||||
LoRa.channelActivityDetection(); // put the radio into CAD mode
|
|
||||||
#endif // USE_LORA_SX127X_CAD
|
|
||||||
return packet_size;
|
return packet_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoraSendSx127x(uint8_t* data, uint32_t len) {
|
bool LoraSendSx127x(uint8_t* data, uint32_t len, bool invert) {
|
||||||
// Lora.sendFlag = true;
|
if (invert) {
|
||||||
LoRa.beginPacket(Lora.implicit_header); // start packet
|
LoRa.enableInvertIQ(); // active invert I and Q signals
|
||||||
LoRa.write(data, len); // send message
|
}
|
||||||
|
LoRa.beginPacket(LoraSettings.implicit_header); // start packet
|
||||||
|
LoRa.write(data, len); // send message
|
||||||
LoRa.endPacket(); // finish packet and send it
|
LoRa.endPacket(); // finish packet and send it
|
||||||
|
if (invert) {
|
||||||
|
LoRa.disableInvertIQ(); // normal mode
|
||||||
|
}
|
||||||
LoRa.receive(); // go back into receive mode
|
LoRa.receive(); // go back into receive mode
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoraConfigSx127x(void) {
|
bool LoraConfigSx127x(void) {
|
||||||
LoRa.setFrequency(Lora.frequency * 1000 * 1000);
|
LoRa.setFrequency(LoraSettings.frequency * 1000 * 1000);
|
||||||
LoRa.setSignalBandwidth(Lora.bandwidth * 1000);
|
LoRa.setSignalBandwidth(LoraSettings.bandwidth * 1000);
|
||||||
LoRa.setSpreadingFactor(Lora.spreading_factor);
|
LoRa.setSpreadingFactor(LoraSettings.spreading_factor);
|
||||||
LoRa.setCodingRate4(Lora.coding_rate);
|
LoRa.setCodingRate4(LoraSettings.coding_rate);
|
||||||
LoRa.setSyncWord(Lora.sync_word);
|
LoRa.setSyncWord(LoraSettings.sync_word);
|
||||||
LoRa.setTxPower(Lora.output_power);
|
LoRa.setTxPower(LoraSettings.output_power);
|
||||||
LoRa.setPreambleLength(Lora.preamble_length);
|
LoRa.setPreambleLength(LoraSettings.preamble_length);
|
||||||
LoRa.setOCP(Lora.current_limit);
|
LoRa.setOCP(LoraSettings.current_limit);
|
||||||
if (Lora.crc_bytes) {
|
if (LoraSettings.crc_bytes) {
|
||||||
LoRa.enableCrc();
|
LoRa.enableCrc();
|
||||||
} else {
|
} else {
|
||||||
LoRa.disableCrc();
|
LoRa.disableCrc();
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
if (Lora.implicit_header) {
|
if (LoraSettings.implicit_header) {
|
||||||
LoRa.implicitHeaderMode();
|
LoRa.implicitHeaderMode();
|
||||||
} else {
|
} else {
|
||||||
LoRa.explicitHeaderMode();
|
LoRa.explicitHeaderMode();
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
LoRa.disableInvertIQ(); // normal mode
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoraInitSx127x(void) {
|
bool LoraInitSx127x(void) {
|
||||||
LoRa.setPins(Pin(GPIO_LORA_CS), Pin(GPIO_LORA_RST), Pin(GPIO_LORA_DI0));
|
LoRa.setPins(Pin(GPIO_LORA_CS), Pin(GPIO_LORA_RST), Pin(GPIO_LORA_DI0));
|
||||||
if (LoRa.begin(Lora.frequency * 1000 * 1000)) {
|
if (LoRa.begin(LoraSettings.frequency * 1000 * 1000)) {
|
||||||
LoraConfigSx127x();
|
LoraConfigSx127x();
|
||||||
LoRa.onReceive(LoraOnReceiveSx127x);
|
LoRa.onReceive(LoraOnReceiveSx127x);
|
||||||
#ifdef USE_LORA_SX127X_CAD
|
|
||||||
LoRa.onCadDone(LoraOnCadDoneSx127x); // register the channel activity dectection callback
|
|
||||||
LoRa.channelActivityDetection();
|
|
||||||
#else
|
|
||||||
LoRa.receive();
|
LoRa.receive();
|
||||||
#endif // USE_LORA_SX127X_CAD
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
xdrv_73_4_lorawan_cryptography.ino - LoRaWan cryptography support for Tasmota
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2024 Theo Arends
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef USE_SPI_LORA
|
||||||
|
#ifdef USE_LORAWAN
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* LoRaWan cryptography
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
//#define USE_LORAWAN_TEST
|
||||||
|
|
||||||
|
#include <utils/Cryptography.h>
|
||||||
|
|
||||||
|
uint32_t LoraWanGenerateMIC(uint8_t* msg, size_t len, uint8_t* key) {
|
||||||
|
if((msg == NULL) || (len == 0)) {
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
RadioLibAES128Instance.init(key);
|
||||||
|
uint8_t cmac[TAS_LORAWAN_AES128_KEY_SIZE];
|
||||||
|
RadioLibAES128Instance.generateCMAC(msg, len, cmac);
|
||||||
|
return(((uint32_t)cmac[0]) | ((uint32_t)cmac[1] << 8) | ((uint32_t)cmac[2] << 16) | ((uint32_t)cmac[3]) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deriveLegacySKey derives a session key
|
||||||
|
void _LoraWanDeriveLegacySKey(uint8_t* key, uint8_t t, uint32_t jn, uint32_t nid, uint16_t dn, uint8_t* derived) {
|
||||||
|
uint8_t buf[TAS_LORAWAN_AES128_KEY_SIZE] = { 0 };
|
||||||
|
buf[0] = t;
|
||||||
|
buf[1] = jn;
|
||||||
|
buf[2] = jn >> 8;
|
||||||
|
buf[3] = jn >> 16;
|
||||||
|
buf[4] = nid;
|
||||||
|
buf[5] = nid >> 8;
|
||||||
|
buf[6] = nid >> 16;
|
||||||
|
buf[7] = dn;
|
||||||
|
buf[8] = dn >> 8;
|
||||||
|
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: buf %16_H"), buf);
|
||||||
|
|
||||||
|
RadioLibAES128Instance.init(key);
|
||||||
|
RadioLibAES128Instance.encryptECB(buf, TAS_LORAWAN_AES128_KEY_SIZE, derived);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeriveLegacyAppSKey derives the LoRaWAN Application Session Key
|
||||||
|
// - If a LoRaWAN 1.0 device joins a LoRaWAN 1.0/1.1 network, the AppKey is used as "key"
|
||||||
|
// - If a LoRaWAN 1.1 device joins a LoRaWAN 1.0 network, the NwkKey is used as "key"
|
||||||
|
void _LoraWanDeriveLegacyAppSKey(uint8_t* key, uint32_t jn, uint32_t nid, uint16_t dn, uint8_t* AppSKey) {
|
||||||
|
_LoraWanDeriveLegacySKey(key, 0x02, jn, nid, dn, AppSKey);
|
||||||
|
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: key %16_H, AppSKey %16_H"), key, AppSKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraWanDeriveLegacyAppSKey(uint32_t node, uint8_t* AppSKey) {
|
||||||
|
_LoraWanDeriveLegacyAppSKey(LoraSettings.end_node[node].AppKey, TAS_LORAWAN_JOINNONCE +node, TAS_LORAWAN_NETID, LoraSettings.end_node[node].DevNonce, AppSKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeriveLegacyNwkSKey derives the LoRaWAN 1.0 Network Session Key. AppNonce is entered as JoinNonce.
|
||||||
|
// - If a LoRaWAN 1.0 device joins a LoRaWAN 1.0/1.1 network, the AppKey is used as "key"
|
||||||
|
// - If a LoRaWAN 1.1 device joins a LoRaWAN 1.0 network, the NwkKey is used as "key"
|
||||||
|
void _LoraWanDeriveLegacyNwkSKey(uint8_t* appKey, uint32_t jn, uint32_t nid, uint16_t dn, uint8_t* NwkSKey) {
|
||||||
|
_LoraWanDeriveLegacySKey(appKey, 0x01, jn, nid, dn, NwkSKey);
|
||||||
|
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: appKey %16_H, NwkSKey %16_H"), appKey, NwkSKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraWanDeriveLegacyNwkSKey(uint32_t node, uint8_t* NwkSKey) {
|
||||||
|
_LoraWanDeriveLegacyNwkSKey(LoraSettings.end_node[node].AppKey, TAS_LORAWAN_JOINNONCE +node, TAS_LORAWAN_NETID, LoraSettings.end_node[node].DevNonce, NwkSKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_LORAWAN_TEST
|
||||||
|
/*-------------------------------------------------------------------------------------------*/
|
||||||
|
void LoraWanTestKeyDerivation(void) {
|
||||||
|
uint8_t key[] = {0xBE, 0xC4, 0x99, 0xC6, 0x9E, 0x9C, 0x93, 0x9E, 0x41, 0x3B, 0x66, 0x39, 0x61, 0x63, 0x6C, 0x61};
|
||||||
|
uint16_t dn = 0x7369; // dn := types.DevNonce{0x73, 0x69}
|
||||||
|
uint32_t nid = 0x00020101; // nid := types.NetID{0x02, 0x01, 0x01}
|
||||||
|
uint32_t jn = 0x00AE3B1C; // jn := types.JoinNonce{0xAE, 0x3B, 0x1C}
|
||||||
|
|
||||||
|
uint8_t appSKey[TAS_LORAWAN_AES128_KEY_SIZE];
|
||||||
|
_LoraWanDeriveLegacyAppSKey(key, jn, nid, dn, appSKey);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TST: LoraWanDeriveLegacyAppSKey %16_H"), appSKey);
|
||||||
|
// a.So(appSKey, should.Equal, types.AES128Key{0x8C, 0x1E, 0x05, 0x43, 0xA2, 0x29, 0x08, 0x8D, 0xE6, 0xF8, 0x4E, 0x74, 0xBB, 0x46, 0xBD, 0x62})
|
||||||
|
|
||||||
|
uint8_t nwkSKey[TAS_LORAWAN_AES128_KEY_SIZE];
|
||||||
|
_LoraWanDeriveLegacyNwkSKey(key, jn, nid, dn, nwkSKey);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TST: LoraWanDeriveLegacyNwkSKey %16_H"), nwkSKey);
|
||||||
|
// a.So(nwkSKey, should.Equal, types.AES128Key{0x0D, 0xB9, 0x24, 0xEE, 0x6A, 0xF9, 0x06, 0x98, 0xE0, 0x5F, 0xC7, 0xCE, 0x48, 0x30, 0x3C, 0x01})
|
||||||
|
}
|
||||||
|
#endif // USE_LORAWAN_TEST
|
||||||
|
|
||||||
|
/*********************************************************************************************/
|
||||||
|
|
||||||
|
//func EncryptMessage(key types.AES128Key, dir uint8, addr types.DevAddr, fCnt uint32, payload []byte, opts ...EncryptionOption) ([]byte, error) {
|
||||||
|
void LoraWanProcessAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t addr, uint32_t fcnt, uint8_t dir, uint8_t ctrId, bool counter) {
|
||||||
|
// figure out how many encryption blocks are there
|
||||||
|
size_t numBlocks = len / TAS_LORAWAN_AES128_KEY_SIZE;
|
||||||
|
if (len % TAS_LORAWAN_AES128_KEY_SIZE) {
|
||||||
|
numBlocks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the encryption blocks
|
||||||
|
uint8_t encBuffer[TAS_LORAWAN_AES128_KEY_SIZE] = { 0 };
|
||||||
|
uint8_t encBlock[TAS_LORAWAN_AES128_KEY_SIZE] = { 0 };
|
||||||
|
encBlock[0] = 0x01;
|
||||||
|
encBlock[4] = ctrId;
|
||||||
|
encBlock[5] = dir;
|
||||||
|
encBlock[6] = addr;
|
||||||
|
encBlock[7] = addr >> 8;
|
||||||
|
encBlock[8] = addr >> 16;
|
||||||
|
encBlock[9] = addr >> 24;
|
||||||
|
encBlock[10] = fcnt;
|
||||||
|
encBlock[11] = fcnt >> 8;
|
||||||
|
encBlock[12] = fcnt >> 16;
|
||||||
|
encBlock[13] = fcnt >> 24;
|
||||||
|
|
||||||
|
// now encrypt the input
|
||||||
|
// on downlink frames, this has a decryption effect because server actually "decrypts" the plaintext
|
||||||
|
size_t remLen = len;
|
||||||
|
for (size_t i = 0; i < numBlocks; i++) {
|
||||||
|
|
||||||
|
if (counter) {
|
||||||
|
encBlock[15] = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt the buffer
|
||||||
|
RadioLibAES128Instance.init(key);
|
||||||
|
RadioLibAES128Instance.encryptECB(encBlock, TAS_LORAWAN_AES128_KEY_SIZE, encBuffer);
|
||||||
|
|
||||||
|
// now xor the buffer with the input
|
||||||
|
size_t xorLen = remLen;
|
||||||
|
if (xorLen > TAS_LORAWAN_AES128_KEY_SIZE) {
|
||||||
|
xorLen = TAS_LORAWAN_AES128_KEY_SIZE;
|
||||||
|
}
|
||||||
|
for (uint8_t j = 0; j < xorLen; j++) {
|
||||||
|
out[i * TAS_LORAWAN_AES128_KEY_SIZE + j] = in[i * TAS_LORAWAN_AES128_KEY_SIZE + j] ^ encBuffer[j];
|
||||||
|
}
|
||||||
|
remLen -= xorLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptUplink decrypts an uplink payload
|
||||||
|
// - The payload contains the FRMPayload bytes
|
||||||
|
// - For FPort>0, the AppSKey is used
|
||||||
|
// - For FPort=0, the NwkSEncKey/NwkSKey is used
|
||||||
|
void LoraWanDecryptUplink(uint8_t* key, uint32_t addr, uint32_t fCnt, uint8_t* payload, size_t payload_len, uint32_t opts, uint8_t* res) {
|
||||||
|
LoraWanProcessAES(payload, payload_len, key, res, addr, fCnt, 0, opts, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptDownlink encrypts a downlink payload
|
||||||
|
// - The payload contains the FRMPayload bytes
|
||||||
|
// - For FPort>0, the AppSKey is used
|
||||||
|
// - For FPort=0, the NwkSEncKey/NwkSKey is used
|
||||||
|
//func EncryptDownlink(key types.AES128Key, addr types.DevAddr, fCnt uint32, payload []byte, opts ...EncryptionOption) ([]byte, error) {
|
||||||
|
// return encryptMessage(key, 1, addr, fCnt, payload, opts...)
|
||||||
|
void LoraWanEncryptDownlink(uint8_t* key, uint32_t addr, uint32_t fCnt, uint8_t* payload, size_t payload_len, uint32_t opts, uint8_t* res) {
|
||||||
|
LoraWanProcessAES(payload, payload_len, key, res, addr, fCnt, 1, opts, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_LORAWAN_TEST
|
||||||
|
/*-------------------------------------------------------------------------------------------*/
|
||||||
|
void LorWanTestUplinkDownlinkEncryption(void) {
|
||||||
|
uint8_t key[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||||
|
uint32_t addr = 0x01020304; // addr := types.DevAddr{1, 2, 3, 4}
|
||||||
|
uint32_t frameIdentifier = 0x0000001; // frameIdentifier := [4]byte{0x00, 0x00, 0x00, 0x01}
|
||||||
|
|
||||||
|
uint8_t res[TAS_LORAWAN_AES128_KEY_SIZE] = { 0 };
|
||||||
|
|
||||||
|
// FRM Payload
|
||||||
|
uint8_t payloadd[] = {0xCF, 0xF3, 0x0B, 0x4E};
|
||||||
|
LoraWanDecryptUplink(key, addr, 1, payloadd, 4, 0, res);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TST: LoraWanDecryptUplink %4_H"), res);
|
||||||
|
// a.So(res, should.Resemble, []byte{1, 2, 3, 4})
|
||||||
|
|
||||||
|
uint8_t payloade[] = {1, 2, 3, 4};
|
||||||
|
LoraWanEncryptDownlink(key, addr, 1, payloade, 4, 0, res);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TST: LoraWanEncryptDownlink %4_H"), res);
|
||||||
|
// a.So(res, should.Resemble, []byte{0x4E, 0x75, 0xF4, 0x40})
|
||||||
|
}
|
||||||
|
#endif // USE_LORAWAN_TEST
|
||||||
|
|
||||||
|
/*********************************************************************************************/
|
||||||
|
|
||||||
|
uint32_t LoraWanComputeMIC(uint8_t* key, uint8_t dir, uint16_t confFCnt, uint32_t addr, uint32_t fCnt, uint8_t* payload, size_t payload_len) {
|
||||||
|
uint8_t b0[TAS_LORAWAN_AES128_KEY_SIZE + payload_len]; // error: variable-sized object 'b0' may not be initialized
|
||||||
|
memset(b0, 0, sizeof(b0));
|
||||||
|
b0[0] = 0x49;
|
||||||
|
b0[1] = confFCnt;
|
||||||
|
b0[2] = confFCnt >> 8;
|
||||||
|
b0[5] = dir;
|
||||||
|
b0[6] = addr;
|
||||||
|
b0[7] = addr >> 8;
|
||||||
|
b0[8] = addr >> 16;
|
||||||
|
b0[9] = addr >> 24;
|
||||||
|
b0[10] = fCnt;
|
||||||
|
b0[11] = fCnt >> 8;
|
||||||
|
b0[15] = payload_len;
|
||||||
|
memcpy(&b0[16], payload, payload_len);
|
||||||
|
return LoraWanGenerateMIC(b0, sizeof(b0), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComputeLegacyUplinkMIC computes the Uplink Message Integrity Code.
|
||||||
|
// - The payload contains MHDR | FHDR | FPort | FRMPayload
|
||||||
|
// - The NwkSKey is used
|
||||||
|
uint32_t LoraWanComputeLegacyUplinkMIC(uint8_t* key, uint32_t addr, uint32_t fCnt, uint8_t* payload, uint8_t payload_len) {
|
||||||
|
return LoraWanComputeMIC(key, 0, 0, addr, fCnt, payload, payload_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComputeLegacyDownlinkMIC computes the Downlink Message Integrity Code.
|
||||||
|
// - The payload contains MHDR | FHDR | FPort | FRMPayload
|
||||||
|
// - The NwkSKey is used
|
||||||
|
uint32_t LoraWanComputeLegacyDownlinkMIC(uint8_t* key, uint32_t addr, uint32_t fCnt, uint8_t* payload, uint8_t payload_len) {
|
||||||
|
return LoraWanComputeMIC(key, 1, 0, addr, fCnt, payload, payload_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_LORAWAN_TEST
|
||||||
|
/*-------------------------------------------------------------------------------------------*/
|
||||||
|
void LorWanTestUplinkDownlinkMIC(void) {
|
||||||
|
uint8_t key[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||||
|
uint32_t addr = 0x01020304; // addr := types.DevAddr{1, 2, 3, 4}
|
||||||
|
uint8_t payloadWithoutMIC[] ={
|
||||||
|
0x40, // Unconfirmed Uplink
|
||||||
|
0x04, 0x03, 0x02, 0x01, // DevAddr 01020304
|
||||||
|
0x00, // Empty FCtrl
|
||||||
|
0x01, 0x00, // FCnt 1
|
||||||
|
0x01, // FPort 1
|
||||||
|
0x01, 0x02, 0x03, 0x04 // Data
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t upMIC = LoraWanComputeLegacyUplinkMIC(key, addr, 1, payloadWithoutMIC, sizeof(payloadWithoutMIC));
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TST: LoraWanComputeLegacyUplinkMIC %08X"), upMIC);
|
||||||
|
// a.So(mic, should.Equal, [4]byte{0x3B, 0x07, 0x31, 0x82}) - as uint32_t 0x8231073B
|
||||||
|
|
||||||
|
uint32_t dnMIC = LoraWanComputeLegacyDownlinkMIC(key, addr, 1, payloadWithoutMIC, sizeof(payloadWithoutMIC));
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TST: LoraWanComputeLegacyDownlinkMIC %08X"), dnMIC);
|
||||||
|
// a.So(mic, should.Equal, [4]byte{0xA5, 0x60, 0x9F, 0xA9}) - as uint32_t 0xA99F60A5
|
||||||
|
}
|
||||||
|
#endif // USE_LORAWAN_TEST
|
||||||
|
|
||||||
|
#endif // USE_LORAWAN
|
||||||
|
#endif // USE_SPI_LORA
|
|
@ -0,0 +1,700 @@
|
||||||
|
/*
|
||||||
|
xdrv_73_8_lorawan.ino - LoRaWan EU868 support for Tasmota
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2024 Theo Arends
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef USE_SPI_LORA
|
||||||
|
#ifdef USE_LORAWAN_BRIDGE
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* EU868 LoRaWan bridge support
|
||||||
|
*
|
||||||
|
* The goal of the LoRaWan Bridge is to receive from joined LoRaWan End-Devices or Nodes and
|
||||||
|
* provide decrypted MQTT data.
|
||||||
|
*
|
||||||
|
* EU868 LoRaWan uses at minimum alternating 3 frequencies and 6 spreadingfactors (datarate or DR)
|
||||||
|
* which makes the use of single fixed frequency and spreadingfactor hardware like
|
||||||
|
* SX127x (LiliGo T3, M5 LoRa868 or RFM95W) or SX126x (LiLiGo T3S3) a challenge.
|
||||||
|
* This driver uses one fixed frequency and spreadingfactor trying to tell the End-Device to do
|
||||||
|
* the same. In some cases the End-Device needs to be (serial) configured to use a single
|
||||||
|
* channel and fixed datarate.
|
||||||
|
*
|
||||||
|
* To be able to do this:
|
||||||
|
* - Tasmota needs a filesystem to store persistent data (#define USE_UFILESYS)
|
||||||
|
* - Tasmota Lora has to be configured for one of the three EU868 supported frequencies and
|
||||||
|
* spreadingfactor using command
|
||||||
|
* LoRaConfig 2
|
||||||
|
* or individual commands
|
||||||
|
* LoRaFrequency 868.1 (or 868.3 and 868.5)
|
||||||
|
* LoRaSpreadingFactor 9 (or 7..12 equals LoRaWan DataRate 5..0)
|
||||||
|
* LoRaBandwidth 125
|
||||||
|
* LoRaCodingRate4 5
|
||||||
|
* LoRaSyncWord 52
|
||||||
|
* - LoRaWan has to be enabled (#define USE_LORAWAN_BRIDGE) and configured for the End-Device
|
||||||
|
* 32 character AppKey using command LoRaWanAppKey <vendor provided appkey>
|
||||||
|
* - The End-Device needs to start it's LoRaWan join process as documented by vendor.
|
||||||
|
* This will configure the LoRanWan bridge with the needed information. The bridge will also
|
||||||
|
* try to configure the device for single frequency and datarate. The process can take
|
||||||
|
* over 5 minutes as the device transmits at different frequencies and DR.
|
||||||
|
* - If all's well MQTT data will appear when the End-Device sends it's data.
|
||||||
|
* - If a device does not support Adaptive Datarate Control (ADR) the device needs to be set to
|
||||||
|
* a fixed frequency and datarate externally. As an example the Dragino LDS02 needs to be
|
||||||
|
* configured before joining using it's serial interface with the following commands
|
||||||
|
* Password 123456
|
||||||
|
* AT+CHS=868100000
|
||||||
|
* AT+CADR=0
|
||||||
|
* AT+CDATARATE=3
|
||||||
|
*
|
||||||
|
* The current driver supports decoding for Dragino LDS02 Door/Window sensor and MerryIoT
|
||||||
|
* DW10 Door/Window sensor. Both battery powered.
|
||||||
|
*
|
||||||
|
* The MQTT output can be user defined with SetOption commands like the Zigbee driver
|
||||||
|
* SetOption89 - Distinct MQTT topics per device (1) (#7835)
|
||||||
|
* SetOption100 - Remove LwReceived form JSON message (1)
|
||||||
|
* SetOption118 - Move LwReceived from JSON message and into the subtopic replacing "SENSOR"
|
||||||
|
* SetOption119 - Remove the device addr from json payload, can be used with LoRaWanName
|
||||||
|
* where the addr is already known from the topic
|
||||||
|
* SetOption144 - Include time in `LwReceived` messages like other sensors (SO100 0 and SO118 0)
|
||||||
|
* LoRaOption3 - Disable driver decoding (1) and provide decrypted payload for decoding
|
||||||
|
*
|
||||||
|
* The LoRaWanBridge is default off. Enable it with command
|
||||||
|
* LoRaWanBridge 1
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Driver Settings load and save
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
#ifdef USE_UFILESYS
|
||||||
|
|
||||||
|
#define D_JSON_APPKEY "AppKey"
|
||||||
|
#define D_JSON_DEVEUI "DevEUI"
|
||||||
|
#define D_JSON_DEVNONCE "DevNonce"
|
||||||
|
#define D_JSON_FCNTUP "FCntUp"
|
||||||
|
#define D_JSON_FCNTDOWN "FCntDown"
|
||||||
|
#define D_JSON_FLAGS "Flags"
|
||||||
|
|
||||||
|
bool LoraWanLoadData(void) {
|
||||||
|
char key[12]; // Max 99 nodes (drvset73_1 to drvset73_99)
|
||||||
|
for (uint32_t n = 0; n < TAS_LORAWAN_ENDNODES; n++) {
|
||||||
|
snprintf_P(key, sizeof(key), PSTR(XDRV_73_KEY "_%d"), n +1);
|
||||||
|
|
||||||
|
String json = UfsJsonSettingsRead(key);
|
||||||
|
if (json.length() == 0) { continue; } // Only load used slots
|
||||||
|
|
||||||
|
// {"AppKey":"00000000000000000000000000000000","DevEUI","0000000000000000","DevNonce":0,"FCntUp":0,"FCntDown":0,"Flags":0,"NAME":""}
|
||||||
|
JsonParser parser((char*)json.c_str());
|
||||||
|
JsonParserObject root = parser.getRootObject();
|
||||||
|
if (!root) { continue; } // Only load used slots
|
||||||
|
|
||||||
|
const char* app_key = nullptr;
|
||||||
|
app_key = root.getStr(PSTR(D_JSON_APPKEY), nullptr);
|
||||||
|
if (strlen(app_key)) {
|
||||||
|
size_t out_len = TAS_LORAWAN_AES128_KEY_SIZE;
|
||||||
|
HexToBytes(app_key, LoraSettings.end_node[n].AppKey, &out_len);
|
||||||
|
}
|
||||||
|
LoraSettings.end_node[n].DevEUIh = root.getUInt(PSTR(D_JSON_DEVEUI "h"), LoraSettings.end_node[n].DevEUIh);
|
||||||
|
LoraSettings.end_node[n].DevEUIl = root.getUInt(PSTR(D_JSON_DEVEUI "l"), LoraSettings.end_node[n].DevEUIl);
|
||||||
|
LoraSettings.end_node[n].DevNonce = root.getUInt(PSTR(D_JSON_DEVNONCE), LoraSettings.end_node[n].DevNonce);
|
||||||
|
LoraSettings.end_node[n].FCntUp = root.getUInt(PSTR(D_JSON_FCNTUP), LoraSettings.end_node[n].FCntUp);
|
||||||
|
LoraSettings.end_node[n].FCntDown = root.getUInt(PSTR(D_JSON_FCNTDOWN), LoraSettings.end_node[n].FCntDown);
|
||||||
|
LoraSettings.end_node[n].flags = root.getUInt(PSTR(D_JSON_FLAGS), LoraSettings.end_node[n].flags);
|
||||||
|
const char* name = nullptr;
|
||||||
|
name = root.getStr(PSTR(D_JSON_NAME), nullptr);
|
||||||
|
if (strlen(app_key)) {
|
||||||
|
LoraSettings.end_node[n].name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoraWanSaveData(void) {
|
||||||
|
bool result = false;
|
||||||
|
for (uint32_t n = 0; n < TAS_LORAWAN_ENDNODES; n++) {
|
||||||
|
if (LoraSettings.end_node[n].AppKey[0] > 0) { // Only save used slots
|
||||||
|
Response_P(PSTR("{\"" XDRV_73_KEY "_%d\":{\"" D_JSON_APPKEY "\":\"%16_H\""
|
||||||
|
",\"" D_JSON_DEVEUI "h\":%lu,\"" D_JSON_DEVEUI "l\":%lu"
|
||||||
|
",\"" D_JSON_DEVNONCE "\":%u"
|
||||||
|
",\"" D_JSON_FCNTUP "\":%u,\"" D_JSON_FCNTDOWN "\":%u"
|
||||||
|
",\"" D_JSON_FLAGS "\":%u"
|
||||||
|
",\"" D_JSON_NAME "\":\"%s\"}}"),
|
||||||
|
n +1,
|
||||||
|
LoraSettings.end_node[n].AppKey,
|
||||||
|
LoraSettings.end_node[n].DevEUIh,LoraSettings.end_node[n].DevEUIl,
|
||||||
|
LoraSettings.end_node[n].DevNonce,
|
||||||
|
LoraSettings.end_node[n].FCntUp, LoraSettings.end_node[n].FCntDown,
|
||||||
|
LoraSettings.end_node[n].flags,
|
||||||
|
LoraSettings.end_node[n].name.c_str());
|
||||||
|
result = UfsJsonSettingsWrite(ResponseData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraWanDeleteData(void) {
|
||||||
|
char key[12]; // Max 99 nodes (drvset73_1 to drvset73_99)
|
||||||
|
for (uint32_t n = 0; n < TAS_LORAWAN_ENDNODES; n++) {
|
||||||
|
snprintf_P(key, sizeof(key), PSTR(XDRV_73_KEY "_%d"), n +1);
|
||||||
|
UfsJsonSettingsDelete(key); // Use defaults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // USE_UFILESYS
|
||||||
|
|
||||||
|
/*********************************************************************************************/
|
||||||
|
|
||||||
|
#include <Ticker.h>
|
||||||
|
Ticker LoraWan_Send;
|
||||||
|
|
||||||
|
void LoraWanTickerSend(void) {
|
||||||
|
Lorawan.send_buffer_step--;
|
||||||
|
if (1 == Lorawan.send_buffer_step) {
|
||||||
|
Lorawan.rx = true; // Always send during RX1
|
||||||
|
Lora.receive_time = 0; // Reset receive timer
|
||||||
|
LoraWan_Send.attach_ms(TAS_LORAWAN_RECEIVE_DELAY2, LoraWanTickerSend); // Retry after 1000 ms
|
||||||
|
}
|
||||||
|
if (Lorawan.rx) { // If received in RX1 do not resend in RX2
|
||||||
|
LoraSend(Lorawan.send_buffer, Lorawan.send_buffer_len, true);
|
||||||
|
}
|
||||||
|
if (Lorawan.send_buffer_step <= 0) {
|
||||||
|
LoraWan_Send.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraWanSendResponse(uint8_t* buffer, size_t len, uint32_t lorawan_delay) {
|
||||||
|
memcpy(Lorawan.send_buffer, buffer, sizeof(Lorawan.send_buffer));
|
||||||
|
Lorawan.send_buffer_len = len;
|
||||||
|
Lorawan.send_buffer_step = 2;
|
||||||
|
LoraWan_Send.attach_ms(lorawan_delay - TimePassedSince(Lora.receive_time), LoraWanTickerSend);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
uint32_t LoraWanSpreadingFactorToDataRate(void) {
|
||||||
|
// Allow only JoinReq message datarates (125kHz bandwidth)
|
||||||
|
if (LoraSettings.spreading_factor > 12) {
|
||||||
|
LoraSettings.spreading_factor = 12;
|
||||||
|
}
|
||||||
|
if (LoraSettings.spreading_factor < 7) {
|
||||||
|
LoraSettings.spreading_factor = 7;
|
||||||
|
}
|
||||||
|
LoraSettings.bandwidth = 125;
|
||||||
|
return 12 - LoraSettings.spreading_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t LoraWanFrequencyToChannel(void) {
|
||||||
|
// EU863-870 (EU868) JoinReq message frequencies are 868.1, 868.3 and 868.5
|
||||||
|
uint32_t frequency = (LoraSettings.frequency * 10);
|
||||||
|
uint32_t channel = 250;
|
||||||
|
if (8681 == frequency) {
|
||||||
|
channel = 0;
|
||||||
|
}
|
||||||
|
else if (8683 == frequency) {
|
||||||
|
channel = 1;
|
||||||
|
}
|
||||||
|
else if (8685 == frequency) {
|
||||||
|
channel = 2;
|
||||||
|
}
|
||||||
|
if (250 == channel) {
|
||||||
|
LoraSettings.frequency = 868.1;
|
||||||
|
Lora.Config();
|
||||||
|
channel = 0;
|
||||||
|
}
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************/
|
||||||
|
|
||||||
|
void LoraWanPublishHeader(uint32_t node) {
|
||||||
|
ResponseClear(); // clear string
|
||||||
|
|
||||||
|
// Do we prefix with `LwReceived`?
|
||||||
|
if (!Settings->flag4.remove_zbreceived && // SetOption100 - (Zigbee) Remove LwReceived form JSON message (1)
|
||||||
|
!Settings->flag5.zb_received_as_subtopic) { // SetOption118 - (Zigbee) Move LwReceived from JSON message and into the subtopic replacing "SENSOR" default
|
||||||
|
if (Settings->flag5.zigbee_include_time && // SetOption144 - (Zigbee) Include time in `LwReceived` messages like other sensors
|
||||||
|
(Rtc.utc_time >= START_VALID_TIME)) {
|
||||||
|
// Add time if needed (and if time is valid)
|
||||||
|
ResponseAppendTimeFormat(Settings->flag2.time_format); // CMND_TIME
|
||||||
|
ResponseAppend_P(PSTR(",\"LwReceived\":"));
|
||||||
|
} else {
|
||||||
|
ResponseAppend_P(PSTR("{\"LwReceived\":"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Settings->flag5.zb_omit_json_addr) { // SetOption119 - (Zigbee) Remove the device addr from json payload, can be used with zb_topic_fname where the addr is already known from the topic
|
||||||
|
ResponseAppend_P(PSTR("{\"%s\":"), EscapeJSONString(LoraSettings.end_node[node].name.c_str()).c_str());
|
||||||
|
}
|
||||||
|
ResponseAppend_P(PSTR("{\"Node\":%d,\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), node +1, LoraSettings.end_node[node].DevEUIl & 0x0000FFFF);
|
||||||
|
if (!LoraSettings.end_node[node].name.startsWith(F("0x"))) {
|
||||||
|
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), EscapeJSONString(LoraSettings.end_node[node].name.c_str()).c_str());
|
||||||
|
}
|
||||||
|
ResponseAppend_P(PSTR(",\"RSSI\":%1_f,\"SNR\":%1_f"), &Lora.rssi, &Lora.snr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraWanPublishFooter(uint32_t node) {
|
||||||
|
if (!Settings->flag5.zb_omit_json_addr) { // SetOption119 - (Zigbee) Remove the device addr from json payload, can be used with zb_topic_fname where the addr is already known from the topic
|
||||||
|
ResponseAppend_P(PSTR("}"));
|
||||||
|
}
|
||||||
|
if (!Settings->flag4.remove_zbreceived && // SetOption100 - (Zigbee) Remove LwReceived form JSON message (1)
|
||||||
|
!Settings->flag5.zb_received_as_subtopic) { // SetOption118 - (Zigbee) Move LwReceived from JSON message and into the subtopic replacing "SENSOR" default
|
||||||
|
ResponseAppend_P(PSTR("}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_INFLUXDB
|
||||||
|
InfluxDbProcess(1); // Use a copy of ResponseData
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (Settings->flag4.zigbee_distinct_topics) { // SetOption89 - (MQTT, Zigbee) Distinct MQTT topics per device for Zigbee (1) (#7835)
|
||||||
|
char subtopic[TOPSZ];
|
||||||
|
// Clean special characters
|
||||||
|
char stemp[TOPSZ];
|
||||||
|
strlcpy(stemp, LoraSettings.end_node[node].name.c_str(), sizeof(stemp));
|
||||||
|
MakeValidMqtt(0, stemp);
|
||||||
|
if (Settings->flag5.zigbee_hide_bridge_topic) { // SetOption125 - (Zigbee) Hide bridge topic from zigbee topic (use with SetOption89) (1)
|
||||||
|
snprintf_P(subtopic, sizeof(subtopic), PSTR("%s"), stemp);
|
||||||
|
} else {
|
||||||
|
snprintf_P(subtopic, sizeof(subtopic), PSTR("%s/%s"), TasmotaGlobal.mqtt_topic, stemp);
|
||||||
|
}
|
||||||
|
char stopic[TOPSZ];
|
||||||
|
if (Settings->flag5.zb_received_as_subtopic) // SetOption118 - (Zigbee) Move LwReceived from JSON message and into the subtopic replacing "SENSOR" default
|
||||||
|
GetTopic_P(stopic, TELE, subtopic, PSTR("LwReceived"));
|
||||||
|
else
|
||||||
|
GetTopic_P(stopic, TELE, subtopic, PSTR(D_RSLT_SENSOR));
|
||||||
|
MqttPublish(stopic, Settings->flag.mqtt_sensor_retain);
|
||||||
|
} else {
|
||||||
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings->flag.mqtt_sensor_retain);
|
||||||
|
}
|
||||||
|
XdrvRulesProcess(0); // apply rules
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************/
|
||||||
|
|
||||||
|
void LoraWanSendLinkADRReq(uint32_t node) {
|
||||||
|
uint32_t DevAddr = Lorawan.device_address +node;
|
||||||
|
uint16_t FCnt = LoraSettings.end_node[node].FCntDown++;
|
||||||
|
uint8_t NwkSKey[TAS_LORAWAN_AES128_KEY_SIZE];
|
||||||
|
LoraWanDeriveLegacyNwkSKey(node, NwkSKey);
|
||||||
|
|
||||||
|
uint8_t data[32];
|
||||||
|
data[0] = 0xA0; // Confirmed data downlink
|
||||||
|
data[1] = DevAddr;
|
||||||
|
data[2] = DevAddr >> 8;
|
||||||
|
data[3] = DevAddr >> 16;
|
||||||
|
data[4] = DevAddr >> 24;
|
||||||
|
data[5] = 0x05; // FCtrl with 5 FOpts
|
||||||
|
data[6] = FCnt;
|
||||||
|
data[7] = FCnt >> 8;
|
||||||
|
data[8] = 0x03; // CId LinkADRReq to single channel LoraFrequency and DR LoraSpreadingFactor
|
||||||
|
data[9] = LoraWanSpreadingFactorToDataRate() << 4 | 0x0F; // DataRate 3 and unchanged TXPower
|
||||||
|
data[10] = 0x01 << LoraWanFrequencyToChannel();
|
||||||
|
data[11] = 0x00;
|
||||||
|
data[12] = 0x00; // ChMaskCntl applies to Channels0..15, NbTrans is default (1)
|
||||||
|
|
||||||
|
uint32_t MIC = LoraWanComputeLegacyDownlinkMIC(NwkSKey, DevAddr, FCnt, data, 13);
|
||||||
|
data[13] = MIC;
|
||||||
|
data[14] = MIC >> 8;
|
||||||
|
data[15] = MIC >> 16;
|
||||||
|
data[16] = MIC >> 24;
|
||||||
|
|
||||||
|
// A0 F3F51700 05 0000 033F010000 0B2C1B8B
|
||||||
|
LoraWanSendResponse(data, 17, TAS_LORAWAN_RECEIVE_DELAY1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
|
||||||
|
bool result = false;
|
||||||
|
uint32_t MType = data[0] >> 5; // Upper three bits (used to be called FType)
|
||||||
|
|
||||||
|
if (0 == MType) { // Join request
|
||||||
|
// 0007010000004140A8D64A89710E4140A82893A8AD137F - Dragino
|
||||||
|
// 000600000000161600B51F000000161600FDA5D8127912 - MerryIoT
|
||||||
|
uint64_t JoinEUI = (uint64_t)data[ 1] | ((uint64_t)data[ 2] << 8) |
|
||||||
|
((uint64_t)data[ 3] << 16) | ((uint64_t)data[ 4] << 24) |
|
||||||
|
((uint64_t)data[ 5] << 32) | ((uint64_t)data[ 6] << 40) |
|
||||||
|
((uint64_t)data[ 7] << 48) | ((uint64_t)data[ 8] << 56);
|
||||||
|
uint32_t DevEUIl = (uint32_t)data[ 9] | ((uint32_t)data[10] << 8) | // Use uint32_t to fix easy JSON migration
|
||||||
|
((uint32_t)data[11] << 16) | ((uint32_t)data[12] << 24);
|
||||||
|
uint32_t DevEUIh = (uint32_t)data[13] | ((uint32_t)data[14] << 8) |
|
||||||
|
((uint32_t)data[15] << 16) | ((uint32_t)data[16] << 24);
|
||||||
|
uint16_t DevNonce = (uint16_t)data[17] | ((uint16_t)data[18] << 8);
|
||||||
|
uint32_t MIC = (uint32_t)data[19] | ((uint32_t)data[20] << 8) |
|
||||||
|
((uint32_t)data[21] << 16) | ((uint32_t)data[22] << 24);
|
||||||
|
|
||||||
|
for (uint32_t node = 0; node < TAS_LORAWAN_ENDNODES; node++) {
|
||||||
|
uint32_t CalcMIC = LoraWanGenerateMIC(data, 19, LoraSettings.end_node[node].AppKey);
|
||||||
|
if (MIC == CalcMIC) { // Valid MIC based on LoraWanAppKey
|
||||||
|
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: JoinEUI %8_H, DevEUIh %08X, DevEUIl %08X, DevNonce %04X, MIC %08X"),
|
||||||
|
(uint8_t*)&JoinEUI, DevEUIh, DevEUIl, DevNonce, MIC);
|
||||||
|
|
||||||
|
LoraSettings.end_node[node].DevEUIl = DevEUIl;
|
||||||
|
LoraSettings.end_node[node].DevEUIh = DevEUIh;
|
||||||
|
LoraSettings.end_node[node].DevNonce = DevNonce;
|
||||||
|
LoraSettings.end_node[node].FCntUp = 0;
|
||||||
|
LoraSettings.end_node[node].FCntDown = 0;
|
||||||
|
bitClear(LoraSettings.end_node[node].flags, TAS_LORAWAN_LINK_ADR_REQ);
|
||||||
|
if (LoraSettings.end_node[node].name.equals(F("0x0000"))) {
|
||||||
|
char name[10];
|
||||||
|
ext_snprintf_P(name, sizeof(name), PSTR("0x%04X"), LoraSettings.end_node[node].DevEUIl & 0x0000FFFF);
|
||||||
|
LoraSettings.end_node[node].name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t JoinNonce = TAS_LORAWAN_JOINNONCE +node;
|
||||||
|
uint32_t DevAddr = Lorawan.device_address +node;
|
||||||
|
uint32_t NetID = TAS_LORAWAN_NETID;
|
||||||
|
uint8_t join_data[33] = { 0 };
|
||||||
|
join_data[0] = 0x20; // Join Accept
|
||||||
|
join_data[1] = JoinNonce;
|
||||||
|
join_data[2] = JoinNonce >> 8;
|
||||||
|
join_data[3] = JoinNonce >> 16;
|
||||||
|
join_data[4] = NetID;
|
||||||
|
join_data[5] = NetID >> 8;
|
||||||
|
join_data[6] = NetID >> 16;
|
||||||
|
join_data[7] = DevAddr;
|
||||||
|
join_data[8] = DevAddr >> 8;
|
||||||
|
join_data[9] = DevAddr >> 16;
|
||||||
|
join_data[10] = DevAddr >> 24;
|
||||||
|
join_data[11] = 0x03; // DLSettings
|
||||||
|
join_data[12] = 1; // RXDelay;
|
||||||
|
|
||||||
|
uint32_t NewMIC = LoraWanGenerateMIC(join_data, 13, LoraSettings.end_node[node].AppKey);
|
||||||
|
join_data[13] = NewMIC;
|
||||||
|
join_data[14] = NewMIC >> 8;
|
||||||
|
join_data[15] = NewMIC >> 16;
|
||||||
|
join_data[16] = NewMIC >> 24;
|
||||||
|
uint8_t EncData[33];
|
||||||
|
EncData[0] = join_data[0];
|
||||||
|
RadioLibAES128Instance.init(LoraSettings.end_node[node].AppKey);
|
||||||
|
RadioLibAES128Instance.decryptECB(&join_data[1], 16, &EncData[1]);
|
||||||
|
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Join %17_H"), join_data);
|
||||||
|
|
||||||
|
// 203106E5000000412E010003017CB31DD4 - Dragino
|
||||||
|
// 203206E5000000422E010003016A210EEA - MerryIoT
|
||||||
|
LoraWanSendResponse(EncData, 17, TAS_LORAWAN_JOIN_ACCEPT_DELAY1);
|
||||||
|
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((2 == MType) || // Unconfirmed data uplink
|
||||||
|
(4 == MType)) { // Confirmed data uplink
|
||||||
|
// 0 1 2 3 4 5 6 7 8 9 8 9101112131415... packet_size -4
|
||||||
|
// PHYPayload --------------------------------------------------------------
|
||||||
|
// MHDR MACPayload ---------------------------------------------- MIC ----
|
||||||
|
// MHDR FHDR ----------------------- FPort FRMPayload --------- MIC ----
|
||||||
|
// MHDR DevAddr FCtrl FCnt FOpts FPort FRMPayload --------- MIC ----
|
||||||
|
// 1 4 1 2 0..15 0..1 0..N 4 - Number of octets
|
||||||
|
// Not encrypted --------------------------- Encrypted ---------- Not encr
|
||||||
|
// - Dragino
|
||||||
|
// 40 412E0100 80 2500 0A 6A6FEFD6A16B0C7AC37B 5F95FABC - decrypt using AppSKey
|
||||||
|
// 80 412E0100 80 2A00 0A A58EF5E0D1DDE03424F0 6F2D56FA - decrypt using AppSKey
|
||||||
|
// 80 412E0100 80 2B00 0A 8F2F0D33E5C5027D57A6 F67C9DFE - decrypt using AppSKey
|
||||||
|
// 40 412E0100 A0 1800 00 0395 2C94B1D8 - FCtrl ADR support, Ack, FPort = 0 -> MAC commands, decrypt using NwkSKey
|
||||||
|
// 40 412E0100 A0 7800 00 78C9 A60D8977 - FCtrl ADR support, Ack, FPort = 0 -> MAC commands, decrypt using NwkSKey
|
||||||
|
// 40 F3F51700 20 0100 00 2A7C 407036A2 - FCtrl No ADR support, Ack, FPort = 0 -> MAC commands, decrypt using NwkSKey, response after LinkADRReq
|
||||||
|
// - MerryIoT
|
||||||
|
// 40 422E0100 80 0400 78 B9C75DF9E8934C6651 A57DA6B1 - decrypt using AppSKey
|
||||||
|
// 40 422E0100 80 0100 CC 7C462537AC00C07F99 5500BF2B - decrypt using AppSKey
|
||||||
|
// 40 422E0100 A2 1800 0307 78 29FBF8FD9227729984 8C71E95B - FCtrl ADR support, Ack, FOptsLen = 2 -> FOpts = MAC, response after LinkADRReq
|
||||||
|
// 40 F4F51700 A2 0200 0307 CC 6517D4AB06D32C9A9F 14CBA305 - FCtrl ADR support, Ack, FOptsLen = 2 -> FOpts = MAC, response after LinkADRReq
|
||||||
|
|
||||||
|
uint32_t DevAddr = (uint32_t)data[1] | ((uint32_t)data[2] << 8) | ((uint32_t)data[3] << 16) | ((uint32_t)data[4] << 24);
|
||||||
|
for (uint32_t node = 0; node < TAS_LORAWAN_ENDNODES; node++) {
|
||||||
|
if (0 == LoraSettings.end_node[node].DevEUIh) { continue; } // No DevEUI so never joined
|
||||||
|
if ((Lorawan.device_address +node) != DevAddr) { continue; } // Not my device
|
||||||
|
|
||||||
|
uint32_t FCtrl = data[5];
|
||||||
|
uint32_t FOptsLen = FCtrl & 0x0F;
|
||||||
|
uint32_t FCnt = (LoraSettings.end_node[node].FCntUp & 0xFFFF0000) | data[6] | (data[7] << 8);
|
||||||
|
uint8_t* FOpts = &data[8];
|
||||||
|
uint32_t FPort = data[8 +FOptsLen];
|
||||||
|
uint8_t* FRMPayload = &data[9 +FOptsLen];
|
||||||
|
uint32_t payload_len = packet_size -9 -FOptsLen -4;
|
||||||
|
|
||||||
|
uint32_t MIC = (uint32_t)data[packet_size -4] | ((uint32_t)data[packet_size -3] << 8) | ((uint32_t)data[packet_size -2] << 16) | ((uint32_t)data[packet_size -1] << 24);
|
||||||
|
uint8_t NwkSKey[TAS_LORAWAN_AES128_KEY_SIZE];
|
||||||
|
LoraWanDeriveLegacyNwkSKey(node, NwkSKey);
|
||||||
|
uint32_t CalcMIC = LoraWanComputeLegacyUplinkMIC(NwkSKey, DevAddr, FCnt, data, packet_size -4);
|
||||||
|
if (MIC != CalcMIC) { continue; } // Same device address but never joined
|
||||||
|
|
||||||
|
bool FCtrl_ADR = bitRead(FCtrl, 7);
|
||||||
|
bool FCtrl_ACK = bitRead(FCtrl, 5);
|
||||||
|
/*
|
||||||
|
if ((0 == FOptsLen) && (0 == FOpts[0])) { // MAC response
|
||||||
|
FOptsLen = payload_len;
|
||||||
|
FOpts = &data[9];
|
||||||
|
payload_len = 0;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
uint8_t payload_decrypted[TAS_LORAWAN_AES128_KEY_SIZE +9 +payload_len];
|
||||||
|
if (payload_len) {
|
||||||
|
uint8_t Key[TAS_LORAWAN_AES128_KEY_SIZE];
|
||||||
|
if (0 == FPort) {
|
||||||
|
LoraWanDeriveLegacyNwkSKey(node, Key);
|
||||||
|
} else {
|
||||||
|
LoraWanDeriveLegacyAppSKey(node, Key);
|
||||||
|
}
|
||||||
|
LoraWanDecryptUplink(Key, DevAddr, FCnt, FRMPayload, payload_len, 0, payload_decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t org_payload_len = payload_len; // Save for logging
|
||||||
|
if ((0 == FOptsLen) && (0 == FOpts[0])) { // MAC response in payload only
|
||||||
|
FOptsLen = payload_len; // Payload length is MAC length
|
||||||
|
FOpts = payload_decrypted; // Payload is encrypted MAC
|
||||||
|
payload_len = 0; // Payload is MAC only
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_LORA_DEBUG
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: DevAddr %08X, FCtrl %0X, FOptsLen %d, FCnt %d, FOpts %*_H, FPort %d, Payload %*_H, Decrypted %*_H, MIC %08X"),
|
||||||
|
DevAddr, FCtrl, FOptsLen, FCnt, FOptsLen, FOpts, FPort, org_payload_len, FRMPayload, org_payload_len, payload_decrypted, MIC);
|
||||||
|
#endif // USE_LORA_DEBUG
|
||||||
|
|
||||||
|
if (LoraSettings.end_node[node].FCntUp <= FCnt) { // Skip re-transmissions
|
||||||
|
Lorawan.rx = false; // Skip RX2 as this is a response from RX1
|
||||||
|
LoraSettings.end_node[node].FCntUp++;
|
||||||
|
if (LoraSettings.end_node[node].FCntUp < FCnt) { // Report missed frames
|
||||||
|
uint32_t FCnt_missed = FCnt - LoraSettings.end_node[node].FCntUp;
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Missed frames %d"), FCnt_missed);
|
||||||
|
if (FCnt_missed > 1) { // Missed two or more frames
|
||||||
|
bitClear(LoraSettings.end_node[node].flags, TAS_LORAWAN_LINK_ADR_REQ); // Resend LinkADRReq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoraSettings.end_node[node].FCntUp = FCnt;
|
||||||
|
|
||||||
|
if (FOptsLen) {
|
||||||
|
uint32_t i = 0;
|
||||||
|
while (i < FOptsLen) {
|
||||||
|
if (0x02 == FOpts[i]) { // Response from LinkCheckReq (LinkCheckAns)
|
||||||
|
// Used by end-device to validate it's connectivity to a network
|
||||||
|
// Need to send Margin/GWCnt
|
||||||
|
}
|
||||||
|
else if (0x03 == FOpts[i]) { // Response from LinkADRReq (LinkADRAns)
|
||||||
|
i++;
|
||||||
|
uint8_t status = FOpts[i];
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: MAC LinkADRAns PowerACK %d, DataRateACK %d, ChannelMaskACK %d"),
|
||||||
|
bitRead(status, 2), bitRead(status, 1), bitRead(status, 0));
|
||||||
|
bitSet(LoraSettings.end_node[node].flags, TAS_LORAWAN_LINK_ADR_REQ);
|
||||||
|
}
|
||||||
|
else if (0x04 == FOpts[i]) { // Response from DutyCycleReq (DutyCycleAns)
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (0x05 == FOpts[i]) { // Response from RXParamSetupReq (RXParamSetupAns)
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (0x06 == FOpts[i]) { // Response from DevStatusReq (DevStatusAns)
|
||||||
|
i++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (0x07 == FOpts[i]) { // Response from NewChannelReq (NewChannelAns)
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (0x08 == FOpts[i]) { // Response from RXTimingSetupReq (RXTimingSetupAns)
|
||||||
|
}
|
||||||
|
else if (0x09 == FOpts[i]) { // Response from TXParamSetupReq (TXParamSetupAns)
|
||||||
|
}
|
||||||
|
else if (0x0A == FOpts[i]) { // Response from DIChannelReq (DIChannelAns)
|
||||||
|
}
|
||||||
|
else if (0x0D == FOpts[i]) { // Response from DeviceTimeReq (DeviceTimeAns)
|
||||||
|
// Used by the end-device to request the current GPS time
|
||||||
|
// Need to send epoch/fractional second
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// RFU
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload_len) {
|
||||||
|
if (bitRead(LoraSettings.flags, TAS_LORAWAN_DECODE_ENABLED) &&
|
||||||
|
(0x00161600 == LoraSettings.end_node[node].DevEUIh)) { // MerryIoT
|
||||||
|
if (120 == FPort) { // MerryIoT door/window Sensor (DW10)
|
||||||
|
if (9 == payload_len) { // MerryIoT Sensor state
|
||||||
|
// 1 2 3 4 5 6 7 8 9
|
||||||
|
// 03 0F 19 2C 8A00 040000 - button
|
||||||
|
// 00 0F 19 2C 0000 050000 - door
|
||||||
|
uint8_t status = payload_decrypted[0];
|
||||||
|
float battery_volt = (float)(21 + payload_decrypted[1]) / 10.0;
|
||||||
|
int temperature = payload_decrypted[2];
|
||||||
|
int humidity = payload_decrypted[3];
|
||||||
|
uint32_t elapsed_time = payload_decrypted[4] | (payload_decrypted[5] << 8);
|
||||||
|
uint32_t events = payload_decrypted[6] | (payload_decrypted[7] << 8) | (payload_decrypted[8] << 16);
|
||||||
|
#ifdef USE_LORA_DEBUG
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Node %d, DevEUI %08X%08X, Events %d, LastEvent %d min, DoorOpen %d, Button %d, Tamper %d, Tilt %d, Battery %1_fV, Temp %d, Hum %d"),
|
||||||
|
node +1, LoraSettings.end_node[node].DevEUIh, LoraSettings.end_node[node].DevEUIl,
|
||||||
|
events, elapsed_time,
|
||||||
|
bitRead(status, 0), bitRead(status, 1), bitRead(status, 2), bitRead(status, 3),
|
||||||
|
&battery_volt,
|
||||||
|
temperature, humidity);
|
||||||
|
#endif // USE_LORA_DEBUG
|
||||||
|
LoraWanPublishHeader(node);
|
||||||
|
ResponseAppend_P(PSTR(",\"Events\":%d,\"LastEvent\":%d,\"DoorOpen\":%d,\"Button\":%d,\"Tamper\":%d,\"Tilt\":%d"
|
||||||
|
",\"Battery\":%1_f,"),
|
||||||
|
events, elapsed_time,
|
||||||
|
bitRead(status, 0), bitRead(status, 1), bitRead(status, 2), bitRead(status, 3),
|
||||||
|
&battery_volt);
|
||||||
|
ResponseAppendTHD(temperature, humidity);
|
||||||
|
ResponseAppend_P(PSTR("}"));
|
||||||
|
LoraWanPublishFooter(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (bitRead(LoraSettings.flags, TAS_LORAWAN_DECODE_ENABLED) &&
|
||||||
|
(0xA840410E == LoraSettings.end_node[node].DevEUIh)) { // Dragino
|
||||||
|
// Dragino v1.7 fails to set DR with ADR so set it using serial interface:
|
||||||
|
// Password 123456
|
||||||
|
// AT+CHS=868100000
|
||||||
|
// Start join using reset button
|
||||||
|
// AT+CADR=0
|
||||||
|
// AT+CDATARATE=3
|
||||||
|
bitSet(LoraSettings.end_node[node].flags, TAS_LORAWAN_LINK_ADR_REQ);
|
||||||
|
if (10 == FPort) { // Dragino LDS02
|
||||||
|
// 8CD2 01 000010 000000 00 - Door Open, 3.282V
|
||||||
|
// 0CD2 01 000011 000000 00 - Door Closed
|
||||||
|
uint8_t status = payload_decrypted[0];
|
||||||
|
float battery_volt = (float)((payload_decrypted[1] | (payload_decrypted[0] << 8)) &0x3FFF) / 1000;
|
||||||
|
uint8_t MOD = payload_decrypted[2]; // Always 0x01
|
||||||
|
uint32_t events = payload_decrypted[5] | (payload_decrypted[4] << 8) | (payload_decrypted[3] << 16);
|
||||||
|
uint32_t open_duration = payload_decrypted[8] | (payload_decrypted[7] << 8) | (payload_decrypted[6] << 16);
|
||||||
|
uint8_t alarm = payload_decrypted[9];
|
||||||
|
#ifdef USE_LORA_DEBUG
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("LOR: Node %d, DevEUI %08X%08X, Events %d, LastEvent %d min, DoorOpen %d, Battery %3_fV, Alarm %d"),
|
||||||
|
node +1, LoraSettings.end_node[node].DevEUIh, LoraSettings.end_node[node].DevEUIl,
|
||||||
|
events, open_duration,
|
||||||
|
bitRead(status, 7),
|
||||||
|
&battery_volt,
|
||||||
|
bitRead(alarm, 0));
|
||||||
|
#endif // USE_LORA_DEBUG
|
||||||
|
LoraWanPublishHeader(node);
|
||||||
|
ResponseAppend_P(PSTR(",\"Events\":%d,\"LastEvent\":%d,\"DoorOpen\":%d,\"Alarm\":%d,\"Battery\":%3_f}"),
|
||||||
|
events, open_duration, bitRead(status, 7), bitRead(alarm, 0), &battery_volt);
|
||||||
|
LoraWanPublishFooter(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Joined device without decoding
|
||||||
|
LoraWanPublishHeader(node);
|
||||||
|
ResponseAppend_P(PSTR(",\"DevEUIh\":\"%08X\",\"DevEUIl\":\"%08X\",\"FPort\":%d,\"Payload\":["),
|
||||||
|
LoraSettings.end_node[node].DevEUIh, LoraSettings.end_node[node].DevEUIl, FPort);
|
||||||
|
for (uint32_t i = 0; i < payload_len; i++) {
|
||||||
|
ResponseAppend_P(PSTR("%s%d"), (0==i)?"":",", payload_decrypted[i]);
|
||||||
|
}
|
||||||
|
ResponseAppend_P(PSTR("]}"));
|
||||||
|
LoraWanPublishFooter(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (4 == MType) { // Confirmed data uplink
|
||||||
|
data[0] = 0x60; // Unconfirmed data downlink
|
||||||
|
data[5] |= 0x20; // FCtrl Set ACK bit
|
||||||
|
uint16_t FCnt = LoraSettings.end_node[node].FCntDown++;
|
||||||
|
data[6] = FCnt;
|
||||||
|
data[7] = FCnt >> 8;
|
||||||
|
uint32_t MIC = LoraWanComputeLegacyDownlinkMIC(NwkSKey, DevAddr, FCnt, data, packet_size -4);
|
||||||
|
data[packet_size -4] = MIC;
|
||||||
|
data[packet_size -3] = MIC >> 8;
|
||||||
|
data[packet_size -2] = MIC >> 16;
|
||||||
|
data[packet_size -1] = MIC >> 24;
|
||||||
|
LoraWanSendResponse(data, packet_size, TAS_LORAWAN_RECEIVE_DELAY1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (2 == MType) { // Unconfirmed data uplink
|
||||||
|
if (!bitRead(LoraSettings.end_node[node].flags, TAS_LORAWAN_LINK_ADR_REQ) &&
|
||||||
|
FCtrl_ADR && !FCtrl_ACK) {
|
||||||
|
// Try to fix single channel and datarate
|
||||||
|
LoraWanSendLinkADRReq(node); // Resend LinkADRReq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Lora.receive_time = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Commands
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
#define D_CMND_LORAWANBRIDGE "Bridge"
|
||||||
|
#define D_CMND_LORAWANAPPKEY "AppKey"
|
||||||
|
#define D_CMND_LORAWANNAME "Name"
|
||||||
|
|
||||||
|
const char kLoraWanCommands[] PROGMEM = "LoRaWan|" // Prefix
|
||||||
|
D_CMND_LORAWANBRIDGE "|" D_CMND_LORAWANAPPKEY "|" D_CMND_LORAWANNAME;
|
||||||
|
|
||||||
|
void (* const LoraWanCommand[])(void) PROGMEM = {
|
||||||
|
&CmndLoraWanBridge, &CmndLoraWanAppKey, &CmndLoraWanName };
|
||||||
|
|
||||||
|
void CmndLoraWanBridge(void) {
|
||||||
|
// LoraWanBridge - Show LoraOption1
|
||||||
|
// LoraWanBridge 1 - Set LoraOption1 1 = Enable LoraWanBridge
|
||||||
|
uint32_t pindex = 0;
|
||||||
|
if (XdrvMailbox.payload >= 0) {
|
||||||
|
bitWrite(LoraSettings.flags, pindex, XdrvMailbox.payload);
|
||||||
|
}
|
||||||
|
#ifdef USE_LORAWAN_TEST
|
||||||
|
LoraWanTestKeyDerivation();
|
||||||
|
LorWanTestUplinkDownlinkEncryption();
|
||||||
|
LorWanTestUplinkDownlinkMIC();
|
||||||
|
#endif // USE_LORAWAN_TEST
|
||||||
|
ResponseCmndChar(GetStateText(bitRead(LoraSettings.flags, pindex)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CmndLoraWanAppKey(void) {
|
||||||
|
// LoraWanAppKey
|
||||||
|
// LoraWanAppKey2 0123456789abcdef0123456789abcdef
|
||||||
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TAS_LORAWAN_ENDNODES)) {
|
||||||
|
uint32_t node = XdrvMailbox.index -1;
|
||||||
|
if (32 == XdrvMailbox.data_len) {
|
||||||
|
size_t out_len = 16;
|
||||||
|
HexToBytes(XdrvMailbox.data, LoraSettings.end_node[node].AppKey, &out_len);
|
||||||
|
if (0 == LoraSettings.end_node[node].name.length()) {
|
||||||
|
LoraSettings.end_node[node].name = F("0x0000");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (0 == XdrvMailbox.payload) {
|
||||||
|
memset(&LoraSettings.end_node[node], 0, sizeof(tEndNode));
|
||||||
|
}
|
||||||
|
char appkey[33];
|
||||||
|
ext_snprintf_P(appkey, sizeof(appkey), PSTR("%16_H"), LoraSettings.end_node[node].AppKey);
|
||||||
|
ResponseCmndIdxChar(appkey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CmndLoraWanName(void) {
|
||||||
|
// LoraWanName
|
||||||
|
// LoraWanName 1 - Set to short DevEUI (or 0x0000 if not yet joined)
|
||||||
|
// LoraWanName2 LDS02a
|
||||||
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TAS_LORAWAN_ENDNODES)) {
|
||||||
|
uint32_t node = XdrvMailbox.index -1;
|
||||||
|
if (XdrvMailbox.data_len) {
|
||||||
|
if (1 == XdrvMailbox.payload) {
|
||||||
|
char name[10];
|
||||||
|
ext_snprintf_P(name, sizeof(name), PSTR("0x%04X"), LoraSettings.end_node[node].DevEUIl & 0x0000FFFF);
|
||||||
|
LoraSettings.end_node[node].name = name;
|
||||||
|
} else {
|
||||||
|
LoraSettings.end_node[node].name = XdrvMailbox.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ResponseCmndIdxChar(LoraSettings.end_node[node].name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Interface
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
void LoraWanInit(void) {
|
||||||
|
// The Things Network has been assigned a 7-bits "device address prefix" a.k.a. NwkID
|
||||||
|
// %0010011. Using that, TTN currently sends NetID 0x000013, and a TTN DevAddr always
|
||||||
|
// starts with 0x26 or 0x27
|
||||||
|
// Private networks are supposed to used NetID 0x000000.
|
||||||
|
Lorawan.device_address = (TAS_LORAWAN_NETID << 25) | (ESP_getChipId() & 0x01FFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_LORAWAN_BRIDGE
|
||||||
|
#endif // USE_SPI_LORA
|
|
@ -16,15 +16,196 @@
|
||||||
|
|
||||||
/*********************************************************************************************/
|
/*********************************************************************************************/
|
||||||
|
|
||||||
|
void LoraDefaults(void) {
|
||||||
|
LoraSettings.frequency = TAS_LORA_FREQUENCY;
|
||||||
|
LoraSettings.bandwidth = TAS_LORA_BANDWIDTH;
|
||||||
|
LoraSettings.spreading_factor = TAS_LORA_SPREADING_FACTOR;
|
||||||
|
LoraSettings.coding_rate = TAS_LORA_CODING_RATE;
|
||||||
|
LoraSettings.sync_word = TAS_LORA_SYNC_WORD;
|
||||||
|
LoraSettings.output_power = TAS_LORA_OUTPUT_POWER;
|
||||||
|
LoraSettings.preamble_length = TAS_LORA_PREAMBLE_LENGTH;
|
||||||
|
LoraSettings.current_limit = TAS_LORA_CURRENT_LIMIT;
|
||||||
|
LoraSettings.implicit_header = TAS_LORA_HEADER;
|
||||||
|
LoraSettings.crc_bytes = TAS_LORA_CRC_BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraWanDefaults(void) {
|
||||||
|
LoraSettings.frequency = TAS_LORAWAN_FREQUENCY;
|
||||||
|
LoraSettings.bandwidth = TAS_LORAWAN_BANDWIDTH;
|
||||||
|
LoraSettings.spreading_factor = TAS_LORAWAN_SPREADING_FACTOR;
|
||||||
|
LoraSettings.coding_rate = TAS_LORAWAN_CODING_RATE;
|
||||||
|
LoraSettings.sync_word = TAS_LORAWAN_SYNC_WORD;
|
||||||
|
LoraSettings.output_power = TAS_LORAWAN_OUTPUT_POWER;
|
||||||
|
LoraSettings.preamble_length = TAS_LORAWAN_PREAMBLE_LENGTH;
|
||||||
|
LoraSettings.current_limit = TAS_LORAWAN_CURRENT_LIMIT;
|
||||||
|
LoraSettings.implicit_header = TAS_LORAWAN_HEADER;
|
||||||
|
LoraSettings.crc_bytes = TAS_LORAWAN_CRC_BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraSettings2Json(void) {
|
||||||
|
ResponseAppend_P(PSTR("\"" D_JSON_FREQUENCY "\":%1_f"), &LoraSettings.frequency); // xxx.x MHz
|
||||||
|
ResponseAppend_P(PSTR(",\"" D_JSON_BANDWIDTH "\":%1_f"), &LoraSettings.bandwidth); // xxx.x kHz
|
||||||
|
ResponseAppend_P(PSTR(",\"" D_JSON_SPREADING_FACTOR "\":%d"), LoraSettings.spreading_factor);
|
||||||
|
ResponseAppend_P(PSTR(",\"" D_JSON_CODINGRATE4 "\":%d"), LoraSettings.coding_rate);
|
||||||
|
ResponseAppend_P(PSTR(",\"" D_JSON_SYNCWORD "\":%d"), LoraSettings.sync_word);
|
||||||
|
ResponseAppend_P(PSTR(",\"" D_JSON_OUTPUT_POWER "\":%d"), LoraSettings.output_power); // dBm
|
||||||
|
ResponseAppend_P(PSTR(",\"" D_JSON_PREAMBLE_LENGTH "\":%d"), LoraSettings.preamble_length); // symbols
|
||||||
|
ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT_LIMIT "\":%1_f"), &LoraSettings.current_limit); // xx.x mA (Overcurrent Protection - OCP)
|
||||||
|
ResponseAppend_P(PSTR(",\"" D_JSON_IMPLICIT_HEADER "\":%d"), LoraSettings.implicit_header); // 0 = explicit
|
||||||
|
ResponseAppend_P(PSTR(",\"" D_JSON_CRC_BYTES "\":%d"), LoraSettings.crc_bytes); // bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraJson2Settings(JsonParserObject root) {
|
||||||
|
LoraSettings.frequency = root.getFloat(PSTR(D_JSON_FREQUENCY), LoraSettings.frequency);
|
||||||
|
LoraSettings.bandwidth = root.getFloat(PSTR(D_JSON_BANDWIDTH), LoraSettings.bandwidth);
|
||||||
|
LoraSettings.spreading_factor = root.getUInt(PSTR(D_JSON_SPREADING_FACTOR), LoraSettings.spreading_factor);
|
||||||
|
LoraSettings.coding_rate = root.getUInt(PSTR(D_JSON_CODINGRATE4), LoraSettings.coding_rate);
|
||||||
|
LoraSettings.sync_word = root.getUInt(PSTR(D_JSON_SYNCWORD), LoraSettings.sync_word);
|
||||||
|
LoraSettings.output_power = root.getUInt(PSTR(D_JSON_OUTPUT_POWER), LoraSettings.output_power);
|
||||||
|
LoraSettings.preamble_length = root.getUInt(PSTR(D_JSON_PREAMBLE_LENGTH), LoraSettings.preamble_length);
|
||||||
|
LoraSettings.current_limit = root.getFloat(PSTR(D_JSON_CURRENT_LIMIT), LoraSettings.current_limit);
|
||||||
|
LoraSettings.implicit_header = root.getUInt(PSTR(D_JSON_IMPLICIT_HEADER), LoraSettings.implicit_header);
|
||||||
|
LoraSettings.crc_bytes = root.getUInt(PSTR(D_JSON_CRC_BYTES), LoraSettings.crc_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Driver Settings load and save
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
#ifdef USE_UFILESYS
|
||||||
|
#define XDRV_73_KEY "drvset73"
|
||||||
|
|
||||||
|
bool LoraLoadData(void) {
|
||||||
|
char key[] = XDRV_73_KEY;
|
||||||
|
String json = UfsJsonSettingsRead(key);
|
||||||
|
if (json.length() == 0) { return false; }
|
||||||
|
|
||||||
|
// {"Crc":1882268982,"Flags":0,"Frequency":868.1,"Bandwidth":125.0,"SpreadingFactor":7,"CodingRate4":5,"SyncWord":52,"OutputPower":10,"PreambleLength":8,"CurrentLimit":60.0,"ImplicitHeader":0,"CrcBytes":2}
|
||||||
|
JsonParser parser((char*)json.c_str());
|
||||||
|
JsonParserObject root = parser.getRootObject();
|
||||||
|
if (!root) { return false; }
|
||||||
|
|
||||||
|
LoraSettings.crc32 = root.getUInt(PSTR("Crc"), LoraSettings.crc32);
|
||||||
|
LoraSettings.flags = root.getUInt(PSTR("Flags"), LoraSettings.flags);
|
||||||
|
LoraJson2Settings(root);
|
||||||
|
|
||||||
|
#ifdef USE_LORAWAN_BRIDGE
|
||||||
|
if (!LoraWanLoadData()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif // USE_LORAWAN_BRIDGE
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoraSaveData(void) {
|
||||||
|
Response_P(PSTR("{\"" XDRV_73_KEY "\":{"
|
||||||
|
"\"Crc\":%u,"
|
||||||
|
"\"Flags\":%u,"),
|
||||||
|
LoraSettings.crc32,
|
||||||
|
LoraSettings.flags);
|
||||||
|
LoraSettings2Json();
|
||||||
|
ResponseAppend_P(PSTR("}}"));
|
||||||
|
|
||||||
|
if (!UfsJsonSettingsWrite(ResponseData())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#ifdef USE_LORAWAN_BRIDGE
|
||||||
|
if (!LoraWanSaveData()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif // USE_LORAWAN_BRIDGE
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraDeleteData(void) {
|
||||||
|
char key[] = XDRV_73_KEY;
|
||||||
|
UfsJsonSettingsDelete(key); // Use defaults
|
||||||
|
#ifdef USE_LORAWAN_BRIDGE
|
||||||
|
LoraWanDeleteData();
|
||||||
|
#endif // USE_LORAWAN_BRIDGE
|
||||||
|
}
|
||||||
|
#endif // USE_UFILESYS
|
||||||
|
|
||||||
|
/*********************************************************************************************/
|
||||||
|
|
||||||
|
void LoraSettingsLoad(bool erase) {
|
||||||
|
// Called from FUNC_PRE_INIT (erase = 0) once at restart
|
||||||
|
// Called from FUNC_RESET_SETTINGS (erase = 1) after command reset 4, 5, or 6
|
||||||
|
|
||||||
|
// *** Start init default values in case key is not found ***
|
||||||
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DRV: " D_USE_DEFAULTS));
|
||||||
|
|
||||||
|
memset(&LoraSettings, 0x00, sizeof(LoraSettings));
|
||||||
|
// Init any other parameter in struct LoraSettings
|
||||||
|
LoraDefaults();
|
||||||
|
// *** End Init default values ***
|
||||||
|
|
||||||
|
#ifndef USE_UFILESYS
|
||||||
|
AddLog(LOG_LEVEL_INFO, PSTR("CFG: Lora use defaults as file system not enabled"));
|
||||||
|
#else
|
||||||
|
// Try to load key
|
||||||
|
if (erase) {
|
||||||
|
LoraDeleteData();
|
||||||
|
}
|
||||||
|
else if (LoraLoadData()) {
|
||||||
|
AddLog(LOG_LEVEL_INFO, PSTR("CFG: Lora loaded from file"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// File system not ready: No flash space reserved for file system
|
||||||
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("CFG: Lora use defaults as file system not ready or key not found"));
|
||||||
|
}
|
||||||
|
#endif // USE_UFILESYS
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraSettingsSave(void) {
|
||||||
|
// Called from FUNC_SAVE_SETTINGS every SaveData second and at restart
|
||||||
|
#ifdef USE_UFILESYS
|
||||||
|
uint32_t crc32 = GetCfgCrc32((uint8_t*)&LoraSettings +4, sizeof(LoraSettings) -4); // Skip crc32
|
||||||
|
if (crc32 != LoraSettings.crc32) {
|
||||||
|
// Try to save file /.drvset122
|
||||||
|
LoraSettings.crc32 = crc32;
|
||||||
|
|
||||||
|
if (LoraSaveData()) {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: Lora saved to file"));
|
||||||
|
} else {
|
||||||
|
// File system not ready: No flash space reserved for file system
|
||||||
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("CFG: Lora ERROR File system not ready or unable to save file"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // USE_UFILESYS
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************/
|
||||||
|
|
||||||
|
bool LoraSend(uint8_t* data, uint32_t len, bool invert) {
|
||||||
|
uint32_t lora_time = millis(); // Time is important for LoRaWan RX windows
|
||||||
|
bool result = Lora.Send(data, len, invert);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LOR: Send (%u) '%*_H', Invert %d, Time %d"),
|
||||||
|
lora_time, len, data, invert, TimePassedSince(lora_time));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void LoraInput(void) {
|
void LoraInput(void) {
|
||||||
if (!Lora.Available()) { return; }
|
if (!Lora.Available()) { return; }
|
||||||
|
|
||||||
char data[LORA_MAX_PACKET_LENGTH] = { 0 };
|
char data[TAS_LORA_MAX_PACKET_LENGTH] = { 0 };
|
||||||
int packet_size = Lora.Receive(data);
|
int packet_size = Lora.Receive(data);
|
||||||
if (!packet_size) { return; }
|
if (!packet_size) { return; }
|
||||||
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LOR: Rcvd (%u) '%*_H', RSSI %1_f, SNR %1_f"),
|
||||||
|
Lora.receive_time, packet_size, data, &Lora.rssi, &Lora.snr);
|
||||||
|
|
||||||
|
#ifdef USE_LORAWAN_BRIDGE
|
||||||
|
if (bitRead(LoraSettings.flags, TAS_LORAWAN_BRIDGE_ENABLED)) {
|
||||||
|
if (LoraWanInput((uint8_t*)data, packet_size)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // USE_LORAWAN_BRIDGE
|
||||||
|
Lora.receive_time = 0;
|
||||||
|
|
||||||
if (TAS_LORA_REMOTE_COMMAND == data[0]) {
|
if (TAS_LORA_REMOTE_COMMAND == data[0]) {
|
||||||
char *payload = data +1; // Skip TAS_LORA_REMOTE_COMMAND
|
char *payload = data +1; // Skip TAS_LORA_REMOTE_COMMAND
|
||||||
char *command_part;
|
char *command_part;
|
||||||
char *topic_part = strtok_r(payload, " ", &command_part);
|
char *topic_part = strtok_r(payload, " ", &command_part);
|
||||||
if (topic_part && command_part) {
|
if (topic_part && command_part) {
|
||||||
|
@ -32,7 +213,7 @@ void LoraInput(void) {
|
||||||
ExecuteCommand(command_part, SRC_REMOTE);
|
ExecuteCommand(command_part, SRC_REMOTE);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
*--command_part = ' '; // Restore strtok_r '/0'
|
*--command_part = ' '; // Restore strtok_r '/0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,19 +244,6 @@ void LoraInput(void) {
|
||||||
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR("LoRaReceived"));
|
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR("LoRaReceived"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoraDefaults(void) {
|
|
||||||
Lora.frequency = TAS_LORA_FREQUENCY;
|
|
||||||
Lora.bandwidth = TAS_LORA_BANDWIDTH;
|
|
||||||
Lora.spreading_factor = TAS_LORA_SPREADING_FACTOR;
|
|
||||||
Lora.coding_rate = TAS_LORA_CODING_RATE;
|
|
||||||
Lora.sync_word = TAS_LORA_SYNC_WORD;
|
|
||||||
Lora.output_power = TAS_LORA_OUTPUT_POWER;
|
|
||||||
Lora.preamble_length = TAS_LORA_PREAMBLE_LENGTH;
|
|
||||||
Lora.current_limit = TAS_LORA_CURRENT_LIMIT;
|
|
||||||
Lora.implicit_header = TAS_LORA_HEADER;
|
|
||||||
Lora.crc_bytes = TAS_LORA_CRC_BYTES;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoraInit(void) {
|
void LoraInit(void) {
|
||||||
if ((SPI_MOSI_MISO == TasmotaGlobal.spi_enabled) &&
|
if ((SPI_MOSI_MISO == TasmotaGlobal.spi_enabled) &&
|
||||||
(PinUsed(GPIO_LORA_CS)) && (PinUsed(GPIO_LORA_RST))) {
|
(PinUsed(GPIO_LORA_CS)) && (PinUsed(GPIO_LORA_RST))) {
|
||||||
|
@ -86,8 +254,10 @@ void LoraInit(void) {
|
||||||
SPI.begin(Pin(GPIO_SPI_CLK), Pin(GPIO_SPI_MISO), Pin(GPIO_SPI_MOSI), -1);
|
SPI.begin(Pin(GPIO_SPI_CLK), Pin(GPIO_SPI_MISO), Pin(GPIO_SPI_MOSI), -1);
|
||||||
#endif // ESP32
|
#endif // ESP32
|
||||||
|
|
||||||
Lora.enableInterrupt = true;
|
#ifdef USE_LORAWAN_BRIDGE
|
||||||
LoraDefaults();
|
LoraWanInit();
|
||||||
|
#endif // USE_LORAWAN_BRIDGE
|
||||||
|
LoraSettingsLoad(0);
|
||||||
|
|
||||||
char hardware[20];
|
char hardware[20];
|
||||||
strcpy_P(hardware, PSTR("Not"));
|
strcpy_P(hardware, PSTR("Not"));
|
||||||
|
@ -127,27 +297,42 @@ void LoraInit(void) {
|
||||||
* Commands
|
* Commands
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
#define D_CMND_LORASEND "Send"
|
#define D_CMND_LORASEND "Send"
|
||||||
#define D_CMND_LORACONFIG "Config"
|
#define D_CMND_LORACONFIG "Config"
|
||||||
#define D_CMND_LORACOMMAND "Command"
|
#define D_CMND_LORACOMMAND "Command"
|
||||||
|
#define D_CMND_LORAOPTION "Option"
|
||||||
|
|
||||||
const char kLoraCommands[] PROGMEM = "LoRa|" // Prefix
|
const char kLoraCommands[] PROGMEM = "LoRa|" // Prefix
|
||||||
D_CMND_LORASEND "|" D_CMND_LORACONFIG "|" D_CMND_LORACOMMAND ;
|
D_CMND_LORASEND "|" D_CMND_LORACONFIG "|" D_CMND_LORACOMMAND "|" D_CMND_LORAOPTION;
|
||||||
|
|
||||||
void (* const LoraCommand[])(void) PROGMEM = {
|
void (* const LoraCommand[])(void) PROGMEM = {
|
||||||
&CmndLoraSend, &CmndLoraConfig, &CmndLoraCommand };
|
&CmndLoraSend, &CmndLoraConfig, &CmndLoraCommand, &CmndLoraOption };
|
||||||
|
|
||||||
|
void CmndLoraOption(void) {
|
||||||
|
// LoraOption1 1 - Enable LoRaWanBridge
|
||||||
|
// LoraOption2 1 - Enable LoRaWanBridge Join
|
||||||
|
// LoraOption3 1 - Enable LoRaWanBridge decoding
|
||||||
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 8)) {
|
||||||
|
uint32_t pindex = XdrvMailbox.index -1;
|
||||||
|
if (XdrvMailbox.payload >= 0) {
|
||||||
|
bitWrite(LoraSettings.flags, pindex, XdrvMailbox.payload);
|
||||||
|
}
|
||||||
|
ResponseCmndIdxChar(GetStateText(bitRead(LoraSettings.flags, pindex)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CmndLoraCommand(void) {
|
void CmndLoraCommand(void) {
|
||||||
// LoRaCommand <topic_of_lora_receiver> <command>
|
// LoRaCommand <topic_of_lora_receiver> <command>
|
||||||
// LoRaCommand lorareceiver power 2
|
// LoRaCommand lorareceiver power 2
|
||||||
// LoRaCommand lorareceiver publish cmnd/anytopic/power 2
|
// LoRaCommand lorareceiver publish cmnd/anytopic/power 2
|
||||||
|
// LoRaCommand lorareceiver LoRaCommand thisreceiver status
|
||||||
if (XdrvMailbox.data_len > 0) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
char data[LORA_MAX_PACKET_LENGTH] = { 0 };
|
char data[TAS_LORA_MAX_PACKET_LENGTH] = { 0 };
|
||||||
XdrvMailbox.data_len++; // Add Signal CmndLoraCommand to lora receiver
|
XdrvMailbox.data_len++; // Add Signal CmndLoraCommand to lora receiver
|
||||||
uint32_t len = (XdrvMailbox.data_len < LORA_MAX_PACKET_LENGTH -1) ? XdrvMailbox.data_len : LORA_MAX_PACKET_LENGTH -2;
|
uint32_t len = (XdrvMailbox.data_len < TAS_LORA_MAX_PACKET_LENGTH -1) ? XdrvMailbox.data_len : TAS_LORA_MAX_PACKET_LENGTH -2;
|
||||||
data[0] = TAS_LORA_REMOTE_COMMAND;
|
data[0] = TAS_LORA_REMOTE_COMMAND;
|
||||||
strlcpy(data +1, XdrvMailbox.data, len);
|
strlcpy(data +1, XdrvMailbox.data, len);
|
||||||
Lora.Send((uint8_t*)data, len);
|
LoraSend((uint8_t*)data, len, false);
|
||||||
ResponseCmndDone();
|
ResponseCmndDone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,14 +347,19 @@ void CmndLoraSend(void) {
|
||||||
// LoRaSend4 "Hello Tiger" - Send "Hello Tiger" and set to binary decoding
|
// LoRaSend4 "Hello Tiger" - Send "Hello Tiger" and set to binary decoding
|
||||||
// LoRaSend5 "AA004566" - Send "AA004566" as hex values
|
// LoRaSend5 "AA004566" - Send "AA004566" as hex values
|
||||||
// LoRaSend6 "72,101,108,108" - Send decimals as hex values
|
// LoRaSend6 "72,101,108,108" - Send decimals as hex values
|
||||||
// if (XdrvMailbox.index > 9) { XdrvMailbox.index -= 10; } // Allows leading spaces (not supported - See support_command/CommandHandler)
|
// LoRaSend15 "AA004566" - Send "AA004566" as hex values with invert IQ
|
||||||
|
bool invert = false;
|
||||||
|
if (XdrvMailbox.index > 9) {
|
||||||
|
XdrvMailbox.index -= 10;
|
||||||
|
invert = true;
|
||||||
|
}
|
||||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) {
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) {
|
||||||
Lora.raw = (XdrvMailbox.index > 3); // Global flag set even without data
|
Lora.raw = (XdrvMailbox.index > 3); // Global flag set even without data
|
||||||
if (XdrvMailbox.data_len > 0) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
char data[LORA_MAX_PACKET_LENGTH] = { 0 };
|
char data[TAS_LORA_MAX_PACKET_LENGTH] = { 0 };
|
||||||
uint32_t len = (XdrvMailbox.data_len < LORA_MAX_PACKET_LENGTH -1) ? XdrvMailbox.data_len : LORA_MAX_PACKET_LENGTH -2;
|
uint32_t len = (XdrvMailbox.data_len < TAS_LORA_MAX_PACKET_LENGTH -1) ? XdrvMailbox.data_len : TAS_LORA_MAX_PACKET_LENGTH -2;
|
||||||
#ifdef USE_LORA_DEBUG
|
#ifdef USE_LORA_DEBUG
|
||||||
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Len %d, Send %*_H"), len, len + 2, XdrvMailbox.data);
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Len %d, Send %*_H"), len, len, XdrvMailbox.data);
|
||||||
#endif
|
#endif
|
||||||
if (1 == XdrvMailbox.index) { // "Hello Tiger\n"
|
if (1 == XdrvMailbox.index) { // "Hello Tiger\n"
|
||||||
memcpy(data, XdrvMailbox.data, len);
|
memcpy(data, XdrvMailbox.data, len);
|
||||||
|
@ -193,7 +383,7 @@ void CmndLoraSend(void) {
|
||||||
while (size > 1) {
|
while (size > 1) {
|
||||||
strlcpy(stemp, codes, sizeof(stemp));
|
strlcpy(stemp, codes, sizeof(stemp));
|
||||||
data[len++] = strtol(stemp, &p, 16);
|
data[len++] = strtol(stemp, &p, 16);
|
||||||
if (len > LORA_MAX_PACKET_LENGTH -2) { break; }
|
if (len > TAS_LORA_MAX_PACKET_LENGTH -2) { break; }
|
||||||
size -= 2;
|
size -= 2;
|
||||||
codes += 2;
|
codes += 2;
|
||||||
}
|
}
|
||||||
|
@ -205,14 +395,14 @@ void CmndLoraSend(void) {
|
||||||
len = 0;
|
len = 0;
|
||||||
for (char* str = strtok_r(values, ",", &p); str; str = strtok_r(nullptr, ",", &p)) {
|
for (char* str = strtok_r(values, ",", &p); str; str = strtok_r(nullptr, ",", &p)) {
|
||||||
data[len++] = (uint8_t)atoi(str);
|
data[len++] = (uint8_t)atoi(str);
|
||||||
if (len > LORA_MAX_PACKET_LENGTH -2) { break; }
|
if (len > TAS_LORA_MAX_PACKET_LENGTH -2) { break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
len = 0;
|
len = 0;
|
||||||
}
|
}
|
||||||
if (len) {
|
if (len) {
|
||||||
Lora.Send((uint8_t*)data, len);
|
LoraSend((uint8_t*)data, len, invert);
|
||||||
}
|
}
|
||||||
ResponseCmndDone();
|
ResponseCmndDone();
|
||||||
}
|
}
|
||||||
|
@ -222,41 +412,31 @@ void CmndLoraSend(void) {
|
||||||
void CmndLoraConfig(void) {
|
void CmndLoraConfig(void) {
|
||||||
// LoRaConfig - Show all parameters
|
// LoRaConfig - Show all parameters
|
||||||
// LoRaConfig 1 - Set default parameters
|
// LoRaConfig 1 - Set default parameters
|
||||||
|
// LoRaConfig 2 - Set default LoRaWan bridge parameters
|
||||||
// LoRaConfig {"Frequency":868.0,"Bandwidth":125.0} - Enter float parameters
|
// LoRaConfig {"Frequency":868.0,"Bandwidth":125.0} - Enter float parameters
|
||||||
// LoRaConfig {"SyncWord":18} - Enter decimal parameter (=0x12)
|
// LoRaConfig {"SyncWord":18} - Enter decimal parameter (=0x12)
|
||||||
if (XdrvMailbox.data_len > 0) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
if (XdrvMailbox.payload == 1) {
|
if (XdrvMailbox.payload == 1) {
|
||||||
LoraDefaults();
|
LoraDefaults();
|
||||||
Lora.Config();
|
Lora.Config();
|
||||||
} else {
|
}
|
||||||
|
else if (XdrvMailbox.payload == 2) {
|
||||||
|
LoraWanDefaults();
|
||||||
|
Lora.Config();
|
||||||
|
}
|
||||||
|
else {
|
||||||
JsonParser parser(XdrvMailbox.data);
|
JsonParser parser(XdrvMailbox.data);
|
||||||
JsonParserObject root = parser.getRootObject();
|
JsonParserObject root = parser.getRootObject();
|
||||||
if (root) {
|
if (root) {
|
||||||
Lora.frequency = root.getFloat(PSTR(D_JSON_FREQUENCY), Lora.frequency);
|
LoraJson2Settings(root);
|
||||||
Lora.bandwidth = root.getFloat(PSTR(D_JSON_BANDWIDTH), Lora.bandwidth);
|
|
||||||
Lora.spreading_factor = root.getUInt(PSTR(D_JSON_SPREADING_FACTOR), Lora.spreading_factor);
|
|
||||||
Lora.coding_rate = root.getUInt(PSTR(D_JSON_CODINGRATE4), Lora.coding_rate);
|
|
||||||
Lora.sync_word = root.getUInt(PSTR(D_JSON_SYNCWORD), Lora.sync_word);
|
|
||||||
Lora.output_power = root.getUInt(PSTR(D_JSON_OUTPUT_POWER), Lora.output_power);
|
|
||||||
Lora.preamble_length = root.getUInt(PSTR(D_JSON_PREAMBLE_LENGTH), Lora.preamble_length);
|
|
||||||
Lora.current_limit = root.getFloat(PSTR(D_JSON_CURRENT_LIMIT), Lora.current_limit);
|
|
||||||
Lora.implicit_header = root.getUInt(PSTR(D_JSON_IMPLICIT_HEADER), Lora.implicit_header);
|
|
||||||
Lora.crc_bytes = root.getUInt(PSTR(D_JSON_CRC_BYTES), Lora.crc_bytes);
|
|
||||||
Lora.Config();
|
Lora.Config();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResponseCmnd(); // {"LoRaConfig":
|
ResponseCmnd(); // {"LoRaConfig":
|
||||||
ResponseAppend_P(PSTR("{\"" D_JSON_FREQUENCY "\":%1_f"), &Lora.frequency); // xxx.x MHz
|
ResponseAppend_P(PSTR("{"));
|
||||||
ResponseAppend_P(PSTR(",\"" D_JSON_BANDWIDTH "\":%1_f"), &Lora.bandwidth); // xxx.x kHz
|
LoraSettings2Json();
|
||||||
ResponseAppend_P(PSTR(",\"" D_JSON_SPREADING_FACTOR "\":%d"), Lora.spreading_factor);
|
ResponseAppend_P(PSTR("}}"));
|
||||||
ResponseAppend_P(PSTR(",\"" D_JSON_CODINGRATE4 "\":%d"), Lora.coding_rate);
|
|
||||||
ResponseAppend_P(PSTR(",\"" D_JSON_SYNCWORD "\":%d"), Lora.sync_word);
|
|
||||||
ResponseAppend_P(PSTR(",\"" D_JSON_OUTPUT_POWER "\":%d"), Lora.output_power); // dBm
|
|
||||||
ResponseAppend_P(PSTR(",\"" D_JSON_PREAMBLE_LENGTH "\":%d"), Lora.preamble_length); // symbols
|
|
||||||
ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT_LIMIT "\":%1_f"), &Lora.current_limit); // xx.x mA (Overcurrent Protection - OCP)
|
|
||||||
ResponseAppend_P(PSTR(",\"" D_JSON_IMPLICIT_HEADER "\":%d"), Lora.implicit_header); // 0 = explicit
|
|
||||||
ResponseAppend_P(PSTR(",\"" D_JSON_CRC_BYTES "\":%d}}"), Lora.crc_bytes); // bytes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
|
@ -275,8 +455,19 @@ bool Xdrv73(uint32_t function) {
|
||||||
case FUNC_SLEEP_LOOP:
|
case FUNC_SLEEP_LOOP:
|
||||||
LoraInput();
|
LoraInput();
|
||||||
break;
|
break;
|
||||||
|
case FUNC_RESET_SETTINGS:
|
||||||
|
LoraSettingsLoad(1);
|
||||||
|
break;
|
||||||
|
case FUNC_SAVE_SETTINGS:
|
||||||
|
LoraSettingsSave();
|
||||||
|
break;
|
||||||
case FUNC_COMMAND:
|
case FUNC_COMMAND:
|
||||||
result = DecodeCommand(kLoraCommands, LoraCommand);
|
result = DecodeCommand(kLoraCommands, LoraCommand);
|
||||||
|
#ifdef USE_LORAWAN_BRIDGE
|
||||||
|
if (!result) {
|
||||||
|
result = DecodeCommand(kLoraWanCommands, LoraWanCommand);
|
||||||
|
}
|
||||||
|
#endif // USE_LORAWAN_BRIDGE
|
||||||
break;
|
break;
|
||||||
case FUNC_ACTIVE:
|
case FUNC_ACTIVE:
|
||||||
result = true;
|
result = true;
|
||||||
|
|
Loading…
Reference in New Issue