Tasmota/tasmota/tasmota_xdrv_driver/xdrv_73_6_lorawan_decode.ino

165 lines
8.4 KiB
C++

/*
xdrv_73_6_lorawan_decode.ino - LoRaWan node decode support for Tasmota
SPDX-FileCopyrightText: 2024 Theo Arends
SPDX-License-Identifier: GPL-3.0-only
*/
#ifdef USE_SPI_LORA
#ifdef USE_LORAWAN_BRIDGE
/*********************************************************************************************\
* LoRaWan node decode and presentation
\*********************************************************************************************/
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(Lora->settings.end_node[node].name.c_str()).c_str());
}
ResponseAppend_P(PSTR("{\"Node\":%d,\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\""), node +1, Lora->settings.end_node[node].DevEUIl & 0x0000FFFF);
if (!Lora->settings.end_node[node].name.startsWith(F("0x"))) {
ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), EscapeJSONString(Lora->settings.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, Lora->settings.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 LoraWanDecode(struct LoraNodeData_t* node_data) {
/* Enter with LoraNodeData_t
float rssi;
float snr;
uint8_t* payload;
uint8_t payload_len;
uint8_t node;
uint8_t FPort;
*/
if (bitRead(Lora->settings.flags, TAS_LORA_FLAG_DECODE_ENABLED)) { // LoraOption3 1
if (0x00161600 == Lora->settings.end_node[node_data->node].DevEUIh) { // MerryIoT
if (120 == node_data->FPort) { // MerryIoT door/window Sensor (DW10)
if (9 == node_data->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 = node_data->payload[0];
float battery_volt = (float)(21 + node_data->payload[1]) / 10.0;
int temperature = node_data->payload[2];
int humidity = node_data->payload[3];
uint32_t elapsed_time = node_data->payload[4] | (node_data->payload[5] << 8);
uint32_t events = node_data->payload[6] | (node_data->payload[7] << 8) | (node_data->payload[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_data->node +1, Lora->settings.end_node[node_data->node].DevEUIh, Lora->settings.end_node[node_data->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_data->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_data->node);
return;
}
}
}
else if (0xA840410E == Lora->settings.end_node[node_data->node].DevEUIh) { // Dragino
if (10 == node_data->FPort) { // Dragino LDS02
// 8CD2 01 000010 000000 00 - Door Open, 3.282V
// 0CD2 01 000011 000000 00 - Door Closed
uint8_t status = node_data->payload[0];
float battery_volt = (float)((node_data->payload[1] | (node_data->payload[0] << 8)) &0x3FFF) / 1000;
uint8_t MOD = node_data->payload[2]; // Always 0x01
uint32_t events = node_data->payload[5] | (node_data->payload[4] << 8) | (node_data->payload[3] << 16);
uint32_t open_duration = node_data->payload[8] | (node_data->payload[7] << 8) | (node_data->payload[6] << 16);
uint8_t alarm = node_data->payload[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_data->node +1, Lora->settings.end_node[node_data->node].DevEUIh, Lora->settings.end_node[node_data->node].DevEUIl,
events, open_duration,
bitRead(status, 7),
&battery_volt,
bitRead(alarm, 0));
#endif // USE_LORA_DEBUG
LoraWanPublishHeader(node_data->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_data->node);
return;
}
}
}
// Joined device without decoding
LoraWanPublishHeader(node_data->node);
ResponseAppend_P(PSTR(",\"DevEUIh\":\"%08X\",\"DevEUIl\":\"%08X\",\"FPort\":%d,\"Payload\":["),
Lora->settings.end_node[node_data->node].DevEUIh, Lora->settings.end_node[node_data->node].DevEUIl, node_data->FPort);
for (uint32_t i = 0; i < node_data->payload_len; i++) {
ResponseAppend_P(PSTR("%s%d"), (0==i)?"":",", node_data->payload[i]);
}
ResponseAppend_P(PSTR("]}"));
LoraWanPublishFooter(node_data->node);
}
#endif // USE_LORAWAN_BRIDGE
#endif // USE_SPI_LORA