mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-19 16:56:34 +00:00
Add GUI support to LoRaWan decoders
This commit is contained in:
parent
92f14dd0c0
commit
4f4bf7c61b
@ -2,41 +2,77 @@
|
|||||||
# https://github.com/TheThingsNetwork/lorawan-devices/tree/master/vendor
|
# 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 LwRegions = ["EU868", "US915", "IN865","AU915","KZ865","RU864","AS923", "AS923-1","AS923-2","AS923-3"]
|
||||||
|
var LwDeco
|
||||||
|
|
||||||
import mqtt
|
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('SetOption100 off') # Keep LwReceived in JSON message
|
||||||
tasmota.cmd('SetOption118 off') # Keep SENSOR as subtopic name
|
tasmota.cmd('SetOption118 off') # Keep SENSOR as subtopic name
|
||||||
tasmota.cmd('SetOption119 off') # Keep device address in JSON message
|
tasmota.cmd('SetOption119 off') # Keep device address in JSON message
|
||||||
tasmota.cmd('SetOption147 on') # Hide LwReceived MQTT message but keep rule processing
|
tasmota.cmd('SetOption147 on') # Hide LwReceived MQTT message but keep rule processing
|
||||||
tasmota.cmd('LoRaWanBridge on')
|
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))
|
|
@ -8,6 +8,9 @@
|
|||||||
# the string module is not needed in this decoder; shown as an example.
|
# the string module is not needed in this decoder; shown as an example.
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
# define a global map for storage of GUI parameters
|
||||||
|
global.DEVICENodes = {}
|
||||||
|
|
||||||
# Declare a new Class
|
# Declare a new Class
|
||||||
# The Class name should follow this format: LwDecoXXXX where XXXX is the DEVICE
|
# The Class name should follow this format: LwDecoXXXX where XXXX is the DEVICE
|
||||||
class LwDecoDEVICE
|
class LwDecoDEVICE
|
||||||
@ -16,24 +19,49 @@ class LwDecoDEVICE
|
|||||||
# Name: decodeUplink Must use this name, and arguments
|
# Name: decodeUplink Must use this name, and arguments
|
||||||
# Arguments: FPort The Fport number used by the End Device for this packet of data
|
# Arguments: FPort The Fport number used by the End Device for this packet of data
|
||||||
# Bytes The Raw Data Payload
|
# 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
|
# 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:
|
# For each Fport used by the DEVICE:
|
||||||
# write a decoder that continues to populate the data structure by parsing the Raw Data Payload
|
# 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
|
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("LABEL1", Bytes[0] | Bytes[1] <<8 ) #Example Numerical value = Bytes[1]*256 + Bytes[0]
|
||||||
data.insert("LABEL2", "TEXT VALUE") #Example Text value
|
gui_value = Bytes[0] | Bytes[1] <<8
|
||||||
|
data.insert("LABEL2", "TEXT VALUE") #Example Text value
|
||||||
|
|
||||||
else
|
|
||||||
# Ignore other Fports
|
|
||||||
end #Fport
|
|
||||||
|
|
||||||
return data
|
var valid_values = true
|
||||||
end #decodeUplink()
|
|
||||||
|
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
|
end #class
|
||||||
|
|
||||||
# Set LwDeco variable to the new Class
|
# Set LwDeco variable to the new Class
|
||||||
|
@ -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/
|
# 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
|
# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/dragino/lds02.js
|
||||||
|
|
||||||
|
import string
|
||||||
|
|
||||||
|
global.lds02Nodes = {}
|
||||||
|
|
||||||
class LwDecoLDS02
|
class LwDecoLDS02
|
||||||
|
static def decodeUplink(Node, FPort, Bytes)
|
||||||
|
var data = {"Device":"Dragino LDS02"}
|
||||||
|
data.insert("Node", Node)
|
||||||
|
|
||||||
static def decodeUplink(FPort, Bytes)
|
var valid_values = false
|
||||||
var data = {"Device":"Dragino LDS02"}
|
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
|
||||||
|
|
||||||
## SENSOR DATA ##
|
else
|
||||||
|
# Ignore other Fports
|
||||||
|
end #Fport
|
||||||
|
|
||||||
if 10 == FPort && Bytes.size() == 10
|
if valid_values
|
||||||
data.insert("DoorOpen", ( Bytes[0] & 0x80 ) ? true : false )
|
if global.lds02Nodes.find(Node)
|
||||||
data.insert("Battery_mV", ( Bytes[1] | (Bytes[0] << 8) & 0x3FFF ))
|
global.lds02Nodes.remove(Node)
|
||||||
data.insert("DoorOpenEvents", Bytes[5] | (Bytes[4] << 8) | (Bytes[3] << 16 ))
|
end
|
||||||
data.insert("DoorOpenLastDuration_mins", Bytes[8] | (Bytes[7] << 8) | (Bytes[6] << 16))
|
global.lds02Nodes.insert(Node, [Node, door_open])
|
||||||
data.insert("Alarm", (Bytes[9] & 0x01 ) ? true : false)
|
end
|
||||||
|
|
||||||
else
|
return data
|
||||||
# Ignore other Fports
|
end #decodeUplink()
|
||||||
end #Fport
|
|
||||||
|
|
||||||
return data
|
static def add_web_sensor()
|
||||||
end #decodeUplink()
|
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
|
end #class
|
||||||
|
|
||||||
LwDeco = LwDecoLDS02
|
LwDeco = LwDecoLDS02
|
||||||
|
@ -6,50 +6,86 @@
|
|||||||
|
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
global.lht52Nodes = {}
|
||||||
|
|
||||||
class LwDecoLHT52
|
class LwDecoLHT52
|
||||||
|
static def decodeUplink(Node, FPort, Bytes)
|
||||||
|
var data = {"Device":"Dragino LHT52"}
|
||||||
|
data.insert("Node", Node)
|
||||||
|
|
||||||
static def decodeUplink(FPort, Bytes)
|
var valid_values = false
|
||||||
var data = {"Device":"Dragino LHT52"}
|
var temp_int
|
||||||
|
var humidity
|
||||||
|
var temp_ext = 1000
|
||||||
|
## SENSOR DATA ##
|
||||||
|
if 2 == FPort && Bytes.size() == 11
|
||||||
|
var TempC
|
||||||
|
|
||||||
## SENSOR DATA ##
|
TempC = Bytes[0] << 8 | Bytes[1]
|
||||||
if 2 == FPort && Bytes.size() == 11
|
if Bytes[0] > 0x7F
|
||||||
var TempC
|
TempC -= 0x10000
|
||||||
|
end
|
||||||
|
TempC /= 100.0
|
||||||
|
data.insert("TempC_Internal", TempC)
|
||||||
|
temp_int = TempC
|
||||||
|
|
||||||
TempC = Bytes[0]<<8 | Bytes[1]
|
TempC = Bytes[4] << 8 | Bytes[5]
|
||||||
if Bytes[0]>0x7F TempC -= 0x10000 end
|
if 0x7FFF == TempC
|
||||||
TempC /= 100.0
|
data.insert("Ext_SensorConnected", false)
|
||||||
data.insert("TempC_Internal",TempC)
|
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
|
||||||
|
|
||||||
TempC = Bytes[4]<<8 | Bytes[5]
|
data.insert("Hum_Internal", ((Bytes[2] << 8) | Bytes[3]) / 10.0)
|
||||||
if 0x7FFF == TempC
|
humidity = ((Bytes[2] << 8) | Bytes[3]) / 10.0
|
||||||
data.insert("Ext_SensorConnected", false)
|
data.insert("Ext_SensorType", Bytes[6])
|
||||||
else
|
var epoch = (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10]
|
||||||
data.insert("Ext_SensorConnected", true)
|
data.insert("Systimestamp",tasmota.time_str(epoch))
|
||||||
if Bytes[4]>0x7F TempC -= 0x10000 end
|
valid_values = true
|
||||||
TempC /= 100.0
|
|
||||||
data.insert("TempC_External",TempC)
|
|
||||||
end
|
|
||||||
|
|
||||||
data.insert("Hum_Internal", ((Bytes[2]<<8 ) | Bytes[3])/10.0)
|
## STATUS DATA ##
|
||||||
data.insert("Ext_SensorType", Bytes[6])
|
elif 5 == FPort && Bytes.size() == 7
|
||||||
# data.insert("Systimestamp",(Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10])
|
data.insert("Sensor_Model",Bytes[0])
|
||||||
var epoch = (Bytes[7] << 24) | (Bytes[8] << 16) | (Bytes[9] << 8) | Bytes[10]
|
data.insert("Firmware_Version", f'v{Bytes[1]:%u}.{Bytes[2]>>4:%u}.{Bytes[2]&0xF:%u}')
|
||||||
data.insert("Systimestamp",tasmota.time_str(epoch))
|
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 ##
|
else
|
||||||
elif 5 == FPort && Bytes.size() == 7
|
# Ignore other Fports
|
||||||
data.insert("Sensor_Model",Bytes[0])
|
end #Fport
|
||||||
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
|
if valid_values
|
||||||
# Ignore other Fports
|
if global.lht52Nodes.find(Node)
|
||||||
end #Fport
|
global.lht52Nodes.remove(Node)
|
||||||
|
end
|
||||||
|
global.lht52Nodes.insert(Node, [Node, temp_int, humidity, temp_ext])
|
||||||
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end #decodeUplink()
|
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
|
end #class
|
||||||
|
|
||||||
LwDeco = LwDecoLHT52
|
LwDeco = LwDecoLHT52
|
||||||
|
@ -6,96 +6,147 @@
|
|||||||
|
|
||||||
import string
|
import string
|
||||||
var LHT65_BatteryStatus = ["Very low <= 2.5V","Low <=2.55V","OK","Good >= 2.65V"]
|
var LHT65_BatteryStatus = ["Very low <= 2.5V","Low <=2.55V","OK","Good >= 2.65V"]
|
||||||
|
global.lht65Nodes = {}
|
||||||
|
|
||||||
class LwDecoLHT65
|
class LwDecoLHT65
|
||||||
static def decodeUplink(FPort, Bytes)
|
static def decodeUplink(FPort, Bytes)
|
||||||
var data = {"Device":"Dragino LHT65"}
|
var data = {"Device":"Dragino LHT65"}
|
||||||
data.insert("poll_message_status",(Bytes[6] & 0x40) >> 6)
|
data.insert("Node", Node)
|
||||||
var Ext = Bytes[6] & 0x0F #External sensor type
|
data.insert("poll_message_status",(Bytes[6] & 0x40) >> 6)
|
||||||
var NoConnect = (Bytes[6] & 0x80) >> 7
|
|
||||||
|
|
||||||
## SENSOR DATA ##
|
var Ext = Bytes[6] & 0x0F #External sensor type
|
||||||
if 2 == FPort && Bytes.size() == 11
|
var NoConnect = (Bytes[6] & 0x80) >> 7
|
||||||
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
|
var valid_values = false
|
||||||
TempC = ((Bytes[2] << 8) | Bytes[3])
|
var temp_int = 1000
|
||||||
if Bytes[2]>0x7F TempC -= 0x10000 end
|
var humidity
|
||||||
data.insert("TempC_Internal", ( TempC / 100.0))
|
var temp_ext = 1000
|
||||||
data.insert("Hum_Internal" , (((Bytes[4] << 8) | Bytes[5]) / 10.0))
|
if global.lht65Nodes.find(Node)
|
||||||
end
|
temp_int = global.lht65Nodes.item(Node)[1]
|
||||||
|
humidity = global.lht65Nodes.item(Node)[2]
|
||||||
if NoConnect
|
temp_ext = global.lht65Nodes.item(Node)[3]
|
||||||
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
|
end
|
||||||
elif 4==Ext
|
## SENSOR DATA ##
|
||||||
data.insert("Work_mode", 'Interrupt Sensor send')
|
if 2 == FPort && Bytes.size() == 11
|
||||||
data.insert("Exti_pin_level", Bytes[7] ? 'High' : 'Low')
|
var TempC
|
||||||
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
|
if Ext == 9 #Sensor E3, Temperature Sensor, Datalog Mod
|
||||||
data.insert("Sensor_Model",Bytes[0])
|
TempC = ((Bytes[0] << 8) | Bytes[1])
|
||||||
data.insert("Firmware_Version", f'v{Bytes[1]:%u}.{Bytes[2]>>4:%u}.{Bytes[2]&0xF:%u}')
|
if 0x7FFF == TempC
|
||||||
data.insert("Freq_Band",LwRegions[Bytes[3]-1])
|
data.insert("Ext_SensorConnected", false)
|
||||||
data.insert("Sub_Band",Bytes[4])
|
else
|
||||||
data.insert("Bat_mV",(Bytes[5] << 8) | Bytes[6])
|
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
|
||||||
|
|
||||||
else
|
if Ext != 0x0F
|
||||||
# Ignore other Fports
|
TempC = ((Bytes[2] << 8) | Bytes[3])
|
||||||
end #Fport
|
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
|
||||||
|
|
||||||
return data
|
if NoConnect
|
||||||
end # decodeUplink()
|
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])
|
||||||
|
|
||||||
|
else
|
||||||
|
# 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
|
end # class
|
||||||
|
|
||||||
LwDeco = LwDecoLHT65
|
LwDeco = LwDecoLHT65
|
@ -4,30 +4,65 @@
|
|||||||
# DW10 Product information: https://www.browan.com/products-detail/OpenClose-Sensor-EBL-LoRaWAN/
|
# 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
|
# 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
|
class LwDecoDW10
|
||||||
|
static def decodeUplink(Node, FPort, Bytes)
|
||||||
|
var data = {"Device":"MerryIoT DW10"}
|
||||||
|
data.insert("Node", Node)
|
||||||
|
|
||||||
static def decodeUplink(FPort, Bytes)
|
var valid_values = false
|
||||||
var data = {"Device":"MerryIoT DW10"}
|
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
|
||||||
|
|
||||||
## SENSOR DATA ##
|
else
|
||||||
|
# Ignore other Fports
|
||||||
|
end #Fport
|
||||||
|
|
||||||
if 120 == FPort && Bytes.size() == 9
|
if valid_values
|
||||||
data.insert("DoorOpen", ( Bytes[0] & 0x01 ) ? true : false )
|
if global.dw10Nodes.find(Node)
|
||||||
data.insert("ButtonPress", ( Bytes[0] & 0x02 ) ? true : false )
|
global.dw10Nodes.remove(Node)
|
||||||
data.insert("TamperDetect", ( Bytes[0] & 0x04 ) ? true : false )
|
end
|
||||||
data.insert("TiltDetect", ( Bytes[0] & 0x08 ) ? true : false )
|
global.dw10Nodes.insert(Node, [Node, door_open, button_pressed, temperature, humidity])
|
||||||
data.insert("Battery_mV", ( 21 + Bytes[1] ) * 100 )
|
end
|
||||||
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 ))
|
|
||||||
|
|
||||||
else
|
return data
|
||||||
# Ignore other Fports
|
end #decodeUplink()
|
||||||
end #Fport
|
|
||||||
|
|
||||||
return data
|
static def add_web_sensor()
|
||||||
end #decodeUplink()
|
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
|
end #class
|
||||||
|
|
||||||
LwDeco = LwDecoDW10
|
LwDeco = LwDecoDW10
|
||||||
|
Loading…
x
Reference in New Issue
Block a user