Add support for LoraWanSend and LoraWanNode

This commit is contained in:
Theo Arends 2025-06-03 17:58:35 +02:00
parent 81465a8506
commit 263d84315f
3 changed files with 144 additions and 22 deletions

View File

@ -179,7 +179,8 @@ enum TasLoraFlags {
};
enum TasLoraWanFlags {
TAS_LORAWAN_FLAG_LINK_ADR_REQ
TAS_LORAWAN_FLAG_LINK_ADR_REQ,
TAS_LORAWAN_FLAG_DISABLED
};
enum TasLoraWanMTypes {
@ -220,7 +221,13 @@ enum TasLoraWanCIDNode {
TAS_LORAWAN_CID_DL_CHANNEL_ANS,
TAS_LORAWAN_CID_RFU1_ANS,
TAS_LORAWAN_CID_RFU2_ANS,
TAS_LORAWAN_CID_DEVICE_TIME_REQ
TAS_LORAWAN_CID_DEVICE_TIME_REQ,
TAS_LORAWAN_CID_RFU1,
TAS_LORAWAN_CID_RFU2,
TAS_LORAWAN_CID_PING_SLOT_INFO_REQ, // Class B
TAS_LORAWAN_CID_PING_SLOT_CHANNEL_ANS, // Class B
TAS_LORAWAN_CID_BEACON_TIMING_REQ, // Class B - Deprecated
TAS_LORAWAN_CID_BEACON_FREQ_ANS // Class B
};
enum LoRaWanRadioMode_t {
@ -315,6 +322,7 @@ typedef struct Lora_t {
uint8_t send_buffer_step;
uint8_t send_buffer_len;
uint8_t nodes;
uint8_t delay_settings_save;
bool rx;
bool send_request;
bool profile_changed;

View File

@ -446,6 +446,7 @@ void LoraWanSendResponse(uint8_t* buffer, size_t len, uint32_t lorawan_delay) {
Lora->send_request = false;
Lora->backup_settings = Lora->settings; // Make a copy;
Lora->send_buffer_step = 2; // Send at RX1 and RX2
Lora->delay_settings_save = ((lorawan_delay + TAS_LORAWAN_RECEIVE_DELAY2) / 1000) +2; // Delay settings save
uint32_t delay_rx1 = lorawan_delay - TimePassedSince(Lora->receive_time);
LoraWan_Send_RX1.once_ms(delay_rx1, LoraWanTickerSend);
@ -535,6 +536,43 @@ uint32_t LoraWanSpreadingFactorToDataRate(bool downlink) {
/*********************************************************************************************/
void LoraWanSendAppData(uint32_t node, uint8_t* FRMPayload, uint32_t len) {
if (len > 15) { return; } // M is region specific and depends on DataRate
uint32_t DevAddr = Lora->device_address +node;
uint16_t FCnt = Lora->settings.end_node[node]->FCntDown++;
uint8_t Key[TAS_LORAWAN_AES128_KEY_SIZE];
uint8_t FRMPayload_encrypted[TAS_LORAWAN_AES128_KEY_SIZE +9 +len];
LoraWanDeriveLegacyAppSKey(node, Key);
LoraWanEncryptDownlink(Key, DevAddr, FCnt, FRMPayload, len, 0, FRMPayload_encrypted);
uint32_t data_size = 13 + len;
uint8_t data[data_size];
data[0] = TAS_LORAWAN_MTYPE_UNCONFIRMED_DATA_DOWNLINK << 5;
data[1] = DevAddr;
data[2] = DevAddr >> 8;
data[3] = DevAddr >> 16;
data[4] = DevAddr >> 24;
data[5] = 0x00; // FCtrl - No FOpts
data[6] = FCnt;
data[7] = FCnt >> 8;
data[8] = 0x01; // FPort no MAC (as used by Dragino)
for (uint32_t i = 0; i < len; i++) {
data[9 +i] = FRMPayload_encrypted[i];
}
LoraWanDeriveLegacyNwkSKey(node, Key);
uint32_t MIC = LoraWanComputeLegacyDownlinkMIC(Key, DevAddr, FCnt, data, data_size -4);
data[data_size -4] = MIC;
data[data_size -3] = MIC >> 8;
data[data_size -2] = MIC >> 16;
data[data_size -1] = MIC >> 24;
LoraWanSendResponse(data, data_size, 10);
}
/*-------------------------------------------------------------------------------------------*/
void LoraWanSendLinkADRReq(uint32_t node) {
uint32_t DevAddr = Lora->device_address +node;
uint16_t FCnt = Lora->settings.end_node[node]->FCntDown++;
@ -623,6 +661,8 @@ void LoraWanSendMacResponse(uint32_t node, uint8_t* FOpts, uint32_t FCtrl) {
LoraWanSendResponse(data, data_size, 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)
@ -644,6 +684,8 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
((uint32_t)data[21] << 16) | ((uint32_t)data[22] << 24);
for (uint32_t node = 0; node < Lora->nodes; node++) {
if (bitRead(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_DISABLED)) { continue; } // Skip
uint32_t CalcMIC = LoraWanGenerateMIC(data, 19, Lora->settings.end_node[node]->AppKey);
if (MIC == CalcMIC) { // Valid MIC based on LoraWanAppKey
@ -739,6 +781,7 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
for (uint32_t node = 0; node < Lora->nodes; node++) {
if (0 == Lora->settings.end_node[node]->DevEUIh) { continue; } // No DevEUI so never joined
if ((Lora->device_address +node) != DevAddr) { continue; } // Not my device
if (bitRead(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_DISABLED)) { continue; } // Skip
uint32_t FCtrl = data[5];
uint32_t FOptsLen = FCtrl & 0x0F;
@ -761,7 +804,7 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
// page 19
// In testing with a Dragino LHT52 device, FCtrl_ADRACKReq was set after 64 (0x40) uplinks (= 21.3 hrs)
bool FCtrl_ACK = bitRead(FCtrl, 5);
bool Fctrl_ClassB = bitRead(FCtrl, 4);
bool Fctrl_ClassB = bitRead(FCtrl, 4); // Ready to receive scheduled downlink pings (Class B only)
/*
if ((0 == FOptsLen) && (0 == FOpts[0])) { // MAC response
@ -862,6 +905,15 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
mac_data[mac_data_idx++] = 0x00; // Fractional-second 1/256 sec
}
}
else if (TAS_LORAWAN_CID_PING_SLOT_INFO_REQ == FOpts[i]) {
i++; // PingSlotParam
}
else if (TAS_LORAWAN_CID_PING_SLOT_CHANNEL_ANS == FOpts[i]) {
i++; // Status
}
else if (TAS_LORAWAN_CID_BEACON_FREQ_ANS == FOpts[i]) {
i++; // Status
}
else {
// RFU
}
@ -936,17 +988,23 @@ bool LoraWanInput(uint8_t* data, uint32_t packet_size) {
\*********************************************************************************************/
#define D_CMND_LORAWANBRIDGE "Bridge"
#define D_CMND_LORAWANNODE "Node"
#define D_CMND_LORAWANAPPKEY "AppKey"
#define D_CMND_LORAWANNAME "Name"
#define D_CMND_LORAWANDECODER "Decoder"
#define D_CMND_LORAWANSEND "Send"
const char kLoraWanCommands[] PROGMEM = "LoRaWan|" // Prefix
D_CMND_LORAWANBRIDGE "|" D_CMND_LORAWANAPPKEY "|" D_CMND_LORAWANNAME "|" D_CMND_LORAWANDECODER;
const char kLoraWanCommands[] PROGMEM = "LoRaWan" // Prefix
"|" D_CMND_LORAWANBRIDGE "|" D_CMND_LORAWANNODE "|" D_CMND_LORAWANAPPKEY
"|" D_CMND_LORAWANNAME "|" D_CMND_LORAWANDECODER "|" D_CMND_LORAWANSEND;
void (* const LoraWanCommand[])(void) PROGMEM = {
&CmndLoraWanBridge, &CmndLoraWanAppKey, &CmndLoraWanName, &CmndLoraWanDecoder };
&CmndLoraWanBridge, &CmndLoraWanNode, &CmndLoraWanAppKey,
&CmndLoraWanName, &CmndLoraWanDecoder, &CmndLoraWanSend };
void CmndLoraWanBridge(void) {
// Enable LoraWan bridge
// LoraWanBridge <0|1>
// LoraWanBridge - Show LoraOption1
// LoraWanBridge 1 - Set LoraOption1 1 = Enable LoraWanBridge
uint32_t pindex = 0;
@ -961,8 +1019,28 @@ void CmndLoraWanBridge(void) {
ResponseCmndChar(GetStateText(bitRead(Lora->settings.flags, pindex)));
}
void CmndLoraWanNode(void) {
// Disable joined node (for testing purposes on other gateway)
// LoraWanNode<1..16> <0|1>
// LoraWanNode - Show current state
// LoraWanNode2 1 - Enable node 2
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Lora->nodes)) {
uint32_t node = XdrvMailbox.index -1;
if (XdrvMailbox.data_len) {
if (1 == XdrvMailbox.payload) {
bitClear(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_DISABLED); // Enable (clears bit)
} else {
bitSet(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_DISABLED); // Disable (sets bit)
}
}
ResponseCmndIdxChar(bitRead(Lora->settings.end_node[node]->flags, TAS_LORAWAN_FLAG_DISABLED)?"Disabled":"Enabled");
}
}
void CmndLoraWanAppKey(void) {
// LoraWanAppKey
// Configure node 32 digit AppKey
// LoraWanAppKey<1..16> <appkey>
// LoraWanAppKey - Show current appkey
// LoraWanAppKey2 0123456789abcdef0123456789abcdef
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Lora->nodes +1)) {
if (Lora->nodes < XdrvMailbox.index) {
@ -987,7 +1065,9 @@ void CmndLoraWanAppKey(void) {
}
void CmndLoraWanName(void) {
// LoraWanName
// Configure node name
// LoraName<1..16> <1|name>
// LoraWanName - Show current name
// LoraWanName 1 - Set to short DevEUI (or 0x0000 if not yet joined)
// LoraWanName2 LDS02a
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Lora->nodes)) {
@ -1006,9 +1086,10 @@ void CmndLoraWanName(void) {
}
void CmndLoraWanDecoder(void) {
// LoraWanDecoder
// LoraWanDecoder DraginoLDS02 - Set Dragino LDS02 message decoder for node 1
// LoraWanDecoder2 MerryIoTDW10 - Set MerryIoT DW10 message decoder for node 2
// Configure node berry decoder name
// LoraWanDecoder<1..16> <decoder name>
// LoraWanDecoder LDS02 - Set Dragino LDS02 message decoder for node 1
// LoraWanDecoder2 DW10 - Set MerryIoT DW10 message decoder for node 2
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Lora->nodes)) {
uint32_t node = XdrvMailbox.index -1;
if (XdrvMailbox.data_len) {
@ -1018,6 +1099,35 @@ void CmndLoraWanDecoder(void) {
}
}
void CmndLoraWanSend(void) {
// Send downlink command to node. Works in Class C (which uses more power)
// LoraWanSend<1..16> <hexdata>
// LoraWanSend3 260014 - Set dragino RJTDC time interval to 20 minutes
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Lora->nodes)) {
uint32_t node = XdrvMailbox.index -1;
if (XdrvMailbox.data_len) {
char *codes = RemoveSpace(XdrvMailbox.data);
int size = strlen(XdrvMailbox.data);
if (size > 30) { return; } // M is region specific and depends on DataRate
char *p;
char stemp[3];
uint8_t code;
uint8_t data[16];
uint32_t data_idx = 0;
while (size > 1) {
strlcpy(stemp, codes, sizeof(stemp));
code = strtol(stemp, &p, 16);
data[data_idx++] = code; // "260014" as hex values
size -= 2;
codes += 2;
}
Lora->receive_time = millis();
LoraWanSendAppData(node, data, data_idx);
ResponseCmndDone();
}
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/

View File

@ -175,6 +175,10 @@ void LoraSettingsLoad(bool erase) {
void LoraSettingsSave(void) {
// Called from FUNC_SAVE_SETTINGS every SaveData second and at restart
#ifdef USE_UFILESYS
if (Lora->delay_settings_save) { // Delay settings update when expecting cascading changes
Lora->delay_settings_save--;
return;
}
uint32_t crc32 = GetCfgCrc32((uint8_t*)&Lora->settings +4, sizeof(LoraSettings_t) -4); // Skip crc32
#ifdef USE_LORAWAN_BRIDGE
crc32 += LoraWanGetCfgCrc();