From 4f4bf7c61b500d35c156e8c37a9b93c617d775a6 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 22 May 2025 17:32:16 +0200 Subject: [PATCH] Add GUI support to LoRaWan decoders --- tasmota/berry/lorawan/decoders/LwDecode.be | 98 +++++--- .../berry/lorawan/decoders/LwDecoderSample.be | 50 +++- .../lorawan/decoders/vendors/dragino/LDS02.be | 56 +++-- .../lorawan/decoders/vendors/dragino/LHT52.be | 108 ++++++--- .../lorawan/decoders/vendors/dragino/LHT65.be | 223 +++++++++++------- .../lorawan/decoders/vendors/merryiot/DW10.be | 75 ++++-- 6 files changed, 410 insertions(+), 200 deletions(-) diff --git a/tasmota/berry/lorawan/decoders/LwDecode.be b/tasmota/berry/lorawan/decoders/LwDecode.be index 7a0f3efd7..923067744 100644 --- a/tasmota/berry/lorawan/decoders/LwDecode.be +++ b/tasmota/berry/lorawan/decoders/LwDecode.be @@ -2,41 +2,77 @@ # https://github.com/TheThingsNetwork/lorawan-devices/tree/master/vendor var LwRegions = ["EU868", "US915", "IN865","AU915","KZ865","RU864","AS923", "AS923-1","AS923-2","AS923-3"] +var LwDeco import mqtt + +class lwdecode_cls + var thisDevice + var LwDecoders + + def init() + self.thisDevice = tasmota.cmd('Status',true)['Status']['Topic'] + self.LwDecoders = {} + + if global.lwdecode_driver + global.lwdecode_driver.stop() # Let previous instance bail out cleanly + end + tasmota.add_driver(global.lwdecode_driver := self) + tasmota.add_rule("LwReceived", /value, trigger, payload -> self.LwDecode(payload)) + end + + def LwDecode(data) + import json + + var deviceData = data['LwReceived'] + var deviceName = deviceData.keys()() + var Node = deviceData[deviceName]['Node'] + var Payload = deviceData[deviceName]['Payload'] + var FPort = deviceData[deviceName]['FPort'] + var decoder = deviceData[deviceName].find('Decoder') + if !decoder + return true + end + + if !self.LwDecoders.find(decoder) + LwDeco = nil + load(decoder) #sets LwDeco if found + if LwDeco + self.LwDecoders.insert(decoder, LwDeco) + end + end + + if Payload.size() && self.LwDecoders.find(decoder) + var topic = "tele/" + self.thisDevice + "/SENSOR" + var decoded = self.LwDecoders[decoder].decodeUplink(Node, FPort, Payload) + var mqttData = {"LwDecoded":{deviceName:decoded}} + mqtt.publish(topic, json.dump(mqttData)) + end + + return true #processed + end + + #------------------------------------------------------------ + Display sensor value in the web UI and react to button + Called every WebRefresh time + ------------------------------------------------------------# + def web_sensor() + import string + + var msg = "" + for decoder: self.LwDecoders + msg = msg + decoder.add_web_sensor() + end + if msg + tasmota.web_send_decimal(msg) + end + end +end + +lwdecode = lwdecode_cls() + tasmota.cmd('SetOption100 off') # Keep LwReceived in JSON message tasmota.cmd('SetOption118 off') # Keep SENSOR as subtopic name tasmota.cmd('SetOption119 off') # Keep device address in JSON message tasmota.cmd('SetOption147 on') # Hide LwReceived MQTT message but keep rule processing tasmota.cmd('LoRaWanBridge on') -var thisDevice = tasmota.cmd('Status',true)['Status']['Topic'] -var LwDecoders = {} -var LwDeco - -def LwDecode(data) - import json - - var deviceData = data['LwReceived'] - var deviceName = deviceData.keys()() - var Payload = deviceData[deviceName]['Payload'] - var FPort = deviceData[deviceName]['FPort'] - var decoder = deviceData[deviceName].find('Decoder') - if !decoder return true end - - if !LwDecoders.find(decoder) - LwDeco = nil - load(decoder) #sets LwDeco if found - if LwDeco LwDecoders.insert(decoder, LwDeco) end - end - - if Payload.size() && LwDecoders.find(decoder) - var topic = "tele/" + thisDevice + "/SENSOR" - var decoded = LwDecoders[decoder].decodeUplink(FPort, Payload) - var mqttData = {"LwDecoded":{deviceName:decoded}} - mqtt.publish (topic, json.dump(mqttData)) - end - - return true #processed -end - -tasmota.add_rule("LwReceived", /value, trigger, payload -> LwDecode(payload)) \ No newline at end of file diff --git a/tasmota/berry/lorawan/decoders/LwDecoderSample.be b/tasmota/berry/lorawan/decoders/LwDecoderSample.be index 54c784b7c..c34f05d32 100644 --- a/tasmota/berry/lorawan/decoders/LwDecoderSample.be +++ b/tasmota/berry/lorawan/decoders/LwDecoderSample.be @@ -8,6 +8,9 @@ # the string module is not needed in this decoder; shown as an example. import string +# define a global map for storage of GUI parameters +global.DEVICENodes = {} + # Declare a new Class # The Class name should follow this format: LwDecoXXXX where XXXX is the DEVICE class LwDecoDEVICE @@ -16,24 +19,49 @@ class LwDecoDEVICE # Name: decodeUplink Must use this name, and arguments # Arguments: FPort The Fport number used by the End Device for this packet of data # Bytes The Raw Data Payload - static def decodeUplink(FPort, Bytes) + static def decodeUplink(Node, FPort, Bytes) # Create the data structure (a Berry 'map'), and populate with the VENDOR & DEVICE names - var data = {"Device":"VENDOR DEVICE"} - + var data = {"Device":"VENDOR DEVICE"} + data.insert("Node", Node) + + var valid_values = false + var gui_value # For each Fport used by the DEVICE: # write a decoder that continues to populate the data structure by parsing the Raw Data Payload - if 2 == FPort && 11 == Bytes.size() #Example: For this device, Data from Fport 2 should have 11 bytes - data.insert("LABEL1", Bytes[0] | Bytes[1] <<8 ) #Example Numerical value = Bytes[1]*256 + Bytes[0] - data.insert("LABEL2", "TEXT VALUE") #Example Text value + if 2 == FPort && 11 == Bytes.size() #Example: For this device, Data from Fport 2 should have 11 bytes + data.insert("LABEL1", Bytes[0] | Bytes[1] <<8 ) #Example Numerical value = Bytes[1]*256 + Bytes[0] + gui_value = Bytes[0] | Bytes[1] <<8 + data.insert("LABEL2", "TEXT VALUE") #Example Text value - else - # Ignore other Fports - end #Fport - return data - end #decodeUplink() + var valid_values = true + + else + # Ignore other Fports + end #Fport + + if valid_values + if global.DEVICENodes.find(Node) + global.DEVICENodes.remove(Node) + end + global.DEVICENodes.insert(Node, [Node, gui_value]) + end + + return data + end #decodeUplink() + + static def add_web_sensor() + var msg = "" + for sensor: global.DEVICENodes + msg += string.format("{s}DEVICE_%i Gui value{m}%i{e}", + sensor[0], sensor[1]) + end + + return msg + end #add_web_sensor() + end #class # Set LwDeco variable to the new Class diff --git a/tasmota/berry/lorawan/decoders/vendors/dragino/LDS02.be b/tasmota/berry/lorawan/decoders/vendors/dragino/LDS02.be index e02766e2e..df2b7fed9 100644 --- a/tasmota/berry/lorawan/decoders/vendors/dragino/LDS02.be +++ b/tasmota/berry/lorawan/decoders/vendors/dragino/LDS02.be @@ -4,26 +4,50 @@ # LHT52 User Manual: https://wiki.dragino.com/xwiki/bin/view/Main/User%20Manual%20for%20LoRaWAN%20End%20Nodes/LDS02%20-%20LoRaWAN%20Door%20Sensor%20User%20Manual/ # TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/dragino/lds02.js +import string + +global.lds02Nodes = {} + class LwDecoLDS02 + static def decodeUplink(Node, FPort, Bytes) + var data = {"Device":"Dragino LDS02"} + data.insert("Node", Node) - static def decodeUplink(FPort, Bytes) - var data = {"Device":"Dragino LDS02"} - - ## SENSOR DATA ## - - if 10 == FPort && Bytes.size() == 10 - data.insert("DoorOpen", ( Bytes[0] & 0x80 ) ? true : false ) - data.insert("Battery_mV", ( Bytes[1] | (Bytes[0] << 8) & 0x3FFF )) - data.insert("DoorOpenEvents", Bytes[5] | (Bytes[4] << 8) | (Bytes[3] << 16 )) - data.insert("DoorOpenLastDuration_mins", Bytes[8] | (Bytes[7] << 8) | (Bytes[6] << 16)) - data.insert("Alarm", (Bytes[9] & 0x01 ) ? true : false) + var valid_values = false + var door_open + ## SENSOR DATA ## + if 10 == FPort && Bytes.size() == 10 + door_open = ( Bytes[0] & 0x80 ) ? 1 : 0 + data.insert("DoorOpen", ( door_open ) ? true : false) + data.insert("Battery_mV", ( Bytes[1] | (Bytes[0] << 8) & 0x3FFF )) + data.insert("DoorOpenEvents", Bytes[5] | (Bytes[4] << 8) | (Bytes[3] << 16 )) + data.insert("DoorOpenLastDuration_mins", Bytes[8] | (Bytes[7] << 8) | (Bytes[6] << 16)) + data.insert("Alarm", (Bytes[9] & 0x01 ) ? true : false) + valid_values = true - else - # Ignore other Fports - end #Fport + else + # Ignore other Fports + end #Fport - return data - end #decodeUplink() + if valid_values + if global.lds02Nodes.find(Node) + global.lds02Nodes.remove(Node) + end + global.lds02Nodes.insert(Node, [Node, door_open]) + end + + return data + end #decodeUplink() + + static def add_web_sensor() + var msg = "" + for sensor: global.lds02Nodes + msg += string.format("{s}LDS02_%i Door{m}%s{e}", + sensor[0], (sensor[1]) ? "Open" : "Closed") + end + + return msg + end end #class LwDeco = LwDecoLDS02 diff --git a/tasmota/berry/lorawan/decoders/vendors/dragino/LHT52.be b/tasmota/berry/lorawan/decoders/vendors/dragino/LHT52.be index 9b4450771..f5d6bf425 100644 --- a/tasmota/berry/lorawan/decoders/vendors/dragino/LHT52.be +++ b/tasmota/berry/lorawan/decoders/vendors/dragino/LHT52.be @@ -6,50 +6,86 @@ import string +global.lht52Nodes = {} + class LwDecoLHT52 + static def decodeUplink(Node, FPort, Bytes) + var data = {"Device":"Dragino LHT52"} + data.insert("Node", Node) - static def decodeUplink(FPort, Bytes) - var data = {"Device":"Dragino LHT52"} - - ## SENSOR DATA ## - if 2 == FPort && Bytes.size() == 11 - var TempC + var valid_values = false + var temp_int + var humidity + var temp_ext = 1000 + ## SENSOR DATA ## + if 2 == FPort && Bytes.size() == 11 + var TempC - TempC = Bytes[0]<<8 | Bytes[1] - if Bytes[0]>0x7F TempC -= 0x10000 end - TempC /= 100.0 - data.insert("TempC_Internal",TempC) + TempC = Bytes[0] << 8 | Bytes[1] + if Bytes[0] > 0x7F + TempC -= 0x10000 + end + TempC /= 100.0 + data.insert("TempC_Internal", TempC) + temp_int = TempC - TempC = Bytes[4]<<8 | Bytes[5] - if 0x7FFF == TempC - data.insert("Ext_SensorConnected", false) - else - data.insert("Ext_SensorConnected", true) - if Bytes[4]>0x7F TempC -= 0x10000 end - TempC /= 100.0 - data.insert("TempC_External",TempC) - end + TempC = Bytes[4] << 8 | Bytes[5] + if 0x7FFF == TempC + data.insert("Ext_SensorConnected", false) + else + data.insert("Ext_SensorConnected", true) + if Bytes[4] > 0x7F + TempC -= 0x10000 + end + TempC /= 100.0 + data.insert("TempC_External", TempC) + temp_ext = TempC + end - data.insert("Hum_Internal", ((Bytes[2]<<8 ) | Bytes[3])/10.0) - data.insert("Ext_SensorType", Bytes[6]) -# data.insert("Systimestamp",(Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10]) - var epoch = (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10] - data.insert("Systimestamp",tasmota.time_str(epoch)) + data.insert("Hum_Internal", ((Bytes[2] << 8) | Bytes[3]) / 10.0) + humidity = ((Bytes[2] << 8) | Bytes[3]) / 10.0 + data.insert("Ext_SensorType", Bytes[6]) + var epoch = (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10] + data.insert("Systimestamp",tasmota.time_str(epoch)) + valid_values = true - ## STATUS DATA ## - elif 5 == FPort && Bytes.size() == 7 - data.insert("Sensor_Model",Bytes[0]) - data.insert("Firmware_Version", f'v{Bytes[1]:%u}.{Bytes[2]>>4:%u}.{Bytes[2]&0xF:%u}') - data.insert("Freq_Band",LwRegions[Bytes[3]-1]) - data.insert("Sub_Band",Bytes[4]) - data.insert("Bat_mV",(Bytes[5] << 8) | Bytes[6]) + ## STATUS DATA ## + elif 5 == FPort && Bytes.size() == 7 + data.insert("Sensor_Model",Bytes[0]) + data.insert("Firmware_Version", f'v{Bytes[1]:%u}.{Bytes[2]>>4:%u}.{Bytes[2]&0xF:%u}') + data.insert("Freq_Band",LwRegions[Bytes[3]-1]) + data.insert("Sub_Band",Bytes[4]) + data.insert("Bat_mV",(Bytes[5] << 8) | Bytes[6]) - else - # Ignore other Fports - end #Fport + else + # Ignore other Fports + end #Fport - return data - end #decodeUplink() + if valid_values + if global.lht52Nodes.find(Node) + global.lht52Nodes.remove(Node) + end + global.lht52Nodes.insert(Node, [Node, temp_int, humidity, temp_ext]) + end + + return data + end #decodeUplink() + + static def add_web_sensor() + var msg = "" + for sensor: global.lht52Nodes + msg += string.format("{s}LHT52_%i Temperature{m}%.1f °C{e}".. + "{s}LHT52_%i Humidity{m}%.1f %%{e}", + sensor[0], sensor[1], + sensor[0], sensor[2]) + if sensor[3] < 1000 + msg += string.format("{s}LHT52_%i Temperature ext.{m}%.1f °C{e}", + sensor[0], sensor[3]) + end + end + + return msg + end end #class LwDeco = LwDecoLHT52 diff --git a/tasmota/berry/lorawan/decoders/vendors/dragino/LHT65.be b/tasmota/berry/lorawan/decoders/vendors/dragino/LHT65.be index c31c1cda5..d8bea9b8f 100644 --- a/tasmota/berry/lorawan/decoders/vendors/dragino/LHT65.be +++ b/tasmota/berry/lorawan/decoders/vendors/dragino/LHT65.be @@ -6,96 +6,147 @@ import string var LHT65_BatteryStatus = ["Very low <= 2.5V","Low <=2.55V","OK","Good >= 2.65V"] +global.lht65Nodes = {} class LwDecoLHT65 - static def decodeUplink(FPort, Bytes) - var data = {"Device":"Dragino LHT65"} - data.insert("poll_message_status",(Bytes[6] & 0x40) >> 6) - var Ext = Bytes[6] & 0x0F #External sensor type - var NoConnect = (Bytes[6] & 0x80) >> 7 + static def decodeUplink(FPort, Bytes) + var data = {"Device":"Dragino LHT65"} + data.insert("Node", Node) + data.insert("poll_message_status",(Bytes[6] & 0x40) >> 6) + + var Ext = Bytes[6] & 0x0F #External sensor type + var NoConnect = (Bytes[6] & 0x80) >> 7 + + var valid_values = false + var temp_int = 1000 + var humidity + var temp_ext = 1000 + if global.lht65Nodes.find(Node) + temp_int = global.lht65Nodes.item(Node)[1] + humidity = global.lht65Nodes.item(Node)[2] + temp_ext = global.lht65Nodes.item(Node)[3] + end + ## SENSOR DATA ## + if 2 == FPort && Bytes.size() == 11 + var TempC + + if Ext == 9 #Sensor E3, Temperature Sensor, Datalog Mod + TempC = ((Bytes[0] << 8) | Bytes[1]) + if 0x7FFF == TempC + data.insert("Ext_SensorConnected", false) + else + data.insert("Ext_SensorConnected", true) + if Bytes[0]>0x7F + TempC -= 0x10000 + end + temp_ext = TempC / 100.0 + data.insert("TempC_External", temp_ext) + valid_values = true + end + data.insert("Bat_status", LHT65_BatteryStatus[Bytes[4] >> 6]) + else + data.insert("BatV",(((Bytes[0] << 8) | Bytes[1]) & 0x3fff) / 1000.0) + data.insert("Bat_status", LHT65_BatteryStatus[Bytes[0] >> 6]) + end + + if Ext != 0x0F + TempC = ((Bytes[2] << 8) | Bytes[3]) + if Bytes[2]>0x7F + TempC -= 0x10000 + end + temp_int = TempC / 100.0 + data.insert("TempC_Internal", temp_int) + humidity = (((Bytes[4] << 8) | Bytes[5]) / 10.0) + data.insert("Hum_Internal" , humidity) + valid_values = true + end + + if NoConnect + data.insert('No_connect','No connection to external sensor') + end + + if 0 == Ext + data.insert("Ext_sensor", 'No external sensor') + elif 1==Ext + data.insert("Ext_sensor",'Temperature Sensor') + TempC = ((Bytes[7] << 8) | Bytes[8]) + if 0x7FFF == TempC + data.insert("Ext_SensorConnected", false) + else + data.insert("Ext_SensorConnected", true) + if Bytes[7]>0x7F + TempC -= 0x10000 + end + temp_ext = TempC / 100.0 + data.insert("TempC_External", temp_ext) + valid_values = true + end + elif 4 == Ext + data.insert("Work_mode", 'Interrupt Sensor send') + data.insert("Exti_pin_level", Bytes[7] ? 'High' : 'Low') + data.insert("Exti_status", Bytes[8] ? 'True' : 'False') + elif 5 == Ext + data.insert("Work_mode", 'Illumination Sensor') + data.insert("ILL_lx", (Bytes[7] << 8) | Bytes[8]) + elif 6 == Ext + data.insert("Work_mode", 'ADC Sensor') + data.insert("ADC_V", ((Bytes[7] << 8) | Bytes[8]) / 1000.0) + elif 7 == Ext + data.insert("Work_mode ", 'Interrupt Sensor count') + data.insert("Exit_count", (Bytes[7] << 8) | Bytes[8]) + elif 8 == Ext + data.insert("Work_mode", 'Interrupt Sensor count') + data.insert("Exit_count", (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10]) + elif 9 == Ext + data.insert("Work_mode", 'DS18B20 & timestamp') + var epoch = (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10] + data.insert("Systimestamp",tasmota.time_str(epoch)) + elif 15 == Ext + data.insert("Work_mode",'DS18B20ID') + data.insert("ID",f"{Bytes[2]:%02X}" + f"{Bytes[3]:%02X}" + f"{Bytes[4]:%02X}" + f"{Bytes[5]:%02X}" + f"{Bytes[6]:%02X}" + f"{Bytes[8]:%02X}" + f"{Bytes[9]:%02X}" + f"{Bytes[10]:%02X}" ) + else + data.insert("Ext_sensor", 'Unknown') + end + + elif 5 == FPort && Bytes.size() == 7 + data.insert("Sensor_Model",Bytes[0]) + data.insert("Firmware_Version", f'v{Bytes[1]:%u}.{Bytes[2]>>4:%u}.{Bytes[2]&0xF:%u}') + data.insert("Freq_Band",LwRegions[Bytes[3]-1]) + data.insert("Sub_Band",Bytes[4]) + data.insert("Bat_mV",(Bytes[5] << 8) | Bytes[6]) - ## SENSOR DATA ## - if 2 == FPort && Bytes.size() == 11 - var TempC - if Ext == 9 #Sensor E3, Temperature Sensor, Datalog Mod - TempC = ((Bytes[0] << 8) | Bytes[1]) - if 0x7FFF == TempC - data.insert("Ext_SensorConnected", false) - else - data.insert("Ext_SensorConnected", true) - if Bytes[0]>0x7F TempC -= 0x10000 end - data.insert("TempC_External", TempC / 100.0) - end - data.insert("Bat_status", LHT65_BatteryStatus[Bytes[4] >> 6]) - else - data.insert("BatV",(((Bytes[0] << 8) | Bytes[1]) & 0x3fff) / 1000.0) - data.insert("Bat_status", LHT65_BatteryStatus[Bytes[0] >> 6]) - end - - if Ext != 0x0F - TempC = ((Bytes[2] << 8) | Bytes[3]) - if Bytes[2]>0x7F TempC -= 0x10000 end - data.insert("TempC_Internal", ( TempC / 100.0)) - data.insert("Hum_Internal" , (((Bytes[4] << 8) | Bytes[5]) / 10.0)) - end - - if NoConnect - data.insert('No_connect','No connection to external sensor') - end - - if 0==Ext - data.insert("Ext_sensor", 'No external sensor') - elif 1==Ext - data.insert("Ext_sensor",'Temperature Sensor') - TempC = ((Bytes[7] << 8) | Bytes[8]) - if 0x7FFF == TempC - data.insert("Ext_SensorConnected", false) else - data.insert("Ext_SensorConnected", true) - if Bytes[7]>0x7F TempC -= 0x10000 end - data.insert("TempC_External", TempC / 100.0) - end - elif 4==Ext - data.insert("Work_mode", 'Interrupt Sensor send') - data.insert("Exti_pin_level", Bytes[7] ? 'High' : 'Low') - data.insert("Exti_status", Bytes[8] ? 'True' : 'False') - elif 5==Ext - data.insert("Work_mode", 'Illumination Sensor') - data.insert("ILL_lx", (Bytes[7] << 8) | Bytes[8]) - elif 6==Ext - data.insert("Work_mode", 'ADC Sensor') - data.insert("ADC_V", ((Bytes[7] << 8) | Bytes[8]) / 1000.0) - elif 7==Ext - data.insert("Work_mode ", 'Interrupt Sensor count') - data.insert("Exit_count", (Bytes[7] << 8) | Bytes[8]) - elif 8==Ext - data.insert("Work_mode", 'Interrupt Sensor count') - data.insert("Exit_count", (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10]) - elif 9==Ext - data.insert("Work_mode", 'DS18B20 & timestamp') -# data.insert("Systimestamp", (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10]) - var epoch = (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10] - data.insert("Systimestamp",tasmota.time_str(epoch)) - elif 15==Ext - data.insert("Work_mode",'DS18B20ID') - data.insert("ID",f"{Bytes[2]:%02X}" + f"{Bytes[3]:%02X}" + f"{Bytes[4]:%02X}" + f"{Bytes[5]:%02X}" + f"{Bytes[6]:%02X}" + f"{Bytes[8]:%02X}" + f"{Bytes[9]:%02X}" + f"{Bytes[10]:%02X}" ) - else - data.insert("Ext_sensor", 'Unknown') - end - - elif 5 == FPort && Bytes.size() == 7 - data.insert("Sensor_Model",Bytes[0]) - data.insert("Firmware_Version", f'v{Bytes[1]:%u}.{Bytes[2]>>4:%u}.{Bytes[2]&0xF:%u}') - data.insert("Freq_Band",LwRegions[Bytes[3]-1]) - data.insert("Sub_Band",Bytes[4]) - data.insert("Bat_mV",(Bytes[5] << 8) | Bytes[6]) - - else - # Ignore other Fports - end #Fport - - return data - end # decodeUplink() + # Ignore other Fports + end #Fport + + if valid_values + if global.lht65Nodes.find(Node) + global.lht65Nodes.remove(Node) + end + global.lht65Nodes.insert(Node, [Node, temp_int, humidity, temp_ext]) + end + + + return data + end # decodeUplink() + + static def add_web_sensor() + var msg = "" + for sensor: global.lht65Nodes + if sensor[1] < 1000 + msg += string.format("{s}LHT65_%i Temperature{m}%.1f °C{e}".. + "{s}LHT65_%i Humidity{m}%.1f %%{e}", + sensor[0], sensor[1], + sensor[0], sensor[2]) + end + if sensor[3] < 1000 + msg += string.format("{s}LHT65_%i Temperature ext.{m}%.1f °C{e}", + sensor[0], sensor[3]) + end + end + + return msg + end end # class LwDeco = LwDecoLHT65 \ No newline at end of file diff --git a/tasmota/berry/lorawan/decoders/vendors/merryiot/DW10.be b/tasmota/berry/lorawan/decoders/vendors/merryiot/DW10.be index 860e9d409..a529fc21e 100644 --- a/tasmota/berry/lorawan/decoders/vendors/merryiot/DW10.be +++ b/tasmota/berry/lorawan/decoders/vendors/merryiot/DW10.be @@ -4,30 +4,65 @@ # DW10 Product information: https://www.browan.com/products-detail/OpenClose-Sensor-EBL-LoRaWAN/ # Browan JS Decoder (TTN): https://www.browan.com/member/login/?refererUrl=https%3A%2F%2Fwww.browan.com%2Fproducts-detail%2FOpenClose-Sensor-EBL-LoRaWAN%2F +import string + +global.dw10Nodes = {} + class LwDecoDW10 + static def decodeUplink(Node, FPort, Bytes) + var data = {"Device":"MerryIoT DW10"} + data.insert("Node", Node) - static def decodeUplink(FPort, Bytes) - var data = {"Device":"MerryIoT DW10"} - - ## SENSOR DATA ## - - if 120 == FPort && Bytes.size() == 9 - data.insert("DoorOpen", ( Bytes[0] & 0x01 ) ? true : false ) - data.insert("ButtonPress", ( Bytes[0] & 0x02 ) ? true : false ) - data.insert("TamperDetect", ( Bytes[0] & 0x04 ) ? true : false ) - data.insert("TiltDetect", ( Bytes[0] & 0x08 ) ? true : false ) - data.insert("Battery_mV", ( 21 + Bytes[1] ) * 100 ) - data.insert("Temperature_C", Bytes[2]) - data.insert("Humidity", Bytes[3]) - data.insert("DoorOpenLastDuration_mins", Bytes[4] | (Bytes[5] << 8)) - data.insert("DoorOpenEvents", Bytes[6] | (Bytes[7] << 8) | (Bytes[8] << 16 )) + var valid_values = false + var door_open + var button_pressed + var temperature + var humidity + ## SENSOR DATA ## + if 120 == FPort && Bytes.size() == 9 + door_open = ( Bytes[0] & 0x01 ) ? 1 : 0 + data.insert("DoorOpen", ( door_open ) ? true : false ) + button_pressed = ( Bytes[0] & 0x02 ) ? 1 : 0 + data.insert("ButtonPress", ( button_pressed ) ? true : false ) + data.insert("TamperDetect", ( Bytes[0] & 0x04 ) ? true : false ) + data.insert("TiltDetect", ( Bytes[0] & 0x08 ) ? true : false ) + data.insert("Battery_mV", ( 21 + Bytes[1] ) * 100 ) + data.insert("Temperature_C", Bytes[2]) + temperature = Bytes[2] + data.insert("Humidity", Bytes[3]) + humidity = Bytes[3] + data.insert("DoorOpenLastDuration_mins", Bytes[4] | (Bytes[5] << 8)) + data.insert("DoorOpenEvents", Bytes[6] | (Bytes[7] << 8) | (Bytes[8] << 16 )) + valid_values = true - else - # Ignore other Fports - end #Fport + else + # Ignore other Fports + end #Fport - return data - end #decodeUplink() + if valid_values + if global.dw10Nodes.find(Node) + global.dw10Nodes.remove(Node) + end + global.dw10Nodes.insert(Node, [Node, door_open, button_pressed, temperature, humidity]) + end + + return data + end #decodeUplink() + + static def add_web_sensor() + var msg = "" + for sensor: global.dw10Nodes + msg += string.format("{s}DW10_%i Door{m}%s{e}".. + "{s}DW10_%i Button{m}%s{e}".. + "{s}DW10_%i Temperature{m}%.1f °C{e}".. + "{s}DW10_%i Humidity{m}%.1f %%{e}", + sensor[0], (sensor[1]) ? "Open" : "Closed", + sensor[0], (sensor[2]) ? "Pressed" : "Released", + sensor[0], sensor[3], + sensor[0], sensor[4]) + end + return msg + end end #class LwDeco = LwDecoDW10