diff --git a/tasmota/berry/lorawan/decoders/LwDecode.be b/tasmota/berry/lorawan/decoders/LwDecode.be new file mode 100644 index 000000000..80fa46792 --- /dev/null +++ b/tasmota/berry/lorawan/decoders/LwDecode.be @@ -0,0 +1,42 @@ +# Decoder files are modeled on the *.js files found here: +# https://github.com/TheThingsNetwork/lorawan-devices/tree/master/vendor + +var LwRegions = ["EU868", "US915", "IN865","AU915","KZ865","RU864","AS923", "AS923-1","AS923-2","AS923-3"] + +import mqtt +tasmota.cmd('SetOption100 off') +tasmota.cmd('SetOption118 off') +tasmota.cmd('SetOption119 off') +tasmota.cmd('LoRaWanBridge on') +var thisDevice = tasmota.cmd('Status',true)['Status']['Topic'] +var LwDecoders = {} +var LwDeco + +def LwDecode(topic, idx, data, databytes) + import json + + var LwData = json.load(data) + if !LwData.contains('LwReceived') return true end # Processed + var deviceData = LwData['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 decoded = LwDecoders[decoder].decodeUplink(FPort, Payload) + var mqttData = {"LwDecoded":{deviceName:decoded}} + mqtt.publish (topic, json.dump(mqttData)) + end + + return true #processed +end + +mqtt.subscribe("tele/" + thisDevice + "/SENSOR",LwDecode) \ No newline at end of file diff --git a/tasmota/berry/lorawan/decoders/LwDecoderSample.be b/tasmota/berry/lorawan/decoders/LwDecoderSample.be new file mode 100644 index 000000000..54c784b7c --- /dev/null +++ b/tasmota/berry/lorawan/decoders/LwDecoderSample.be @@ -0,0 +1,41 @@ +# LoRaWAN Decoder file for an example DEVICE +# +# References +# Manufacturer Reference: https://xxxxxx <== URL TO Manufacturer's documentation explaining how to decode raw data +# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/xxxxxx/DEVICE + +# import Berry modules, if needed. +# the string module is not needed in this decoder; shown as an example. +import string + +# Declare a new Class +# The Class name should follow this format: LwDecoXXXX where XXXX is the DEVICE +class LwDecoDEVICE + +# Define this function exactly as below. +# 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) + +# Create the data structure (a Berry 'map'), and populate with the VENDOR & DEVICE names + var data = {"Device":"VENDOR DEVICE"} + +# 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 + + else + # Ignore other Fports + end #Fport + + return data + end #decodeUplink() +end #class + +# Set LwDeco variable to the new Class +LwDeco = LwDecoDEVICE # LwDeco: Do not Change + # Value: MUST match the class name defined above. diff --git a/tasmota/berry/lorawan/decoders/README.md b/tasmota/berry/lorawan/decoders/README.md new file mode 100644 index 000000000..ec4a657f5 --- /dev/null +++ b/tasmota/berry/lorawan/decoders/README.md @@ -0,0 +1,12 @@ + ## How to use the LoRaWan Device Decoder feature ## + 1. Download to local computer, then upload to your Tasmota File System: + - [LwDecode.be](https://github.com/arendst/Tasmota/tree/master/tasmota/berry/lorawan/decoders/LwDecode.be) + - the _Device Decoder File(s)_ for your _End Device(s)_ + 2. Add this line to `autoexec.be` in the Tasmota File System (create if necessary) + `load("LwDecode.be")` + 3. Join the End Devices to the Tasmota LoRaWan Bridge. + - e.g. `LoRaWanAppKey yyyyyyyy` where `` is the Tasmota LoRaWan node number. + 4. Inform Tasmota of the name of the _Decoder File_ for each end device with this Tasmota console command: `LoRaWanDecoder ` where `` is the Tasmota LoRaWan node number. + e.g. `LoRaWanDecoder1 LHT52` associates node 1 with the `LHT52.be` decoder file + + 5. Restart Berry with this Tasmota console command: `BrRestart` \ No newline at end of file diff --git a/tasmota/berry/lorawan/decoders/vendors/dragino/LDS02.be b/tasmota/berry/lorawan/decoders/vendors/dragino/LDS02.be new file mode 100644 index 000000000..e02766e2e --- /dev/null +++ b/tasmota/berry/lorawan/decoders/vendors/dragino/LDS02.be @@ -0,0 +1,29 @@ +# LoRaWAN Decoder file for Dragino LDS02 +# +# References +# 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 + +class LwDecoLDS02 + + 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) + + else + # Ignore other Fports + end #Fport + + return data + end #decodeUplink() +end #class + +LwDeco = LwDecoLDS02 diff --git a/tasmota/berry/lorawan/decoders/vendors/dragino/LHT52.be b/tasmota/berry/lorawan/decoders/vendors/dragino/LHT52.be new file mode 100644 index 000000000..16cafd15e --- /dev/null +++ b/tasmota/berry/lorawan/decoders/vendors/dragino/LHT52.be @@ -0,0 +1,53 @@ +# LoRaWAN Decoder file for Dragino LHT52 +# +# References +# User Manual: https://wiki.dragino.com/xwiki/bin/view/Main/User%20Manual%20for%20LoRaWAN%20End%20Nodes/LHT52%20-%20LoRaWAN%20Temperature%20%26%20Humidity%20Sensor%20User%20Manual/ +# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/dragino/lht52.js + +import string + +class LwDecoLHT52 + + static def decodeUplink(FPort, Bytes) + var data = {"Device":"Dragino LHT52"} + + ## 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[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 + + 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]) + + ## 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 + + return data + end #decodeUplink() +end #class + +LwDeco = LwDecoLHT52 diff --git a/tasmota/berry/lorawan/decoders/vendors/dragino/LHT65.be b/tasmota/berry/lorawan/decoders/vendors/dragino/LHT65.be new file mode 100644 index 000000000..f799165a6 --- /dev/null +++ b/tasmota/berry/lorawan/decoders/vendors/dragino/LHT65.be @@ -0,0 +1,99 @@ +# LoRaWAN Decoder file for Dragino LHT65 +# +# References +# User Manual: https://www.dragino.com/downloads/downloads/LHT65/UserManual/LHT65_Temperature_Humidity_Sensor_UserManual_v1.8.5.pdf +# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/dragino/lht65.js + +import string +var LHT65_BatteryStatus = ["Very low <= 2.5V","Low <=2.55V","OK","Good >= 2.65V"] + +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 + + ## 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]) + 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() +end # class + +LwDeco = LwDecoLHT65 \ No newline at end of file