Skip to content

Commit 08e8f0b

Browse files
robclark56UBWH
andauthored
LoRaWan Decode Files (#23412)
* Add LoraWanDecoders Documentation to follow in separate PR. See #23394 * Create README.md Quick Guide * Update README.md Typo --------- Co-authored-by: UBWH <clark@ubwh.com.au>
1 parent ca9df09 commit 08e8f0b

File tree

6 files changed

+276
-0
lines changed

6 files changed

+276
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Decoder files are modeled on the *.js files found here:
2+
# https://github.com/TheThingsNetwork/lorawan-devices/tree/master/vendor
3+
4+
var LwRegions = ["EU868", "US915", "IN865","AU915","KZ865","RU864","AS923", "AS923-1","AS923-2","AS923-3"]
5+
6+
import mqtt
7+
tasmota.cmd('SetOption100 off')
8+
tasmota.cmd('SetOption118 off')
9+
tasmota.cmd('SetOption119 off')
10+
tasmota.cmd('LoRaWanBridge on')
11+
var thisDevice = tasmota.cmd('Status',true)['Status']['Topic']
12+
var LwDecoders = {}
13+
var LwDeco
14+
15+
def LwDecode(topic, idx, data, databytes)
16+
import json
17+
18+
var LwData = json.load(data)
19+
if !LwData.contains('LwReceived') return true end # Processed
20+
var deviceData = LwData['LwReceived']
21+
var deviceName = deviceData.keys()()
22+
var Payload = deviceData[deviceName]['Payload']
23+
var FPort = deviceData[deviceName]['FPort']
24+
var decoder = deviceData[deviceName].find('Decoder')
25+
if !decoder return true end
26+
27+
if !LwDecoders.find(decoder)
28+
LwDeco = nil
29+
load(decoder) #sets LwDeco if found
30+
if LwDeco LwDecoders.insert(decoder, LwDeco) end
31+
end
32+
33+
if Payload.size() && LwDecoders.find(decoder)
34+
var decoded = LwDecoders[decoder].decodeUplink(FPort, Payload)
35+
var mqttData = {"LwDecoded":{deviceName:decoded}}
36+
mqtt.publish (topic, json.dump(mqttData))
37+
end
38+
39+
return true #processed
40+
end
41+
42+
mqtt.subscribe("tele/" + thisDevice + "/SENSOR",LwDecode)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# LoRaWAN Decoder file for an example DEVICE
2+
#
3+
# References
4+
# Manufacturer Reference: https://xxxxxx <== URL TO Manufacturer's documentation explaining how to decode raw data
5+
# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/xxxxxx/DEVICE
6+
7+
# import Berry modules, if needed.
8+
# the string module is not needed in this decoder; shown as an example.
9+
import string
10+
11+
# Declare a new Class
12+
# The Class name should follow this format: LwDecoXXXX where XXXX is the DEVICE
13+
class LwDecoDEVICE
14+
15+
# Define this function exactly as below.
16+
# Name: decodeUplink Must use this name, and arguments
17+
# Arguments: FPort The Fport number used by the End Device for this packet of data
18+
# Bytes The Raw Data Payload
19+
static def decodeUplink(FPort, Bytes)
20+
21+
# Create the data structure (a Berry 'map'), and populate with the VENDOR & DEVICE names
22+
var data = {"Device":"VENDOR DEVICE"}
23+
24+
# For each Fport used by the DEVICE:
25+
# write a decoder that continues to populate the data structure by parsing the Raw Data Payload
26+
27+
if 2 == FPort && 11 == Bytes.size() #Example: For this device, Data from Fport 2 should have 11 bytes
28+
data.insert("LABEL1", Bytes[0] | Bytes[1] <<8 ) #Example Numerical value = Bytes[1]*256 + Bytes[0]
29+
data.insert("LABEL2", "TEXT VALUE") #Example Text value
30+
31+
else
32+
# Ignore other Fports
33+
end #Fport
34+
35+
return data
36+
end #decodeUplink()
37+
end #class
38+
39+
# Set LwDeco variable to the new Class
40+
LwDeco = LwDecoDEVICE # LwDeco: Do not Change
41+
# Value: MUST match the class name defined above.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## How to use the LoRaWan Device Decoder feature ##
2+
1. Download to local computer, then upload to your Tasmota File System:
3+
- [LwDecode.be](https://github.com/arendst/Tasmota/tree/master/tasmota/berry/lorawan/decoders/LwDecode.be)
4+
- the _Device Decoder File(s)_ for your _End Device(s)_
5+
2. Add this line to `autoexec.be` in the Tasmota File System (create if necessary)
6+
`load("LwDecode.be")`
7+
3. Join the End Devices to the Tasmota LoRaWan Bridge.
8+
- e.g. `LoRaWanAppKey<x> yyyyyyyy` where `<x>` is the Tasmota LoRaWan node number.
9+
4. Inform Tasmota of the name of the _Decoder File_ for each end device with this Tasmota console command: `LoRaWanDecoder<x> <decoderfile>` where `<x>` is the Tasmota LoRaWan node number.
10+
e.g. `LoRaWanDecoder1 LHT52` associates node 1 with the `LHT52.be` decoder file
11+
12+
5. Restart Berry with this Tasmota console command: `BrRestart`
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# LoRaWAN Decoder file for Dragino LDS02
2+
#
3+
# References
4+
# LHT52 User Manual: https://wiki.dragino.com/xwiki/bin/view/Main/User%20Manual%20for%20LoRaWAN%20End%20Nodes/LDS02%20-%20LoRaWAN%20Door%20Sensor%20User%20Manual/
5+
# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/dragino/lds02.js
6+
7+
class LwDecoLDS02
8+
9+
static def decodeUplink(FPort, Bytes)
10+
var data = {"Device":"Dragino LDS02"}
11+
12+
## SENSOR DATA ##
13+
14+
if 10 == FPort && Bytes.size() == 10
15+
data.insert("DoorOpen", ( Bytes[0] & 0x80 ) ? true : false )
16+
data.insert("Battery_mV", ( Bytes[1] | (Bytes[0] << 8) & 0x3FFF ))
17+
data.insert("DoorOpenEvents", Bytes[5] | (Bytes[4] << 8) | (Bytes[3] << 16 ))
18+
data.insert("DoorOpenLastDuration_mins", Bytes[8] | (Bytes[7] << 8) | (Bytes[6] << 16))
19+
data.insert("Alarm", (Bytes[9] & 0x01 ) ? true : false)
20+
21+
else
22+
# Ignore other Fports
23+
end #Fport
24+
25+
return data
26+
end #decodeUplink()
27+
end #class
28+
29+
LwDeco = LwDecoLDS02
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# LoRaWAN Decoder file for Dragino LHT52
2+
#
3+
# References
4+
# 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/
5+
# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/dragino/lht52.js
6+
7+
import string
8+
9+
class LwDecoLHT52
10+
11+
static def decodeUplink(FPort, Bytes)
12+
var data = {"Device":"Dragino LHT52"}
13+
14+
## SENSOR DATA ##
15+
if 2 == FPort && Bytes.size() == 11
16+
var TempC
17+
18+
TempC = Bytes[0]<<8 | Bytes[1]
19+
if Bytes[0]>0x7F TempC -= 0x10000 end
20+
TempC /= 100.0
21+
data.insert("TempC_Internal",TempC)
22+
23+
TempC = Bytes[4]<<8 | Bytes[5]
24+
if 0x7FFF == TempC
25+
data.insert("Ext_SensorConnected", false)
26+
else
27+
data.insert("Ext_SensorConnected", true)
28+
if Bytes[4]>0x7F TempC -= 0x10000 end
29+
TempC /= 100.0
30+
data.insert("TempC_External",TempC)
31+
end
32+
33+
data.insert("Hum_Internal", ((Bytes[2]<<8 ) | Bytes[3])/10.0)
34+
data.insert("Ext_SensorType", Bytes[6])
35+
data.insert("Systimestamp",(Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10])
36+
37+
## STATUS DATA ##
38+
elif 5 == FPort && Bytes.size() == 7
39+
data.insert("Sensor_Model",Bytes[0])
40+
data.insert("Firmware_Version", f'v{Bytes[1]:%u}.{Bytes[2]>>4:%u}.{Bytes[2]&0xF:%u}')
41+
data.insert("Freq_Band",LwRegions[Bytes[3]-1])
42+
data.insert("Sub_Band",Bytes[4])
43+
data.insert("Bat_mV",(Bytes[5] << 8) | Bytes[6])
44+
45+
else
46+
# Ignore other Fports
47+
end #Fport
48+
49+
return data
50+
end #decodeUplink()
51+
end #class
52+
53+
LwDeco = LwDecoLHT52
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# LoRaWAN Decoder file for Dragino LHT65
2+
#
3+
# References
4+
# User Manual: https://www.dragino.com/downloads/downloads/LHT65/UserManual/LHT65_Temperature_Humidity_Sensor_UserManual_v1.8.5.pdf
5+
# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/dragino/lht65.js
6+
7+
import string
8+
var LHT65_BatteryStatus = ["Very low <= 2.5V","Low <=2.55V","OK","Good >= 2.65V"]
9+
10+
class LwDecoLHT65
11+
static def decodeUplink(FPort, Bytes)
12+
var data = {"Device":"Dragino LHT65"}
13+
data.insert("poll_message_status",(Bytes[6] & 0x40) >> 6)
14+
var Ext = Bytes[6] & 0x0F #External sensor type
15+
var NoConnect = (Bytes[6] & 0x80) >> 7
16+
17+
## SENSOR DATA ##
18+
if 2 == FPort && Bytes.size() == 11
19+
var TempC
20+
if Ext == 9 #Sensor E3, Temperature Sensor, Datalog Mod
21+
TempC = ((Bytes[0] << 8) | Bytes[1])
22+
if 0x7FFF == TempC
23+
data.insert("Ext_SensorConnected", false)
24+
else
25+
data.insert("Ext_SensorConnected", true)
26+
if Bytes[0]>0x7F TempC -= 0x10000 end
27+
data.insert("TempC_External", TempC / 100.0)
28+
end
29+
data.insert("Bat_status", LHT65_BatteryStatus[Bytes[4] >> 6])
30+
else
31+
data.insert("BatV",(((Bytes[0] << 8) | Bytes[1]) & 0x3fff) / 1000.0)
32+
data.insert("Bat_status", LHT65_BatteryStatus[Bytes[0] >> 6])
33+
end
34+
35+
if Ext != 0x0F
36+
TempC = ((Bytes[2] << 8) | Bytes[3])
37+
if Bytes[2]>0x7F TempC -= 0x10000 end
38+
data.insert("TempC_Internal", ( TempC / 100.0))
39+
data.insert("Hum_Internal" , (((Bytes[4] << 8) | Bytes[5]) / 10.0))
40+
end
41+
42+
if NoConnect
43+
data.insert('No_connect','No connection to external sensor')
44+
end
45+
46+
if 0==Ext
47+
data.insert("Ext_sensor", 'No external sensor')
48+
elif 1==Ext
49+
data.insert("Ext_sensor",'Temperature Sensor')
50+
TempC = ((Bytes[7] << 8) | Bytes[8])
51+
if 0x7FFF == TempC
52+
data.insert("Ext_SensorConnected", false)
53+
else
54+
data.insert("Ext_SensorConnected", true)
55+
if Bytes[7]>0x7F TempC -= 0x10000 end
56+
data.insert("TempC_External", TempC / 100.0)
57+
end
58+
elif 4==Ext
59+
data.insert("Work_mode", 'Interrupt Sensor send')
60+
data.insert("Exti_pin_level", Bytes[7] ? 'High' : 'Low')
61+
data.insert("Exti_status", Bytes[8] ? 'True' : 'False')
62+
elif 5==Ext
63+
data.insert("Work_mode", 'Illumination Sensor')
64+
data.insert("ILL_lx", (Bytes[7] << 8) | Bytes[8])
65+
elif 6==Ext
66+
data.insert("Work_mode", 'ADC Sensor')
67+
data.insert("ADC_V", ((Bytes[7] << 8) | Bytes[8]) / 1000.0)
68+
elif 7==Ext
69+
data.insert("Work_mode ", 'Interrupt Sensor count')
70+
data.insert("Exit_count", (Bytes[7] << 8) | Bytes[8])
71+
elif 8==Ext
72+
data.insert("Work_mode", 'Interrupt Sensor count')
73+
data.insert("Exit_count", (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10])
74+
elif 9==Ext
75+
data.insert("Work_mode", 'DS18B20 & timestamp')
76+
data.insert("Systimestamp", (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10])
77+
elif 15==Ext
78+
data.insert("Work_mode",'DS18B20ID')
79+
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}" )
80+
else
81+
data.insert("Ext_sensor", 'Unknown')
82+
end
83+
84+
elif 5 == FPort && Bytes.size() == 7
85+
data.insert("Sensor_Model",Bytes[0])
86+
data.insert("Firmware_Version", f'v{Bytes[1]:%u}.{Bytes[2]>>4:%u}.{Bytes[2]&0xF:%u}')
87+
data.insert("Freq_Band",LwRegions[Bytes[3]-1])
88+
data.insert("Sub_Band",Bytes[4])
89+
data.insert("Bat_mV",(Bytes[5] << 8) | Bytes[6])
90+
91+
else
92+
# Ignore other Fports
93+
end #Fport
94+
95+
return data
96+
end # decodeUplink()
97+
end # class
98+
99+
LwDeco = LwDecoLHT65

0 commit comments

Comments
 (0)