diff --git a/include/include.h b/include/include.h index eb3928c..65060d9 100644 --- a/include/include.h +++ b/include/include.h @@ -36,8 +36,7 @@ #include "TimeLib.h" -#include "lmic.h" -#include "hal/hal.h" +#include #ifdef USE_LED #include "EasyLed.h" @@ -60,11 +59,11 @@ #include "power.h" #include "gps.h" #include "utils.h" -#include "lmic-node.h" +#include "radiolib-node.h" #include "http-helpers.h" #include "power.h" #include "parsers.h" -#include "lmic-helpers.h" +#include "radiolib-helpers.h" #include "mqtt.h" #endif diff --git a/include/lmic-node.h b/include/lmic-node.h deleted file mode 100644 index 0a3f9fd..0000000 --- a/include/lmic-node.h +++ /dev/null @@ -1,183 +0,0 @@ -/******************************************************************************* - * - * File: LMIC-node.h - * - * Function: LMIC-node main header file. - * - * Copyright: Copyright (c) 2021 Leonel Lopes Parente - * Portions Copyright (c) 2018 Terry Moore, MCCI - * - * Permission is hereby granted, free of charge, to anyone - * obtaining a copy of this document and accompanying files to do, - * whatever they want with them without any restriction, including, - * but not limited to, copying, modification and redistribution. - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY. - * - * License: MIT License. See accompanying LICENSE file. - * - * Author: Leonel Lopes Parente - * - ******************************************************************************/ - -#ifndef LMIC_NODE_H_ -#define LMIC_NODE_H_ - -#include "include.h" - -const dr_t DefaultABPDataRate = DR_SF7; -const s1_t DefaultABPTxPower = 14; - -extern bool boardInit(InitType initType); - -#ifdef USE_SERIAL -extern HardwareSerial &serial; -#endif - -#ifdef USE_DISPLAY -// Create U8x8 instance for SSD1306 OLED display (no reset) using hardware I2C. -extern U8X8_SSD1306_128X64_NONAME_HW_I2C display; -#endif - -// Forward declarations - -extern TaskHandle_t LmicTask; -void lmicTask(void *); - -void processDownlink(ostime_t eventTimestamp, uint8_t fPort, uint8_t *data, uint8_t dataLength); -void onLmicEvent(void *pUserData, ev_t ev); -void initLmic(void); -int16_t getSnrTenfold(void); -int16_t getRssi(int8_t snr); -lmic_tx_error_t scheduleUplink(uint8_t fPort, uint8_t *data, uint8_t dataLength, bool confirmed); -extern bool joined; - -#ifndef DO_WORK_INTERVAL_SECONDS // Should be set in platformio.ini -#define DO_WORK_INTERVAL_SECONDS 300 // Default 5 minutes if not set -#endif - -#define TIMESTAMP_WIDTH 12 // Number of columns to display eventtime (zero-padded) -#define MESSAGE_INDENT TIMESTAMP_WIDTH + 3 - -// Determine which LMIC library is used -#ifdef _LMIC_CONFIG_PRECONDITIONS_H_ -#define MCCI_LMIC -#else -#define CLASSIC_LMIC -#endif - -#if !defined(ABP_ACTIVATION) && !defined(OTAA_ACTIVATION) -#define OTAA_ACTIVATION -#endif - -enum class ActivationMode -{ - OTAA, - ABP -}; -#ifdef OTAA_ACTIVATION -const ActivationMode activationMode = ActivationMode::OTAA; -#else -const ActivationMode activationMode = ActivationMode::ABP; -#endif - -#if defined(ABP_ACTIVATION) && defined(OTAA_ACTIVATION) -#error Only one of ABP_ACTIVATION and OTAA_ACTIVATION can be defined. -#endif - -#if defined(ABP_ACTIVATION) && defined(ABP_DEVICEID) -const char deviceId[] = ABP_DEVICEID; -#elif defined(DEVICEID) -const char deviceId[] = DEVICEID; -#else -const char deviceId[] = DEVICEID_DEFAULT; -#endif - -// Allow WAITFOR_SERIAL_SECONDS to be defined in platformio.ini. -// If used it shall be defined in the [common] section. -// The common setting will only be used for boards that have -// WAITFOR_SERIAL_SECONDS_DEFAULT defined (in BSP) with a value != 0 -#if defined(WAITFOR_SERIAL_SECONDS_DEFAULT) && WAITFOR_SERIAL_SECONDS_DEFAULT != 0 -#ifdef WAITFOR_SERIAL_SECONDS -#define WAITFOR_SERIAL_S WAITFOR_SERIAL_SECONDS -#else -#define WAITFOR_SERIAL_S WAITFOR_SERIAL_SECONDS_DEFAULT -#endif -#else -#define WAITFOR_SERIAL_S 0 -#endif - -#if defined(ABP_ACTIVATION) && defined(CLASSIC_LMIC) -#error Do NOT use ABP activation when using the deprecated IBM LMIC framework library. \ - On The Things Network V3 this will cause a downlink message for EVERY uplink message \ - because it does properly handle MAC commands. -#endif - -#ifdef OTAA_ACTIVATION -#if !defined(ASCII_DEVEUI) || !defined(ASCII_APPEUI) || !defined(ASCII_APPKEY) -#error One or more LoRaWAN keys (ASCII_DEVEUI, ASCII_APPEUI, ASCII_APPKEY) are not defined. -#endif -#else -// ABP activation -#if !defined(ABP_DEVADDR) || !defined(ABP_NWKSKEY) || !defined(ABP_APPSKEY) -#error One or more LoRaWAN keys (ABP_DEVADDR, ABP_NWKSKEY, ABP_APPSKEY) are not defined. -#endif -#endif - -// Determine if a valid region is defined. -// This actually has little effect because -// CLASSIC LMIC: defines CFG_eu868 by default, -// MCCI LMIC: if no region is defined it -// sets CFG_eu868 as default. -#if ( \ - (defined(CLASSIC_LMIC) && !(defined(CFG_eu868) || defined(CFG_us915))) || \ - (defined(MCCI_LMIC) && !(defined(CFG_as923) || defined(CFG_as923jp) || defined(CFG_au915) || defined(CFG_eu868) || defined(CFG_in866) || defined(CFG_kr920) || defined(CFG_us915)))) -#Error No valid LoRaWAN region defined -#endif - -#ifndef MCCI_LMIC -#define LMIC_ERROR_SUCCESS 0 -typedef int lmic_tx_error_t; - -// In MCCI LMIC these are already defined. -// This macro can be used to initalize an array of event strings -#define LEGACY_LMIC_EVENT_NAME_TABLE__INIT \ - "<>", \ - "EV_SCAN_TIMEOUT", "EV_BEACON_FOUND", \ - "EV_BEACON_MISSED", "EV_BEACON_TRACKED", "EV_JOINING", \ - "EV_JOINED", "EV_RFU1", "EV_JOIN_FAILED", "EV_REJOIN_FAILED", \ - "EV_TXCOMPLETE", "EV_LOST_TSYNC", "EV_RESET", \ - "EV_RXCOMPLETE", "EV_LINK_DEAD", "EV_LINK_ALIVE" - -// If working on an AVR (or worried about memory size), you can use this multi-zero -// string and put this in a single const F() string to store it in program memory. -// Index through this counting up from 0, until you get to the entry you want or -// to an entry that begins with a \0. -#define LEGACY_LMIC_EVENT_NAME_MULTISZ__INIT \ - "<>\0" \ "EV_SCAN_TIMEOUT\0" \ - "EV_BEACON_FOUND\0" \ - "EV_BEACON_MISSED\0" \ - "EV_BEACON_TRACKED\0" \ - "EV_JOINING\0" \ - "EV_JOINED\0" \ - "EV_RFU1\0" \ - "EV_JOIN_FAILED\0" \ - "EV_REJOIN_FAILED\0" \ - "EV_TXCOMPLETE\0" \ - "EV_LOST_TSYNC\0" \ - "EV_RESET\0" \ - "EV_RXCOMPLETE\0" \ - "EV_LINK_DEAD\0" \ - "EV_LINK_ALIVE\0" -#endif // LMIC_MCCI - -#ifdef MCCI_LMIC -static const char *const lmicEventNames[] = {LMIC_EVENT_NAME_TABLE__INIT}; -static const char *const lmicErrorNames[] = {LMIC_ERROR_NAME__INIT}; -#else -static const char *const lmicEventNames[] = {LEGACY_LMIC_EVENT_NAME_TABLE__INIT}; -#endif - -#endif // LMIC_NODE_H_ diff --git a/include/lmic-helpers.h b/include/radiolib-helpers.h similarity index 82% rename from include/lmic-helpers.h rename to include/radiolib-helpers.h index 9167a06..f7d3b6e 100644 --- a/include/lmic-helpers.h +++ b/include/radiolib-helpers.h @@ -1,5 +1,5 @@ -#ifndef LMIC_HELPERS_H_ -#define LMIC_HELPERS_H_ +#ifndef RADIOLIB_HELPERS_H_ +#define RADIOLIB_HELPERS_H_ #include "include.h" @@ -21,4 +21,4 @@ void handleDownlinkMsgTask(void *parameter); extern PicoMQTT::Server mqtt; -#endif // LMIC_HELPERS_H_ +#endif // RADIOLIB_HELPERS_H_ diff --git a/include/radiolib-node.h b/include/radiolib-node.h new file mode 100644 index 0000000..52eefa5 --- /dev/null +++ b/include/radiolib-node.h @@ -0,0 +1,127 @@ +/******************************************************************************* + * + * File: radiolib-node.h + * + * Function: RadioLib-node main header file. + * + * Copyright: Copyright (c) 2024 LMIC Node Rebuild Project + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files to do, + * whatever they want with them without any restriction, including, + * but not limited to, copying, modification and redistribution. + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY. + * + * License: MIT License. See accompanying LICENSE file. + * + * Author: LMIC Node Rebuild Project + * + ******************************************************************************/ + +#ifndef RADIOLIB_NODE_H_ +#define RADIOLIB_NODE_H_ + +#include "include.h" + +// Default LoRaWAN parameters +const uint8_t DefaultABPDataRate = 5; // SF7BW125 equivalent +const int8_t DefaultABPTxPower = 14; + +extern bool boardInit(InitType initType); + +#ifdef USE_SERIAL +extern HardwareSerial &serial; +#endif + +#ifdef USE_DISPLAY +// Create U8x8 instance for SSD1306 OLED display (no reset) using hardware I2C. +extern U8X8_SSD1306_128X64_NONAME_HW_I2C display; +#endif + +// Forward declarations +extern TaskHandle_t RadioLibTask; +void radioLibTask(void *); + +void processDownlink(uint32_t timestamp, uint8_t fPort, uint8_t *data, uint8_t dataLength); +void onRadioLibEvent(uint16_t eventType, void* eventData); +void initRadioLib(void); +int16_t getSnrTenfold(void); +int16_t getRssi(void); +int16_t scheduleUplink(uint8_t fPort, uint8_t *data, uint8_t dataLength, bool confirmed); +extern bool joined; + +#ifndef DO_WORK_INTERVAL_SECONDS // Should be set in platformio.ini +#define DO_WORK_INTERVAL_SECONDS 300 // Default 5 minutes if not set +#endif + +#define TIMESTAMP_WIDTH 12 // Number of columns to display eventtime (zero-padded) +#define MESSAGE_INDENT TIMESTAMP_WIDTH + 3 + +#if !defined(ABP_ACTIVATION) && !defined(OTAA_ACTIVATION) +#define OTAA_ACTIVATION +#endif + +enum class ActivationMode +{ + OTAA, + ABP +}; +#ifdef OTAA_ACTIVATION +const ActivationMode activationMode = ActivationMode::OTAA; +#else +const ActivationMode activationMode = ActivationMode::ABP; +#endif + +#if defined(ABP_ACTIVATION) && defined(OTAA_ACTIVATION) +#error Only one of ABP_ACTIVATION and OTAA_ACTIVATION can be defined. +#endif + +#if defined(ABP_ACTIVATION) && defined(ABP_DEVICEID) +const char deviceId[] = ABP_DEVICEID; +#elif defined(DEVICEID) +const char deviceId[] = DEVICEID; +#else +const char deviceId[] = DEVICEID_DEFAULT; +#endif + +// Allow WAITFOR_SERIAL_SECONDS to be defined in platformio.ini. +// If used it shall be defined in the [common] section. +// The common setting will only be used for boards that have +// USE_SERIAL defined and WAITFOR_SERIAL_SECONDS > 0 defined. +#ifndef WAITFOR_SERIAL_SECONDS_DEFAULT +#define WAITFOR_SERIAL_SECONDS_DEFAULT 10 +#endif +#ifndef WAITFOR_SERIAL_SECONDS +#define WAITFOR_SERIAL_SECONDS WAITFOR_SERIAL_SECONDS_DEFAULT +#endif + +// Constants for display layout (from original LMIC implementation) +#define COL_0 0 +#define FRMCNTRS_ROW 6 +#define MONITOR_SPEED 115200 +#define WAITFOR_SERIAL_S WAITFOR_SERIAL_SECONDS + +// Device ID defaults +#ifndef DEVICEID_DEFAULT +#define DEVICEID_DEFAULT "RadioLib-Node" +#endif + +// RadioLib specific declarations +extern Module* radio; +extern LoRaWANNode* node; + +// RadioLib event types +#define RADIOLIB_EVENT_JOIN_ACCEPT 0x01 +#define RADIOLIB_EVENT_JOIN_FAILED 0x02 +#define RADIOLIB_EVENT_UPLINK_COMPLETE 0x03 +#define RADIOLIB_EVENT_DOWNLINK_RECEIVED 0x04 +#define RADIOLIB_EVENT_LINK_CHECK 0x05 + +// RadioLib error handling +typedef int16_t radiolib_error_t; +#define RADIOLIB_ERR_NONE 0 + +#endif // RADIOLIB_NODE_H_ diff --git a/platformio.ini b/platformio.ini index 7a7a00a..090fe7b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -25,29 +25,15 @@ lib_deps = [esp32] build_flags = - -D hal_init=LMICHAL_init + -D RADIOLIB_PLATFORM=RADIOLIB_PLATFORM_ESP32 -[mcci_lmic] +[radiolib] lib_deps = - mcci-catena/MCCI LoRaWAN LMIC library + jgromes/RadioLib@^6.4.2 build_flags = - -D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS - - -D DISABLE_PING - -D DISABLE_BEACONS - - - -D USE_ORIGINAL_AES - - -D CFG_eu868=1 + -D RADIOLIB_LORAWAN_EU868 + -D RADIOLIB_LORAWAN_CLASS_A -[classic_lmic] -lib_deps = - matthijskooijman/IBM LMIC framework -build_flags = - - -D DISABLE_PING - -D DISABLE_BEACONS [env:ttgo-lora32-v2] platform = espressif32@6.12.0 @@ -60,7 +46,7 @@ board_build.filesystem = littlefs ; board_build.partitions = huge_app.csv # not required, increases Flash size for program lib_deps = ${common.lib_deps} - ${mcci_lmic.lib_deps} + ${radiolib.lib_deps} mlesniew/PicoMQTT@^0.3.2 adafruit/RTClib@^2.1.1 lewisxhe/XPowersLib @ ^0.2.0 @@ -69,17 +55,17 @@ lib_deps = build_flags = ${common.build_flags} ${esp32.build_flags} - ${mcci_lmic.build_flags} + ${radiolib.build_flags} -D BOARD_TTGO -D DEV2 -D BSFILE=\"bsf_ttgo_lora32_v2.h\" -D MONITOR_SPEED=${common.monitor_speed} - -D LMIC_PRINTF_TO=Serial + -D RADIOLIB_DEBUG -D AXP -D SLEEP_VAR=RTC_DATA_ATTR -D USE_SERIAL -D USE_DISPLAY - -D CFG_sx1276_radio=1 + -D RADIOLIB_MODULE_SX1276 -D USE_RTC ; -D USE_SD @@ -91,20 +77,20 @@ upload_speed = 460800 monitor_speed = ${common.monitor_speed} lib_deps = ${common.lib_deps} - ${mcci_lmic.lib_deps} + ${radiolib.lib_deps} mlesniew/PicoMQTT@^0.3.2 adafruit/RTClib@^2.1.1 build_flags = ${common.build_flags} ${esp32.build_flags} - ${mcci_lmic.build_flags} + ${radiolib.build_flags} -D BOARD_LOPY -D DEV4 -D BSFILE=\"bsf_lopy.h\" -D MONITOR_SPEED=${common.monitor_speed} - -D LMIC_PRINTF_TO=Serial + -D RADIOLIB_DEBUG -D USE_SERIAL - -D CFG_sx1272_radio=1 + -D RADIOLIB_MODULE_SX1272 [env:tbeam] @@ -120,7 +106,7 @@ board_build.filesystem = littlefs ; board_build.partitions = huge_app.csv # not required, increases Flash size for program lib_deps = ${common.lib_deps} - ${mcci_lmic.lib_deps} + ${radiolib.lib_deps} mlesniew/PicoMQTT@^1.1.2 mlesniew/PicoWebsocket@^1.0.0 lewisxhe/XPowersLib @ ^0.2.0 @@ -129,14 +115,14 @@ lib_deps = build_flags = ${common.build_flags} ${esp32.build_flags} - ${mcci_lmic.build_flags} + ${radiolib.build_flags} -D BOARD_TBEAM -D BSFILE=\"bsf_ttgo_t_beam.h\" -D MONITOR_SPEED=${common.monitor_speed} - -D LMIC_PRINTF_TO=Serial + -D RADIOLIB_DEBUG -D USE_SERIAL -D USE_DISPLAY - -D CFG_sx1276_radio=1 + -D RADIOLIB_MODULE_SX1276 -D AXP -D SLEEP_VAR=RTC_DATA_ATTR -D ESP32 @@ -155,7 +141,7 @@ board_build.filesystem = littlefs board_build.partitions = huge_app.csv # not required, increases Flash size for program lib_deps = ${common.lib_deps} - ${mcci_lmic.lib_deps} + ${radiolib.lib_deps} mlesniew/PicoMQTT@^0.3.2 lewisxhe/XPowersLib @ ^0.2.0 mikalhart/TinyGPSPlus @ ^1.0.2 @@ -163,13 +149,13 @@ lib_deps = build_flags = ${common.build_flags} ${esp32.build_flags} - ${mcci_lmic.build_flags} + ${radiolib.build_flags} -D BOARD_DEV -D BSFILE=\"bsf_lopy.h\" -D MONITOR_SPEED=${common.monitor_speed} - -D LMIC_PRINTF_TO=Serial + -D RADIOLIB_DEBUG -D USE_SERIAL - -D CFG_sx1276_radio=1 + -D RADIOLIB_MODULE_SX1276 -D AXP -D SLEEP_VAR=RTC_DATA_ATTR -D ESP32 \ No newline at end of file diff --git a/src/bsf_lopy.cpp b/src/bsf_lopy.cpp index e2469c0..f88ab5b 100644 --- a/src/bsf_lopy.cpp +++ b/src/bsf_lopy.cpp @@ -1,19 +1,9 @@ #if defined(BOARD_LOPY) || defined(BOARD_DEV) #include "bsf_lopy.h" -// Pin mappings for LoRa tranceiver -const lmic_pinmap lmic_pins = { - .nss = 17, - .rxtx = LMIC_UNUSED_PIN, - .rst = LMIC_UNUSED_PIN, - .dio = {/*dio0*/ 23, /*dio1*/ 23, /*dio2*/ 23} -#ifdef MCCI_LMIC - , - .rxtx_rx_active = 0, - .rssi_cal = 10, - .spi_freq = 8000000 /* 8 MHz */ -#endif -}; +// RadioLib module instances (LoPy uses SX1272) +SX1272 radioModule = new Module(17, 23, RADIOLIB_NC, 23); +LoRaWANNode lorawanNode(&radioModule, &EU868); #ifdef USE_SERIAL HardwareSerial &serial = Serial; @@ -46,6 +36,10 @@ bool boardInit(InitType initType) // These pins will be remembered and will not change if any library // later calls SPI.begin() without parameters. SPI.begin(5, 19, 27, 18); + + // Set global RadioLib pointers + radio = &radioModule; + node = &lorawanNode; break; case InitType::PostInitSerial: diff --git a/src/bsf_ttgo_lora32_v2.cpp b/src/bsf_ttgo_lora32_v2.cpp index e971b9a..63b3b88 100644 --- a/src/bsf_ttgo_lora32_v2.cpp +++ b/src/bsf_ttgo_lora32_v2.cpp @@ -2,19 +2,9 @@ #ifdef BOARD_TTGO #include "bsf_ttgo_lora32_v2.h" -// Pin mappings for LoRa tranceiver -const lmic_pinmap lmic_pins = { - .nss = 18, - .rxtx = LMIC_UNUSED_PIN, - .rst = LMIC_UNUSED_PIN, - .dio = {/*dio0*/ 26, /*dio1*/ 33, /*dio2*/ LMIC_UNUSED_PIN} -#ifdef MCCI_LMIC - , - .rxtx_rx_active = 0, - .rssi_cal = 10, - .spi_freq = 8000000 /* 8 MHz */ -#endif -}; +// RadioLib module instances +SX1276 radioModule = new Module(18, 26, RADIOLIB_NC, 33); +LoRaWANNode lorawanNode(&radioModule, &EU868); #ifdef USE_SERIAL HardwareSerial &serial = Serial; @@ -42,7 +32,12 @@ bool boardInit(InitType initType) { case InitType::Hardware: // Note: Serial port and display are not yet initialized and cannot be used use here. - // No actions required for this board. + // Initialize SPI for RadioLib + SPI.begin(); + + // Set global RadioLib pointers + radio = &radioModule; + node = &lorawanNode; break; case InitType::PostInitSerial: diff --git a/src/bsf_ttgo_t_beam.cpp b/src/bsf_ttgo_t_beam.cpp index c260320..3d2960e 100644 --- a/src/bsf_ttgo_t_beam.cpp +++ b/src/bsf_ttgo_t_beam.cpp @@ -1,22 +1,9 @@ #ifdef BOARD_TBEAM #include "bsf_ttgo_t_beam.h" -// Pin mappings for LoRa tranceiver -const lmic_pinmap lmic_pins = { - .nss = 18, - .rxtx = LMIC_UNUSED_PIN, - .rst = 23, - .dio = { - /*dio0*/ 26, - /*dio1*/ 33, - /*dio2*/ 32} -#ifdef MCCI_LMIC - , - .rxtx_rx_active = 0, - .rssi_cal = 10, - .spi_freq = 8000000 /* 8 MHz */ -#endif -}; +// RadioLib module instances +SX1276 radioModule = new Module(18, 26, 23, 33); +LoRaWANNode lorawanNode(&radioModule, &EU868); #ifdef USE_SERIAL HardwareSerial &serial = Serial; @@ -44,7 +31,12 @@ bool boardInit(InitType initType) { case InitType::Hardware: // Note: Serial port and display are not yet initialized and cannot be used use here. - // No actions required for this board. + // Initialize SPI for RadioLib + SPI.begin(); + + // Set global RadioLib pointers + radio = &radioModule; + node = &lorawanNode; break; case InitType::PostInitSerial: diff --git a/src/lmic-helpers.cpp b/src/lmic-helpers.cpp index 8c530eb..0bba82e 100644 --- a/src/lmic-helpers.cpp +++ b/src/lmic-helpers.cpp @@ -1,4 +1,4 @@ -#include "lmic-helpers.h" +#include "radiolib-helpers.h" // LoRaWAN queues linkMessage uplinklinkMessage; @@ -66,20 +66,20 @@ void handleUplinkMsgTask(void *parameter) Serial.println("Got pending uplink message"); Serial.printf("fport: %d; length: %d\n", prxuplinkMessage->fport, prxuplinkMessage->length); - if (LMIC.devaddr != 0) + if (joined) { - if (LMIC.opmode & OP_TXRXPEND) + int16_t result = scheduleUplink(prxuplinkMessage->fport, prxuplinkMessage->data, prxuplinkMessage->length, false); + if (result == RADIOLIB_ERR_NONE) { - Serial.println("Uplink not scheduled because TxRx pending"); - } - else - { - scheduleUplink(prxuplinkMessage->fport, prxuplinkMessage->data, prxuplinkMessage->length, false); lastUplinks.add(*prxuplinkMessage); String topic = "ludwig/lora/uplink"; String message = publishLinkMessage(topic.c_str(), prxuplinkMessage); Serial.printf("Published message in topic '%s': %s\n", topic.c_str(), message.c_str()); } + else + { + Serial.printf("Uplink not scheduled, error code: %d\n", result); + } count = 0; } diff --git a/src/lmic-node.cpp b/src/lmic-node.cpp deleted file mode 100644 index 7682fae..0000000 --- a/src/lmic-node.cpp +++ /dev/null @@ -1,412 +0,0 @@ -#include "lmic-node.h" - -bool joined = false; - -linkMessage downlinkMessage; -QueueHandle_t downlinkQueue = xQueueCreate(10, sizeof(struct linkMessage *)); - -// Helper function to convert ASCII hex string to byte value. -u1_t ASCII2Hex(const char str[2]) -{ - unsigned char ASCII = 0; - // High Nibble - if (str[0] >= 'A' && str[0] <= 'F') - ASCII = str[0] - 'A' + 10; - else if (str[0] >= 'a' && str[0] <= 'f') - ASCII = str[0] - 'a' + 10; - else - ASCII = str[0] - '0'; - ASCII <<= 4; - // Low Nibble - if (str[1] >= 'A' && str[1] <= 'F') - ASCII |= str[1] - 'A' + 10; - else if (str[1] >= 'a' && str[1] <= 'f') - ASCII |= str[1] - 'a' + 10; - else - ASCII |= str[1] - '0'; - return ASCII; -} - -// Set LoRaWAN keys defined in lorawan-keys.h. -#ifdef OTAA_ACTIVATION -const char DEVEUI[] = ASCII_DEVEUI; -const char APPEUI[] = ASCII_APPEUI; -const char APPKEY[] = ASCII_APPKEY; - -// Below callbacks are used by LMIC for reading above values. -void os_getDevEui(u1_t *buf) -{ - for (uint8_t i = 0; i < 8; ++i) - buf[i] = ASCII2Hex(&DEVEUI[(7 - i) * 2]); -} -void os_getArtEui(u1_t *buf) -{ - for (uint8_t i = 0; i < 8; ++i) - buf[i] = ASCII2Hex(&APPEUI[(7 - i) * 2]); -} -void os_getDevKey(u1_t *buf) -{ - for (uint8_t i = 0; i < 16; ++i) - buf[i] = ASCII2Hex(&APPKEY[i * 2]); -} -#else -// ABP activation -static const u4_t DEVADDR = ABP_DEVADDR; -static const PROGMEM u1_t NWKSKEY[16] = {ABP_NWKSKEY}; -static const u1_t PROGMEM APPSKEY[16] = {ABP_APPSKEY}; -// Below callbacks are not used be they must be defined. -void os_getDevEui(u1_t *buf) {} -void os_getArtEui(u1_t *buf) {} -void os_getDevKey(u1_t *buf) {} -#endif - -int16_t getSnrTenfold(void) -{ - // Returns ten times the SNR (dB) value of the last received packet. - // Ten times to prevent the use of float but keep 1 decimal digit accuracy. - // Calculation per SX1276 datasheet rev.7 §6.4, SX1276 datasheet rev.4 §6.4. - // LMIC.snr contains value of PacketSnr, which is 4 times the actual SNR value. - return (LMIC.snr * 10) / 4; -} - -int16_t getRssi(int8_t snr) -{ - // Returns correct RSSI (dBm) value of the last received packet. - // Calculation per SX1276 datasheet rev.7 §5.5.5, SX1272 datasheet rev.4 §5.5.5. - -#define RSSI_OFFSET 64 -#define SX1276_FREQ_LF_MAX 525000000 // per datasheet 6.3 -#define SX1272_RSSI_ADJUST -139 -#define SX1276_RSSI_ADJUST_LF -164 -#define SX1276_RSSI_ADJUST_HF -157 - - int16_t rssi; - -#ifdef MCCI_LMIC - - rssi = LMIC.rssi - RSSI_OFFSET; - -#else - int16_t rssiAdjust; -#ifdef CFG_sx1276_radio - if (LMIC.freq > SX1276_FREQ_LF_MAX) - { - rssiAdjust = SX1276_RSSI_ADJUST_HF; - } - else - { - rssiAdjust = SX1276_RSSI_ADJUST_LF; - } -#else - // CFG_sx1272_radio - rssiAdjust = SX1272_RSSI_ADJUST; -#endif - - // Revert modification (applied in lmic/radio.c) to get PacketRssi. - int16_t packetRssi = LMIC.rssi + 125 - RSSI_OFFSET; - if (snr < 0) - { - rssi = rssiAdjust + packetRssi + snr; - } - else - { - rssi = rssiAdjust + (16 * packetRssi) / 15; - } -#endif - - return rssi; -} - -void setupLmic(bit_t adrEnabled = 1, - dr_t abpDataRate = DefaultABPDataRate, - s1_t abpTxPower = DefaultABPTxPower) -{ - // Initialize LMIC runtime environment - os_init(); - // Reset MAC state - LMIC_reset(); - -#ifdef ABP_ACTIVATION - setAbpParameters(abpDataRate, abpTxPower); -#endif - - // Enable or disable ADR (data rate adaptation). - // Should be turned off if the device is not stationary (mobile). - // 1 is on, 0 is off. - LMIC_setAdrMode(adrEnabled); - - if (activationMode == ActivationMode::OTAA) - { -#if defined(CFG_us915) || defined(CFG_au915) - // NA-US and AU channels 0-71 are configured automatically - // but only one group of 8 should (a subband) should be active - // TTN recommends the second sub band, 1 in a zero based count. - // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json - LMIC_selectSubBand(1); -#endif - } - -// Relax LMIC timing if defined -#if defined(LMIC_CLOCK_ERROR_PPM) - uint32_t clockError = 0; -#if LMIC_CLOCK_ERROR_PPM > 0 -#if defined(MCCI_LMIC) && LMIC_CLOCK_ERROR_PPM > 4000 -// Allow clock error percentage to be > 0.4% -#define LMIC_ENABLE_arbitrary_clock_error 1 -#endif - clockError = (LMIC_CLOCK_ERROR_PPM / 100) * (MAX_CLOCK_ERROR / 100) / 100; - LMIC_setClockError(clockError); -#endif - -#ifdef USE_SERIAL - serial.print(F("Clock Error: ")); - serial.print(LMIC_CLOCK_ERROR_PPM); - serial.print(" ppm ("); - serial.print(clockError); - serial.println(")"); -#endif -#endif - -#ifdef MCCI_LMIC - // Register a custom eventhandler and don't use default onEvent() to enable - // additional features (e.g. make EV_RXSTART available). User data pointer is omitted. - LMIC_registerEventCb(&onLmicEvent, nullptr); -#endif -} - -#ifdef MCCI_LMIC -void onLmicEvent(void *pUserData, ev_t ev) -#else -void onEvent(ev_t ev) -#endif -{ - // LMIC event handler - ostime_t timestamp = os_getTime(); - - const String topic = "ludwig/lora/event"; - DynamicJsonDocument doc(1024); - doc["ostime"] = timestamp; - doc["event"] = ev; - String message; - serializeJson(doc, message); - Serial.println(message); - mqtt.publish(topic, message); - - switch (ev) - { -#ifdef MCCI_LMIC - // Only supported in MCCI LMIC library: - case EV_RXSTART: - // Do not print anything for this event or it will mess up timing. - break; - - case EV_TXSTART: - setTxIndicatorsOn(); - printEvent(timestamp, ev); - break; - - case EV_JOIN_TXCOMPLETE: - case EV_TXCANCELED: - setTxIndicatorsOn(false); - printEvent(timestamp, ev); - break; -#endif - case EV_JOINED: - setTxIndicatorsOn(false); - printEvent(timestamp, ev); - printSessionKeys(); - - // Disable link check validation. - // Link check validation is automatically enabled - // during join, but because slow data rates change - // max TX size, it is not used in this example. - LMIC_setLinkCheckMode(0); - - // Set joined flag to prevent rejoining again and again. - Serial.println("EV_JOINED EVENT!"); - joined = true; - - break; - - case EV_TXCOMPLETE: - // Transmit completed, includes waiting for RX windows. - setTxIndicatorsOn(false); - printEvent(timestamp, ev); - printFrameCounters(); - - // Check if downlink was received - if (LMIC.dataLen != 0 || LMIC.dataBeg != 0) - { - uint8_t fPort = 0; - if (LMIC.txrxFlags & TXRX_PORT) - { - fPort = LMIC.frame[LMIC.dataBeg - 1]; - } - printDownlinkInfo(); - processDownlink(timestamp, fPort, LMIC.frame + LMIC.dataBeg, LMIC.dataLen); - } - break; - case EV_LINK_DEAD: - printEvent(timestamp, ev); - Serial.println("EV_LINK_DEAD EVENT!"); - Serial.println("Restarting..."); - joined = false; - ESP.restart(); - break; - - // Below events are printed only. - case EV_SCAN_TIMEOUT: - case EV_BEACON_FOUND: - case EV_BEACON_MISSED: - case EV_BEACON_TRACKED: - case EV_RFU1: // This event is defined but not used in code - case EV_JOINING: - Serial.println("EV_JOINING EVENT!"); - break; - case EV_JOIN_FAILED: - Serial.println("EV_JOIN_FAILED EVENT!"); - joined = false; - break; - case EV_REJOIN_FAILED: - case EV_LOST_TSYNC: - Serial.println("EV_JOIN_FAILED EVENT!"); - joined = false; - break; - case EV_RESET: - case EV_RXCOMPLETE: - case EV_LINK_ALIVE: -#ifdef MCCI_LMIC - // Only supported in MCCI LMIC library: - case EV_SCAN_FOUND: // This event is defined but not used in code -#endif - printEvent(timestamp, ev); - break; - - default: - printEvent(timestamp, "Unknown Event"); - break; - } -} - -lmic_tx_error_t scheduleUplink(uint8_t fPort, uint8_t *data, uint8_t dataLength, bool confirmed = false) -{ - // Transmission will be performed at the next possible time - - ostime_t timestamp = os_getTime(); - printEvent(timestamp, "Packet queued"); - - lmic_tx_error_t retval = LMIC_setTxData2(fPort, data, dataLength, confirmed ? 1 : 0); - timestamp = os_getTime(); - - if (retval == LMIC_ERROR_SUCCESS) - { -#ifdef CLASSIC_LMIC - // For MCCI_LMIC this will be handled in EV_TXSTART - setTxIndicatorsOn(); -#endif - } - else - { - String errmsg; -#ifdef USE_SERIAL - errmsg = "LMIC Error: "; -#ifdef MCCI_LMIC - errmsg.concat(lmicErrorNames[abs(retval)]); -#else - errmsg.concat(retval); -#endif - printEvent(timestamp, errmsg.c_str(), PrintTarget::Serial); -#endif -#ifdef USE_DISPLAY - errmsg = "LMIC Err: "; - errmsg.concat(retval); - printEvent(timestamp, errmsg.c_str(), PrintTarget::Display); -#endif - } - return retval; -} - -void processDownlink(ostime_t txCompleteTimestamp, uint8_t fPort, uint8_t *data, uint8_t dataLength) -{ - // This function is called from the onEvent() event handler - // on EV_TXCOMPLETE when a downlink message was received. - linkMessage *ptxdownlinkMessage = (linkMessage *)pvPortMalloc(sizeof(linkMessage)); - if (ptxdownlinkMessage == NULL) - { - Serial.println(F("Failed to allocate heap memory.")); - } - else - { - ptxdownlinkMessage->fport = fPort; - ptxdownlinkMessage->length = dataLength; - ptxdownlinkMessage->data = data; - xQueueSend(downlinkQueue, &ptxdownlinkMessage, (TickType_t)0); - } -} - -// █ █ █▀▀ █▀▀ █▀▄ █▀▀ █▀█ █▀▄ █▀▀ █▀▀ █▀█ █▀▄ -// █ █ ▀▀█ █▀▀ █▀▄ █ █ █ █ █ █▀▀ █▀▀ █ █ █ █ -// ▀▀▀ ▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀ ▀▀ ▀▀▀ ▀▀▀ ▀ ▀ ▀▀ - -TaskHandle_t LmicTask; - -void lmicTask(void *parameter) -{ - const TickType_t xDelay = 10 / portTICK_PERIOD_MS; - while (true) - { - os_runloop_once(); - vTaskDelay(xDelay); - } -} - -void initLmic() -{ - // boardInit(InitType::Hardware) must be called at start of setup() before anything else. - bool hardwareInitSucceeded = boardInit(InitType::Hardware); - -#ifdef USE_DISPLAY - initDisplay(); -#endif - -#ifdef USE_SERIAL - initSerial(MONITOR_SPEED, WAITFOR_SERIAL_S); -#endif - - boardInit(InitType::PostInitSerial); - -#if defined(USE_SERIAL) || defined(USE_DISPLAY) - printHeader(); -#endif - - if (!hardwareInitSucceeded) - { -#ifdef USE_SERIAL - serial.println(F("Error: hardware init failed.")); - serial.flush(); -#endif -#ifdef USE_DISPLAY - // Following mesage shown only if failure was unrelated to I2C. - display.setCursor(COL_0, FRMCNTRS_ROW); - display.print(F("HW init failed")); -#endif - abort(); - } - - setupLmic(); - - if (activationMode == ActivationMode::OTAA) - { - LMIC_startJoining(); - } - - xTaskCreatePinnedToCore( - lmicTask, /* Task function. */ - "LMIC Task", /* String with name of task. */ - 30000, /* Stack size in words. */ - NULL, /* Parameter passed as input of the task */ - 1, /* Priority of the task. */ - &LmicTask, /* Task handle. */ - 1 /* Pinned CPU core. */ - ); -} diff --git a/src/main.cpp b/src/main.cpp index dc377ec..83f0392 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -287,7 +287,7 @@ void setup() server.begin(); #ifndef BOARD_DEV - initLmic(); + initRadioLib(); #endif // BOARD_DEV initMqtt(); initHelperTasks(); diff --git a/src/radiolib-helpers.cpp b/src/radiolib-helpers.cpp new file mode 100644 index 0000000..0bba82e --- /dev/null +++ b/src/radiolib-helpers.cpp @@ -0,0 +1,143 @@ +#include "radiolib-helpers.h" + +// LoRaWAN queues +linkMessage uplinklinkMessage; +QueueHandle_t uplinkQueue = xQueueCreate(10, sizeof(struct linkMessage *)); +TaskHandle_t HandleDownlinkMsgTask; +TaskHandle_t HandleUplinkMsgTask; + +void initHelperTasks(void) +{ + xTaskCreatePinnedToCore( + handleUplinkMsgTask, /* Task function. */ + "Handle Uplink Task", /* String with name of task. */ + 10000, /* Stack size in words. */ + NULL, /* Parameter passed as input of the task */ + 3, /* Priority of the task. */ + &HandleUplinkMsgTask, /* Task handle. */ + 1 /* Pinned CPU core. */ + ); + + xTaskCreatePinnedToCore( + handleDownlinkMsgTask, /* Task function. */ + "Handle Downlink Task", /* String with name of task. */ + 10000, /* Stack size in words. */ + NULL, /* Parameter passed as input of the task */ + 2, /* Priority of the task. */ + &HandleDownlinkMsgTask, /* Task handle. */ + 1 /* Pinned CPU core. */ + ); +} + +String publishLinkMessage(const char *topic, const linkMessage *pMessage) +{ + DynamicJsonDocument doc(1024); + doc["fport"] = pMessage->fport; + doc["length"] = pMessage->length; + JsonArray data = doc.createNestedArray("data"); + for (uint8_t i = 0; i < (uint8_t)pMessage->length; i++) + { + data.add(pMessage->data[i]); + } + String message; + serializeJson(doc, message); + mqtt.publish(topic, message); + return message; +} + +void handleUplinkMsgTask(void *parameter) +{ + uint8_t count = 0; + const TickType_t xDelay = 60000 / portTICK_PERIOD_MS; + struct linkMessage *prxuplinkMessage; + + while (true) + { + UBaseType_t uplinkMessagesWaiting = uxQueueMessagesWaiting(uplinkQueue); + Serial.printf("%d uplink(s) waiting...\n", uplinkMessagesWaiting); + + if (joined) + { + Serial.printf("Working uplinkQueue.\n"); + + // Pend on new message in queue and forward it to the corresponding handler + if (xQueueReceive(uplinkQueue, &(prxuplinkMessage), portMAX_DELAY)) + { + Serial.println("Got pending uplink message"); + Serial.printf("fport: %d; length: %d\n", prxuplinkMessage->fport, prxuplinkMessage->length); + + if (joined) + { + int16_t result = scheduleUplink(prxuplinkMessage->fport, prxuplinkMessage->data, prxuplinkMessage->length, false); + if (result == RADIOLIB_ERR_NONE) + { + lastUplinks.add(*prxuplinkMessage); + String topic = "ludwig/lora/uplink"; + String message = publishLinkMessage(topic.c_str(), prxuplinkMessage); + Serial.printf("Published message in topic '%s': %s\n", topic.c_str(), message.c_str()); + } + else + { + Serial.printf("Uplink not scheduled, error code: %d\n", result); + } + count = 0; + } + + vPortFree(prxuplinkMessage); + vPortFree(prxuplinkMessage->data); + } + } + else + { + Serial.println("Not joined yet."); + } + + vTaskDelay(xDelay); + } +} + +void handleDownlinkMsgTask(void *parameter) +{ + struct linkMessage *prxdownlinkMessage; + + const uint8_t cmdPort = 100; + const uint8_t resetCmd = 0xC0; + const uint8_t setTimeCmd = 0xC1; + + while (true) + { + // Pend on new message in queue and forward it to the corresponding handler + if (xQueueReceive(downlinkQueue, &(prxdownlinkMessage), portMAX_DELAY)) + { + Serial.println("Received downlinkQueue"); + Serial.printf("fport: %d; length: %d\n", prxdownlinkMessage->fport, prxdownlinkMessage->length); + Serial.print("payload: "); + for (uint8_t i = 0; i < (uint8_t)prxdownlinkMessage->length; i++) + { + Serial.print(prxdownlinkMessage->data[i]); + } + + String topic = "ludwig/lora/downlink"; + String message = publishLinkMessage(topic.c_str(), prxdownlinkMessage); + + Serial.printf("Published message in topic '%s': %s\n", topic.c_str(), message.c_str()); + + if (prxdownlinkMessage->fport == cmdPort && prxdownlinkMessage->length == 1 && prxdownlinkMessage->data[0] == resetCmd) + { + serial.println("Reset cmd received."); + } + else if (prxdownlinkMessage->fport == cmdPort && prxdownlinkMessage->data[0] == setTimeCmd && prxdownlinkMessage->length == 5) + { + uint32_t unixtime = prxdownlinkMessage->data[1] | (uint32_t)prxdownlinkMessage->data[2] << 8 | (uint32_t)prxdownlinkMessage->data[3] << 16 | (uint32_t)prxdownlinkMessage->data[4] << 24; + Serial.printf("Setting RTC to unix time: %d\n", unixtime); +#ifdef USE_RTC + rtc.adjust(unixtime); +#else + setTime(unixtime); +#endif + } + lastDownlinks.add(*prxdownlinkMessage); + vPortFree(prxdownlinkMessage); + } + } +} diff --git a/src/radiolib-node.cpp b/src/radiolib-node.cpp new file mode 100644 index 0000000..968bef5 --- /dev/null +++ b/src/radiolib-node.cpp @@ -0,0 +1,296 @@ +#include "radiolib-node.h" + +// Forward declarations for utility functions +void printHeader(void); +bool initSerial(unsigned long speed, int16_t timeoutSeconds); +void initDisplay(void); + +bool joined = false; + +linkMessage downlinkMessage; +QueueHandle_t downlinkQueue = xQueueCreate(10, sizeof(struct linkMessage *)); + +// RadioLib objects - will be initialized in board-specific files +Module* radio = nullptr; +LoRaWANNode* node = nullptr; + +// Helper function to convert ASCII hex string to byte value. +uint8_t ASCII2Hex(const char str[2]) +{ + unsigned char ASCII = 0; + // High Nibble + if (str[0] >= 'A' && str[0] <= 'F') + ASCII = str[0] - 'A' + 10; + else if (str[0] >= 'a' && str[0] <= 'f') + ASCII = str[0] - 'a' + 10; + else + ASCII = str[0] - '0'; + ASCII <<= 4; + // Low Nibble + if (str[1] >= 'A' && str[1] <= 'F') + ASCII |= str[1] - 'A' + 10; + else if (str[1] >= 'a' && str[1] <= 'f') + ASCII |= str[1] - 'a' + 10; + else + ASCII |= str[1] - '0'; + return ASCII; +} + +// LoRaWAN keys and configuration +#ifdef OTAA_ACTIVATION +// Use the existing key arrays from lorawan-keys.h +const uint8_t DEVEUI[8] = {OTAA_DEVEUI}; +const uint8_t APPEUI[8] = {OTAA_APPEUI}; +const uint8_t APPKEY[16] = {OTAA_APPKEY}; + +// Copy keys for RadioLib (keys are already in binary format) +void getDevEui(uint8_t *buf) +{ + memcpy(buf, DEVEUI, 8); +} + +void getAppEui(uint8_t *buf) +{ + memcpy(buf, APPEUI, 8); +} + +void getAppKey(uint8_t *buf) +{ + memcpy(buf, APPKEY, 16); +} +#else +// ABP activation +static const uint32_t DEVADDR = ABP_DEVADDR; +static const uint8_t NWKSKEY[16] = {ABP_NWKSKEY}; +static const uint8_t APPSKEY[16] = {ABP_APPSKEY}; +#endif + +int16_t getSnrTenfold(void) +{ + if (node == nullptr) return 0; + // RadioLib returns SNR in dB as float, convert to tenths + return (int16_t)(node->getSNR() * 10.0f); +} + +int16_t getRssi(void) +{ + if (node == nullptr) return -999; + return (int16_t)node->getRSSI(); +} + +void setupRadioLib(bool adrEnabled = true, + uint8_t abpDataRate = DefaultABPDataRate, + int8_t abpTxPower = DefaultABPTxPower) +{ + if (node == nullptr) { + Serial.println(F("Error: LoRaWAN node not initialized")); + return; + } + + // Set up LoRaWAN node + int16_t state = node->begin(); + if (state != RADIOLIB_ERR_NONE) { + Serial.print(F("LoRaWAN node initialization failed, code ")); + Serial.println(state); + return; + } + + // Configure LoRaWAN parameters + if (activationMode == ActivationMode::OTAA) { + uint8_t devEui[8], appEui[8], appKey[16]; + getDevEui(devEui); + getAppEui(appEui); + getAppKey(appKey); + + state = node->beginOTAA(devEui, appEui, appKey); + if (state != RADIOLIB_ERR_NONE) { + Serial.print(F("OTAA setup failed, code ")); + Serial.println(state); + return; + } + } else { + // ABP activation + state = node->beginABP(DEVADDR, NWKSKEY, APPSKEY); + if (state != RADIOLIB_ERR_NONE) { + Serial.print(F("ABP setup failed, code ")); + Serial.println(state); + return; + } + joined = true; // ABP is considered "joined" immediately + } + + // Set ADR mode + node->setADR(adrEnabled); + + // Set data rate and power for ABP + if (activationMode == ActivationMode::ABP) { + node->setDatarate(abpDataRate); + node->setTxPower(abpTxPower); + } + + Serial.println(F("RadioLib LoRaWAN setup complete")); +} + +// RadioLib event callback - simplified for now +void onRadioLibEvent(uint16_t eventType, void* eventData) +{ + uint32_t timestamp = millis(); // Use millis() instead of LMIC's os_getTime() + + const String topic = "ludwig/lora/event"; + DynamicJsonDocument doc(1024); + doc["timestamp"] = timestamp; + doc["event"] = eventType; + String message; + serializeJson(doc, message); + Serial.println(message); + mqtt.publish(topic, message); + + switch (eventType) { + case RADIOLIB_EVENT_JOIN_ACCEPT: + Serial.println(F("Join accept received")); + joined = true; + break; + + case RADIOLIB_EVENT_JOIN_FAILED: + Serial.println(F("Join failed")); + joined = false; + break; + + case RADIOLIB_EVENT_UPLINK_COMPLETE: + Serial.println(F("Uplink complete")); + break; + + case RADIOLIB_EVENT_DOWNLINK_RECEIVED: + Serial.println(F("Downlink received")); + // Handle downlink data processing here + break; + + default: + Serial.print(F("Unknown RadioLib event: ")); + Serial.println(eventType); + break; + } +} + +int16_t scheduleUplink(uint8_t fPort, uint8_t *data, uint8_t dataLength, bool confirmed) +{ + if (node == nullptr || !joined) { + return -1; // Not ready + } + + uint32_t timestamp = millis(); + Serial.print(F("Scheduling uplink on port ")); + Serial.print(fPort); + Serial.print(F(", length ")); + Serial.println(dataLength); + + int16_t state; + if (confirmed) { + state = node->sendReceive(data, dataLength, fPort); + } else { + state = node->uplink(data, dataLength, fPort); + } + + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("Uplink scheduled successfully")); + } else { + Serial.print(F("Uplink failed, code ")); + Serial.println(state); + } + + return state; +} + +void processDownlink(uint32_t txCompleteTimestamp, uint8_t fPort, uint8_t *data, uint8_t dataLength) +{ + // This function is called from the RadioLib event handler + // when a downlink message was received. + linkMessage *ptxdownlinkMessage = (linkMessage *)pvPortMalloc(sizeof(linkMessage)); + if (ptxdownlinkMessage == NULL) { + Serial.println(F("Failed to allocate heap memory.")); + } else { + ptxdownlinkMessage->fport = fPort; + ptxdownlinkMessage->length = dataLength; + ptxdownlinkMessage->data = (uint8_t*)pvPortMalloc(dataLength); + if (ptxdownlinkMessage->data != NULL) { + memcpy(ptxdownlinkMessage->data, data, dataLength); + xQueueSend(downlinkQueue, &ptxdownlinkMessage, (TickType_t)0); + } else { + vPortFree(ptxdownlinkMessage); + Serial.println(F("Failed to allocate heap memory for downlink data.")); + } + } +} + +// █▀▄ ▄▀█ █▀▄ █ █▀█ █ █ █▄▄ ▀█▀ ▄▀█ █▀ █▄▀ +// █▀▄ █▀█ █▄▀ █ █▄█ █▄▄ █ █▄█ █ █▀█ ▄█ █ █ + +TaskHandle_t RadioLibTask; + +void radioLibTask(void *parameter) +{ + const TickType_t xDelay = 100 / portTICK_PERIOD_MS; // 100ms delay + while (true) { + // RadioLib handles most operations internally via interrupts + // This task mainly handles periodic maintenance + if (node != nullptr) { + // Process any pending LoRaWAN operations + node->process(); + } + vTaskDelay(xDelay); + } +} + +void initRadioLib() +{ + // boardInit(InitType::Hardware) must be called at start of setup() before anything else. + bool hardwareInitSucceeded = boardInit(InitType::Hardware); + +#ifdef USE_DISPLAY + initDisplay(); +#endif + +#ifdef USE_SERIAL + initSerial(MONITOR_SPEED, WAITFOR_SERIAL_S); +#endif + + boardInit(InitType::PostInitSerial); + +#if defined(USE_SERIAL) || defined(USE_DISPLAY) + printHeader(); +#endif + + if (!hardwareInitSucceeded) { +#ifdef USE_SERIAL + serial.println(F("Error: hardware init failed.")); + serial.flush(); +#endif +#ifdef USE_DISPLAY + // Following message shown only if failure was unrelated to I2C. + display.setCursor(COL_0, FRMCNTRS_ROW); + display.print(F("HW init failed")); +#endif + abort(); + } + + setupRadioLib(); + + if (activationMode == ActivationMode::OTAA && node != nullptr) { + Serial.println(F("Starting OTAA join...")); + int16_t state = node->activate(); + if (state != RADIOLIB_ERR_NONE) { + Serial.print(F("OTAA activation failed, code ")); + Serial.println(state); + } + } + + xTaskCreatePinnedToCore( + radioLibTask, /* Task function. */ + "RadioLib Task", /* String with name of task. */ + 30000, /* Stack size in words. */ + NULL, /* Parameter passed as input of the task */ + 1, /* Priority of the task. */ + &RadioLibTask, /* Task handle. */ + 1 /* Pinned CPU core. */ + ); +} diff --git a/src/utils.cpp b/src/utils.cpp index 351aed5..badc075 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -184,37 +184,29 @@ void printHeader(void) #ifdef USE_DISPLAY display.clear(); display.setCursor(COL_0, HEADER_ROW); - display.print(F("LMIC-node")); + display.print(F("RadioLib-node")); #ifdef ABP_ACTIVATION display.drawString(ABPMODE_COL, HEADER_ROW, "ABP"); #endif -#ifdef CLASSIC_LMIC - display.drawString(CLMICSYMBOL_COL, HEADER_ROW, "*"); -#endif +// RadioLib symbol + display.drawString(CLMICSYMBOL_COL, HEADER_ROW, "R"); display.drawString(COL_0, DEVICEID_ROW, deviceId); display.setCursor(COL_0, INTERVAL_ROW); #endif #ifdef USE_SERIAL - serial.println(F("\n\nLMIC-node\n")); + serial.println(F("\n\nRadioLib-node\n")); serial.print(F("Device-id: ")); serial.println(deviceId); - serial.print(F("LMIC library: ")); -#ifdef MCCI_LMIC - serial.println(F("MCCI")); -#else - serial.println(F("Classic [Deprecated]")); -#endif + serial.print(F("LoRaWAN library: ")); + serial.println(F("RadioLib")); serial.print(F("Activation: ")); #ifdef OTAA_ACTIVATION serial.println(F("OTAA")); #else serial.println(F("ABP")); #endif -#if defined(LMIC_DEBUG_LEVEL) && LMIC_DEBUG_LEVEL > 0 - serial.print(F("LMIC debug: ")); - serial.println(LMIC_DEBUG_LEVEL); -#endif +// RadioLib debug is controlled via RADIOLIB_DEBUG flag if (activationMode == ActivationMode::OTAA) { serial.println();