From bd0d7d0ac32789ee9b3fc8f7167ed292d6321294 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:24:21 +0200 Subject: [PATCH] Remove pre-production extensions --- tasmota/berry/extensions/Devices_Online.tapp | Bin 19367 -> 0 bytes .../extensions/Devices_Online/autoexec.be | 9 - .../Devices_Online/devices_online.be | 359 ----------- .../extensions/Devices_Online/manifest.json | 8 - .../berry/extensions/LoRaWan_Decoders.tapp | Bin 95874 -> 0 bytes .../berry/extensions/LoRaWan_Decoders/D20.be | 129 ---- .../extensions/LoRaWan_Decoders/DDS75L.be | 94 --- .../berry/extensions/LoRaWan_Decoders/DW10.be | 103 --- .../extensions/LoRaWan_Decoders/LDS02.be | 88 --- .../extensions/LoRaWan_Decoders/LHT52.be | 125 ---- .../extensions/LoRaWan_Decoders/LHT65.be | 202 ------ .../extensions/LoRaWan_Decoders/PS-L-I5.be | 114 ---- .../extensions/LoRaWan_Decoders/SE01-L.be | 149 ----- .../extensions/LoRaWan_Decoders/SN50v3L.be | 119 ---- .../extensions/LoRaWan_Decoders/WS202.be | 118 ---- .../extensions/LoRaWan_Decoders/WS301.be | 118 ---- .../extensions/LoRaWan_Decoders/WS522.be | 369 ----------- .../extensions/LoRaWan_Decoders/autoexec.be | 9 - .../LoRaWan_Decoders/lorawan_decoders.be | 590 ------------------ .../extensions/LoRaWan_Decoders/manifest.json | 8 - .../extensions/LoRaWan_Decoders/walker.be | 242 ------- 21 files changed, 2953 deletions(-) delete mode 100644 tasmota/berry/extensions/Devices_Online.tapp delete mode 100644 tasmota/berry/extensions/Devices_Online/autoexec.be delete mode 100644 tasmota/berry/extensions/Devices_Online/devices_online.be delete mode 100644 tasmota/berry/extensions/Devices_Online/manifest.json delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders.tapp delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/D20.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/DDS75L.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/DW10.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/LDS02.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/LHT52.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/LHT65.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/PS-L-I5.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/SE01-L.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/SN50v3L.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/WS202.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/WS301.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/WS522.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/autoexec.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/lorawan_decoders.be delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/manifest.json delete mode 100644 tasmota/berry/extensions/LoRaWan_Decoders/walker.be diff --git a/tasmota/berry/extensions/Devices_Online.tapp b/tasmota/berry/extensions/Devices_Online.tapp deleted file mode 100644 index b67ddcb5c8c28ee6a596f2da3e11ee8d9eb18bac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19367 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 diff --git a/tasmota/berry/extensions/Devices_Online/autoexec.be b/tasmota/berry/extensions/Devices_Online/autoexec.be deleted file mode 100644 index 47034daa8..000000000 --- a/tasmota/berry/extensions/Devices_Online/autoexec.be +++ /dev/null @@ -1,9 +0,0 @@ -# rm Devices_Online.tapp; zip -j -0 Devices_Online.tapp Devices_Online/* -do # embed in `do` so we don't add anything to global namespace - import introspect - var devices_online = introspect.module('devices_online', true) # load module but don't cache - tasmota.add_extension(devices_online) -end - -# to remove: -# tasmota.unload_extension('Devices Online') diff --git a/tasmota/berry/extensions/Devices_Online/devices_online.be b/tasmota/berry/extensions/Devices_Online/devices_online.be deleted file mode 100644 index 1791e33f0..000000000 --- a/tasmota/berry/extensions/Devices_Online/devices_online.be +++ /dev/null @@ -1,359 +0,0 @@ -################################################################################### -# Display in Main GUI Devices Online based on MQTT Tasmota Discovery Config and STATE reports -# -# Copyright (C) 2025 Stephan Hadinger & Theo Arends -# -# Enable either -# line_option = 1 : Scroll 'line_cnt' lines -# or -# line_option = 2 : Show devices updating within 'line_teleperiod' -# -# rm Devices_Online.tapp; zip -j -0 Devices_Online.tapp Devices_Online/* -################################################################################### - -import mqtt -import json -import string -import webserver -import persist - -class devices_online -# static var line_option = 1 # Scroll line_cnt lines - static var line_option = 2 # Show devices updating within line_teleperiod - - static var line_cnt = 10 # Option 1 number of lines to show - static var line_teleperiod = 600 # Option 2 number of teleperiod seconds for devices to be shown as online - static var line_highlight = 10 # Highlight latest change duration in seconds - static var line_highlight_color = "yellow" # Latest change highlight HTML color like "#FFFF00" or "yellow" - static var line_lowuptime_color = "lime" # Low uptime highlight HTML color like "#00FF00" or "lime" - - var mqtt_tele # MQTT tele STATE subscribe format - var bool_devicename # Show device name - var bool_version # Show version - var bool_ipaddress # Show IP address - var sort_direction # Sort direction - var sort_column # Sort column - var sort_last_column # Sort last column - var list_buffer # Buffer storing lines - var list_config # Buffer storing retained config - - ################################################################################# - # init - # - # install the extension and allocate all resources - ################################################################################# - def init() - self.bool_devicename = persist.std_devicename # Show device name - self.bool_version = persist.std_version # Show version - self.bool_ipaddress = persist.std_ipaddress # Show IP address - - self.sort_direction = persist.std_direction # Sort direction (0) Up or (1) Down - if !self.sort_direction - self.sort_direction = 0 # Default Up - end - self.sort_column = persist.std_column # Sort column - if !self.sort_column - self.sort_column = 0 # Default Hostname - end - self.sort_last_column = self.sort_column # Sort last column to detect direction toggle - - self.list_buffer = [] # Init line buffer list - self.list_config = [] # Init retained config buffer list - -# var full_topic = tasmota.cmd("FullTopic", true)['FullTopic'] # "%prefix%/%topic%/" - var prefix_tele = tasmota.cmd("Prefix", true)['Prefix3'] # tele = Prefix3 used by STATE message - self.mqtt_tele = format("%s/#", prefix_tele) - mqtt.subscribe(self.mqtt_tele, /topic, idx, data, databytes -> self.handle_state_data(topic, idx, data, databytes)) - mqtt.subscribe("tasmota/discovery/+/config", /topic, idx, data, databytes -> self.handle_discovery_data(topic, idx, data, databytes)) - - tasmota.add_driver(self) - end - - ################################################################################# - # unload - # - # Uninstall the extension and deallocate all resources - ################################################################################# - def unload() - mqtt.unsubscribe("tasmota/discovery/+/config") - mqtt.unsubscribe(self.mqtt_tele) - tasmota.remove_driver(self) - end - - ################################################################################# - # handle_discovery_data(discovery_topic, idx, data, databytes) - # - # Handle MQTT Tasmota Discovery Config data - ################################################################################# - def handle_discovery_data(discovery_topic, idx, data, databytes) - var config = json.load(data) - if config - # tasmota/discovery/142B2F9FAF38/config = {"ip":"192.168.2.208","dn":"AtomLite2","fn":["Tasmota",null,null,null,null,null,null,null],"hn":"atomlite2","mac":"142B2F9FAF38","md":"M5Stack Atom Lite","ty":0,"if":0,"cam":0,"ofln":"Offline","onln":"Online","state":["OFF","ON","TOGGLE","HOLD"],"sw":"15.0.1.4","t":"atomlite2","ft":"%prefix%/%topic%/","tp":["cmnd","stat","tele"],"rl":[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"swc":[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"swn":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"btn":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"so":{"4":0,"11":0,"13":0,"17":0,"20":0,"30":0,"68":0,"73":0,"82":0,"114":0,"117":0},"lk":1,"lt_st":3,"bat":0,"dslp":0,"sho":[],"sht":[],"ver":1} (retained) - var topic = config['t'] - var hostname = config['hn'] - var ipaddress = config['ip'] - var devicename = config['dn'] - var version = config['sw'] - var line = format("%s\001%s\001%s\001%s\001%s", topic, hostname, ipaddress, devicename, version) -# tasmota.log(format("STD: 111 Size %03d, Topic '%s', Line '%s'", self.list_config.size(), topic, line), 3) - if self.list_config.size() - var list_index = 0 - var list_size = size(self.list_config) - var topic_delim = format("%s\001", topic) # Add find delimiter - while list_index < list_size # Use while loop as counter is decremented - if 0 == string.find(self.list_config[list_index], topic_delim) - self.list_config.remove(list_index) # Remove current config - list_size -= 1 # Continue for duplicates - end - list_index += 1 - end - end - self.list_config.push(line) # Add (re-discovered) config as last entry -# tasmota.log(format("STD: 222 Size %03d, Topic '%s', Line '%s'", self.list_config.size(), topic, line), 3) - end - return true # return true to stop propagation as a Tasmota cmd - end - - ################################################################################# - # handle_state_data(tele_topic, idx, data, databytes) - # - # Handle MQTT STATE data - ################################################################################# - def handle_state_data(tele_topic, idx, data, databytes) - var subtopic = string.split(tele_topic, "/") - if subtopic[-1] == "STATE" # tele/atomlite2/STATE - var topic = subtopic[1] # Assume default Fulltopic (%prefix%/%topic%/) = tele/atomlite2/STATE = atomlite2 - - var topic_index = -1 - for i: self.list_config.keys() - if 0 == string.find(self.list_config[i], topic) - topic_index = i - break - end - end -# tasmota.log(format("STD: Topic '%s', Index %d, Size %d, Line '%s'", topic, topic_index, self.list_config.size(), self.list_config[topic_index]), 3) - if topic_index == -1 return true end # return true to stop propagation as a Tasmota cmd - - var state = json.load(data) # Assume topic is in retained discovery list - if state # Valid JSON state message - var config_splits = string.split(self.list_config[topic_index], "\001") - var hostname = config_splits[1] - var ipaddress = config_splits[2] - var devicename = config_splits[3] - var version = config_splits[4] - - # tele/atomlite2/STATE = {"Time":"2025-09-24T14:13:00","Uptime":"0T00:15:09","UptimeSec":909,"Heap":142,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":19,"MqttCount":1,"Berry":{"HeapUsed":12,"Objects":167},"POWER":"OFF","Dimmer":10,"Color":"1A0000","HSBColor":"0,100,10","Channel":[10,0,0],"Scheme":0,"Width":1,"Fade":"OFF","Speed":1,"LedTable":"ON","Wifi":{"AP":1,"SSId":"indebuurt_IoT","BSSId":"18:E8:29:CA:17:C1","Channel":11,"Mode":"HT40","RSSI":100,"Signal":-28,"LinkCount":1,"Downtime":"0T00:00:04"},"Hostname":"atomlite2","IPAddress":"192.168.2.208"} - var uptime = state['Uptime'] # 0T00:15:09 - if state.find('Hostname') - hostname = state['Hostname'] # atomlite2 - ipaddress = state['IPAddress'] # 192.168.2.208 - end - var last_seen = tasmota.rtc('local') - var line = format("%s\001%s\001%s\001%d\001%s\001%s", hostname, ipaddress, uptime, last_seen, devicename, version) - - if self.list_buffer.size() - var list_index = 0 - var list_size = size(self.list_buffer) - var hostname_delim = format("%s\001", hostname) # Add find delimiter - while list_index < list_size # Use while loop as counter is decremented - if 0 == string.find(self.list_buffer[list_index], hostname_delim) - self.list_buffer.remove(list_index) # Remove current state - list_size -= 1 # Continue for duplicates - end - list_index += 1 - end - end - self.list_buffer.push(line) # Add state as last entry - - end - end - return true # return true to stop propagation as a Tasmota cmd - end - - ################################################################################# - # sort_col(l, col, dir) - # - # Shell sort list of online devices based on user selected column and direction - ################################################################################# - def sort_col(l, col, dir) # Sort list based on col and Hostname (is first entry in line) - # For 50 records takes 6ms (primary key) or 25ms(ESP32S3&240MHz) / 50ms(ESP32@160MHz) (primary and secondary key) - var cmp = /a,b -> a < b # Sort up - if dir - cmp = /a,b -> a > b # Sort down - end - if col # col is new primary key (not Hostname) - for i:l.keys() - var splits = string.split(l[i], "\001") - l[i] = splits[col] + "\002" + l[i] # Add primary key to secondary key as "col" + Hostname - end - end - for i:1..size(l)-1 - var k = l[i] - var j = i - while (j > 0) && !cmp(l[j-1], k) - l[j] = l[j-1] - j -= 1 - end - l[j] = k - end - if col - for i:l.keys() - var splits = string.split(l[i], "\002") # Remove primary key - l[i] = splits[1] - end - end - return l - end - - ################################################################################# - # persist_save - # - # Save user data to be used on restart - ################################################################################# - def persist_save() - persist.std_devicename = self.bool_devicename - persist.std_version = self.bool_version - persist.std_ipaddress = self.bool_ipaddress - persist.std_column = self.sort_column - persist.std_direction = self.sort_direction - persist.save() -# tasmota.log("STD: Persist saved", 3) - end - - ################################################################################# - # web_sensor - # - # Display Devices Online in user selected sorted columns - ################################################################################# - def web_sensor() - if webserver.has_arg("sd_dn") - # Toggle display Device Name - if self.bool_devicename self.bool_devicename = false else self.bool_devicename = true end - self.persist_save() - elif webserver.has_arg("sd_sw") - # Toggle display software version - if self.bool_version self.bool_version = false else self.bool_version = true end - self.persist_save() - elif webserver.has_arg("sd_ip") - # Toggle display IP address - if self.bool_ipaddress self.bool_ipaddress = false else self.bool_ipaddress = true end - self.persist_save() - elif webserver.has_arg("sd_sort") - # Toggle sort column - self.sort_column = int(webserver.arg("sd_sort")) - if self.sort_last_column == self.sort_column - self.sort_direction ^= 1 - end - self.sort_last_column = self.sort_column - self.persist_save() - end - - if self.list_buffer.size() - var now = tasmota.rtc('local') - var time_window = now - self.line_teleperiod - var list_index = 0 - var list_size = size(self.list_buffer) - while list_index < list_size - var splits = string.split(self.list_buffer[list_index], "\001") - var last_seen = int(splits[3]) - if time_window > last_seen # Remove offline devices - self.list_buffer.remove(list_index) - list_size -= 1 - end - list_index += 1 - end - if !list_size return end # If list became empty bail out - - var msg = "" # Terminate two column table and open new table - msg += "" - - list_index = 0 - if 1 == self.line_option - list_index = list_size - self.line_cnt # Offset in list using self.line_cnt - if list_index < 0 list_index = 0 end - - if self.bool_devicename - msg += "" - end - if self.bool_version - msg += "" - end - msg += "" - if self.bool_ipaddress - msg += "" - end - msg += "" - else - self.sort_col(self.list_buffer, self.sort_column, self.sort_direction) # Sort list by column - - var icon_direction = self.sort_direction ? "▼" : "▲" - if self.bool_devicename - msg += format("", self.sort_column == 4 ? icon_direction : "") - end - if self.bool_version - msg += format("", self.sort_column == 5 ? icon_direction : "") - end - msg += format("", self.sort_column == 0 ? icon_direction : "") - if self.bool_ipaddress - msg += format("", self.sort_column == 1 ? icon_direction : "") - end - msg += format("", self.sort_column == 2 ? icon_direction : "") - end - - msg += "" - - while list_index < list_size - var splits = string.split(self.list_buffer[list_index], "\001") - var hostname = splits[0] - var ipaddress = splits[1] - var uptime = splits[2] - var last_seen = int(splits[3]) - var devicename = splits[4] - var version = splits[5] - - msg += "" - if self.bool_devicename - msg += format("", devicename) - end - if self.bool_version - msg += format("", version) - end - msg += format("", hostname, hostname) - if self.bool_ipaddress - msg += format("", ipaddress, ipaddress) - end - - var uptime_str = string.replace(uptime, "T", ":") # 11T21:50:34 -> 11:21:50:34 - var uptime_splits = string.split(uptime_str, ":") - var uptime_sec = (int(uptime_splits[0]) * 86400) + # 11 * 86400 - (int(uptime_splits[1]) * 3600) + # 21 * 3600 - (int(uptime_splits[2]) * 60) + # 50 * 60 - int(uptime_splits[3]) # 34 - if last_seen >= (now - self.line_highlight) # Highlight changes within latest seconds - msg += format("", self.line_highlight_color, uptime) - elif uptime_sec < self.line_teleperiod # Highlight changes just after restart - msg += format("", self.line_lowuptime_color, uptime) - else - msg += format("", uptime) - end - - msg += "" - list_index += 1 - end - msg += "
Device Name Version Hostname IP Address Uptime Device Name%s Version%s Hostname%s IP Address%s Uptime%s 
%s %s %s %s %s%s%s
{t}" # Terminate three/four/five column table and open new table: - msg += format("{s}Devices online{m}%d{e}", list_size) # - - msg += "
Devices online%d

{t}" # Terminate two column table and open new table: - msg += "" - msg += "" - msg += "" - msg += "
{t}" # Terminate two column table and open new table: - - tasmota.web_send(msg) # Do not use tasmota.web_send_decimal() which will replace IPAddress dots - tasmota.web_send_decimal("") # Force horizontal line - end - end - -end - -return devices_online() diff --git a/tasmota/berry/extensions/Devices_Online/manifest.json b/tasmota/berry/extensions/Devices_Online/manifest.json deleted file mode 100644 index d94838dba..000000000 --- a/tasmota/berry/extensions/Devices_Online/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "Devices Online", - "version": "0x190A0200", - "description": "Display devices online", - "author": "Theo Arends", - "min_tasmota": "0x0E060001", - "features": "" -} diff --git a/tasmota/berry/extensions/LoRaWan_Decoders.tapp b/tasmota/berry/extensions/LoRaWan_Decoders.tapp deleted file mode 100644 index 6f7df1c523d5ed49f5b409b28fb8f7add5fedfb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95874 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 diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/D20.be b/tasmota/berry/extensions/LoRaWan_Decoders/D20.be deleted file mode 100644 index 9f7815e4d..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/D20.be +++ /dev/null @@ -1,129 +0,0 @@ -# LoRaWAN Decoder file for Dragino D20/D22/D23 (1,2,3 temp sensor models) -# -# References -# User Manual: https://wiki.dragino.com/xwiki/bin/view/Main/User%20Manual%20for%20LoRaWAN%20End%20Nodes/D20-LBD22-LBD23-LB_LoRaWAN_Temperature_Sensor_User_Manual/ -# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/dragino/d2x-lb.js - -import string - -if !global.DrgD20Nodes # data survive to decoder reload - global.DrgD20Nodes = {} -end - -class LwDecoDrgD20 - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Dragino D20"} - - var valid_values = false - var last_seen = 1451602800 - var battery_last_seen = 1451602800 - var battery = 1000 - var rssi = RSSI - var tempC1 = 1000 - var tempC2 = 1000 - var tempC3 = 1000 - - if global.DrgD20Nodes.find(Node) - last_seen = global.DrgD20Nodes.item(Node)[2] - battery_last_seen = global.DrgD20Nodes.item(Node)[3] - battery = global.DrgD20Nodes.item(Node)[4] - rssi = global.DrgD20Nodes.item(Node)[5] - tempC1 = global.DrgD20Nodes.item(Node)[6] - tempC2 = global.DrgD20Nodes.item(Node)[7] - tempC3 = global.DrgD20Nodes.item(Node)[8] - end - - ## SENSOR DATA ## - if 2 == FPort && Bytes.size() == 11 - last_seen = tasmota.rtc('local') - var mode=(Bytes[6] & 0x7C)>>2 - - if 3==mode - battery = (Bytes[0]<<8 | Bytes[1])/1000 - data.insert("BattV", battery) - battery_last_seen = tasmota.rtc('local') - - # 0x07FF = 2047 = no temp sensor - - tempC1 = Bytes[2] << 8 | Bytes[3] - if Bytes[2]>0x7F tempC1-=0x10000 end - tempC1 /= 10.0 - data.insert("TempC1", tempC1) - - tempC2 = Bytes[7] << 8 | Bytes[8] - if Bytes[7]>0x7F tempC2-=0x10000 end - tempC2 /= 10.0 - data.insert("TempC2", tempC2) - - tempC3 = Bytes[9] << 8 | Bytes[10] - if Bytes[9]>0x7F tempC3-=0x10000 end - tempC3 /= 10.0 - data.insert("TempC3", tempC3) - - end - - 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("BattV",((Bytes[5] << 8) | Bytes[6]) / 1000.0) - battery_last_seen = tasmota.rtc('local') - battery = ((Bytes[5] << 8) | Bytes[6]) / 1000.0 - valid_values = true - else - # Ignore other Fports - end #Fport - - if valid_values - if global.DrgD20Nodes.find(Node) - global.DrgD20Nodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] [7] [8] - global.DrgD20Nodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, RSSI, tempC1, tempC2, tempC3]) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.DrgD20Nodes - var name = sensor[0] - if string.find(name, "D20") > -1 # If LoRaWanName contains D20 use D20- - name = string.format("D20-%i", sensor[1]) - end - var name_tooltip = "Dragino D20" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - msg += fmt.header(name, name_tooltip, battery, battery_last_seen, rssi, last_seen) - - # Sensors - var tempC1 = sensor[6] - msg += " - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoDrgD20 diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/DDS75L.be b/tasmota/berry/extensions/LoRaWan_Decoders/DDS75L.be deleted file mode 100644 index 3f4b64681..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/DDS75L.be +++ /dev/null @@ -1,94 +0,0 @@ -# LoRaWAN Decoder file for Dragino DDS75-LB/LS -# -# References -# User Manual: https://wiki.dragino.com/xwiki/bin/view/Main/User%20Manual%20for%20LoRaWAN%20End%20Nodes/DDS75-LB_LoRaWAN_Distance_Detection_Sensor_User_Manual/ -# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/dragino/dds75-lb.js - -import string - -if !global.dds75lbNodes # data survive to decoder reload - global.dds75lbNodes = {} -end - -class LwDecoDDS75LB - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Dragino DDS75-LB/LS"} - - var valid_values = false - var last_seen = 1451602800 - var battery_last_seen = 1451602800 - var battery = 1000 - var rssi = RSSI - var distance = 0 - - if global.dds75lbNodes.find(Node) - last_seen = global.dds75lbNodes.item(Node)[2] - battery_last_seen = global.dds75lbNodes.item(Node)[3] - battery = global.dds75lbNodes.item(Node)[4] - rssi = global.dds75lbNodes.item(Node)[5] - distance = global.dds75lbNodes.item(Node)[6] - end - - ## SENSOR DATA ## - if 2 == FPort && 8 == Bytes.size() && 0 == ( Bytes[0] & 0x10 ) - last_seen = tasmota.rtc('local') - - battery_last_seen = tasmota.rtc('local') - battery = ((Bytes[0] << 8) | Bytes[1]) / 1000.0 - data.insert("BattV",battery) - - distance=Bytes[2]<<8 | Bytes[3] - data.insert("Distance",distance) - - valid_values = true - - ## STATUS DATA ## - elif 5 == FPort && 7 == Bytes.size() - 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]) - battery_last_seen = tasmota.rtc('local') - battery = ((Bytes[5] << 8) | Bytes[6]) / 1000.0 - valid_values = true - else - # Ignore other Fports - end #Fport - - if valid_values - if global.dds75lbNodes.find(Node) - global.dds75lbNodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] - global.dds75lbNodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, RSSI, distance]) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.dds75lbNodes - var name = sensor[0] - if string.find(name, "DDS75-L") > -1 # If LoRaWanName contains DDS75-L use DDS75-L- - name = string.format("DDS75-L-%i", sensor[1]) - end - var name_tooltip = "Dragino DDS75-L" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - msg += fmt.header(name, name_tooltip, battery, battery_last_seen, rssi, last_seen) - - # Sensors - var distance = sensor[6] - msg += " - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoDDS75LB diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/DW10.be b/tasmota/berry/extensions/LoRaWan_Decoders/DW10.be deleted file mode 100644 index 3a49c272d..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/DW10.be +++ /dev/null @@ -1,103 +0,0 @@ -# LoRaWAN Decoder file for MerryIoT DW10 Open/Close -# -# References -# 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 - -if !global.dw10Nodes # data survive to decoder reload - global.dw10Nodes = {} -end - -class LwDecoDW10 - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"MerryIoT DW10"} - - var valid_values = false - var last_seen = 1451602800 - var battery_last_seen = 1451602800 - var battery - var rssi = RSSI - var door_open - var door_open_last_seen = 1451602800 - var button_pressed - var temperature - var humidity - if global.dw10Nodes.find(Node) - door_open = global.dw10Nodes.item(Node)[6] - door_open_last_seen = global.dw10Nodes.item(Node)[7] - end - ## SENSOR DATA ## - if 120 == FPort && Bytes.size() == 9 - last_seen = tasmota.rtc('local') - - var last_door_open = door_open - door_open = ( Bytes[0] & 0x01 ) ? 1 : 0 - data.insert("DoorOpen", ( door_open ) ? true : false ) - if last_door_open != door_open - door_open_last_seen = tasmota.rtc('local') - end - - 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("BattV", (( 21 + Bytes[1] ) * 100) / 1000.0 ) - battery_last_seen = tasmota.rtc('local') - battery = (( 21 + Bytes[1] ) * 100) / 1000.0 - data.insert("TemperatureC", 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 - - if valid_values - if global.dw10Nodes.find(Node) - global.dw10Nodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] - global.dw10Nodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, RSSI, door_open, door_open_last_seen, button_pressed, temperature, humidity]) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.dw10Nodes - var name = sensor[0] - if string.find(name, "DW10") > -1 # If LoRaWaName contains DW10 use DW10- - name = string.format("DW10-%i", sensor[1]) - end - var name_tooltip = "MerryIoT DW10" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - msg += fmt.header(name, name_tooltip, battery, battery_last_seen, rssi, last_seen) - - # Sensors - var door_open = sensor[6] - var door_open_last_seen = sensor[7] - var button_pressed = sensor[8] - var temperature = sensor[9] - var humidity = sensor[10] - msg += " - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoDW10 diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/LDS02.be b/tasmota/berry/extensions/LoRaWan_Decoders/LDS02.be deleted file mode 100644 index 225cbf7e6..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/LDS02.be +++ /dev/null @@ -1,88 +0,0 @@ -# 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 - -import string - -if !global.lds02Nodes # data survive to decoder reload - global.lds02Nodes = {} -end - -class LwDecoLDS02 - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Dragino LDS02"} - - var valid_values = false - var last_seen = 1451602800 - var battery_last_seen = 1451602800 - var battery - var rssi = RSSI - var door_open - var door_open_last_seen = 1451602800 - if global.lds02Nodes.find(Node) - door_open = global.lds02Nodes.item(Node)[6] - door_open_last_seen = global.lds02Nodes.item(Node)[7] - end - ## SENSOR DATA ## - if 10 == FPort && Bytes.size() == 10 - last_seen = tasmota.rtc('local') - - var last_door_open = door_open - door_open = ( Bytes[0] & 0x80 ) ? 1 : 0 - data.insert("DoorOpen", ( door_open ) ? true : false) - if last_door_open != door_open - door_open_last_seen = tasmota.rtc('local') - end - - data.insert("BattV", ( Bytes[1] | (Bytes[0] << 8) & 0x3FFF ) / 1000.0) - battery_last_seen = tasmota.rtc('local') - battery = ( Bytes[1] | (Bytes[0] << 8) & 0x3FFF ) / 1000.0 - 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 - - if valid_values - if global.lds02Nodes.find(Node) - global.lds02Nodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] [7] - global.lds02Nodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, RSSI, door_open, door_open_last_seen]) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.lds02Nodes - var name = sensor[0] - if string.find(name, "LDS02") > -1 # If LoRaWanName contains LDS02 use LDS02- - name = string.format("LDS02-%i", sensor[1]) - end - var name_tooltip = "Dragino LDS02" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - msg += fmt.header(name, name_tooltip, battery, battery_last_seen, rssi, last_seen) - - # Sensors - var door_open = sensor[6] - var door_open_last_seen = sensor[7] - msg += " - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoLDS02 diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/LHT52.be b/tasmota/berry/extensions/LoRaWan_Decoders/LHT52.be deleted file mode 100644 index f9acffd6c..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/LHT52.be +++ /dev/null @@ -1,125 +0,0 @@ -# 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 - -if !global.lht52Nodes # data survive to decoder reload - global.lht52Nodes = {} -end - -class LwDecoLHT52 - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Dragino LHT52"} - - var valid_values = false - var last_seen = 1451602800 - var battery_last_seen = 1451602800 - var battery = 1000 - var rssi = RSSI - var temp_int = 1000 - var humidity - var temp_ext = 1000 - if global.lht52Nodes.find(Node) - last_seen = global.lht52Nodes.item(Node)[2] - battery_last_seen = global.lht52Nodes.item(Node)[3] - battery = global.lht52Nodes.item(Node)[4] - rssi = global.lht52Nodes.item(Node)[5] - temp_int = global.lht52Nodes.item(Node)[6] - humidity = global.lht52Nodes.item(Node)[7] - temp_ext = global.lht52Nodes.item(Node)[8] - end - ## SENSOR DATA ## - if 2 == FPort && Bytes.size() == 11 - last_seen = tasmota.rtc('local') - - var 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) - temp_ext = TempC - end - - 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("BattV",((Bytes[5] << 8) | Bytes[6]) / 1000.0) - battery_last_seen = tasmota.rtc('local') - battery = ((Bytes[5] << 8) | Bytes[6]) / 1000.0 - valid_values = true - else - # Ignore other Fports - end #Fport - - if valid_values - if global.lht52Nodes.find(Node) - global.lht52Nodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] [7] [8] - global.lht52Nodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, RSSI, temp_int, humidity, temp_ext]) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.lht52Nodes - var name = sensor[0] - if string.find(name, "LHT52") > -1 # If LoRaWanName contains LHT52 use LHT52- - name = string.format("LHT52-%i", sensor[1]) - end - var name_tooltip = "Dragino LHT52" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - msg += fmt.header(name, name_tooltip, battery, battery_last_seen, rssi, last_seen) - - # Sensors - var temp_int = sensor[6] - var humidity = sensor[7] - var temp_ext = sensor[8] - msg += " - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoLHT52 diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/LHT65.be b/tasmota/berry/extensions/LoRaWan_Decoders/LHT65.be deleted file mode 100644 index 35411e328..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/LHT65.be +++ /dev/null @@ -1,202 +0,0 @@ -# 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"] - -if !global.lht65Nodes # data survive to decoder reload - global.lht65Nodes = {} -end - -class LwDecoLHT65 - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Dragino LHT65"} - - var valid_values = false - var last_seen = 1451602800 - var battery_last_seen = 1451602800 - var battery = 1000 - var rssi = RSSI - var temp_int = 1000 - var humidity - var temp_ext = 1000 - var door_open = 1000 - var door_open_last_seen = 1451602800 - if global.lht65Nodes.find(Node) - last_seen = global.lht65Nodes.item(Node)[2] - battery_last_seen = global.lht65Nodes.item(Node)[3] - battery = global.lht65Nodes.item(Node)[4] - RSSI = global.lht65Nodes.item(Node)[5] - temp_int = global.lht65Nodes.item(Node)[6] - humidity = global.lht65Nodes.item(Node)[7] - temp_ext = global.lht65Nodes.item(Node)[8] - door_open = global.lht65Nodes.item(Node)[9] - door_open_last_seen = global.lht65Nodes.item(Node)[10] - end - - var Ext = Bytes[6] & 0x0F #External sensor type - var NoConnect = (Bytes[6] & 0x80) >> 7 - - ## SENSOR DATA ## - data.insert("poll_message_status",(Bytes[6] & 0x40) >> 6) - - if 2 == FPort && Bytes.size() == 11 - var TempC - - if Ext == 9 #Sensor E3, Temperature Sensor, Datalog Mod - last_seen = tasmota.rtc('local') - 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("BattV",(((Bytes[0] << 8) | Bytes[1]) & 0x3fff) / 1000.0) - battery_last_seen = tasmota.rtc('local') - battery = (((Bytes[0] << 8) | Bytes[1]) & 0x3fff) / 1000.0 - data.insert("Bat_status", LHT65_BatteryStatus[Bytes[0] >> 6]) - valid_values = true - end - - if Ext != 0x0F - last_seen = tasmota.rtc('local') - 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 - last_seen = tasmota.rtc('local') - 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 - last_seen = tasmota.rtc('local') - data.insert("Work_mode", 'Interrupt Sensor send') - door_open = ( Bytes[7] ) ? 0 : 1 # DS sensor - data.insert("Exti_pin_level", Bytes[7] ? 'High' : 'Low') - data.insert("Exti_status", Bytes[8] ? 'True' : 'False') - if Bytes[8] - door_open_last_seen = tasmota.rtc('local') - end - valid_values = true - 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("BattV",((Bytes[5] << 8) | Bytes[6]) / 1000.0) - battery_last_seen = tasmota.rtc('local') - battery = ((Bytes[5] << 8) | Bytes[6]) / 1000.0 - valid_values = true - else - # Ignore other Fports - end #Fport - - if valid_values - if global.lht65Nodes.find(Node) - global.lht65Nodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] - global.lht65Nodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, rssi, temp_int, humidity, temp_ext, door_open, door_open_last_seen]) - end - - return data - end # decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.lht65Nodes - var name = sensor[0] - if string.find(name, "LHT65") > -1 # If LoRaWanName contains LHT65 use LHT65- - name = string.format("LHT65-%i", sensor[1]) - end - var name_tooltip = "Dragino LHT65" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - msg += fmt.header(name, name_tooltip, battery, battery_last_seen, rssi, last_seen) - - # Sensors - var temp_int = sensor[6] - var humidity = sensor[7] - var temp_ext = sensor[8] - var door_open = sensor[9] - var door_open_last_seen = sensor[10] - msg += " - end - return msg - end #add_web_sensor() -end # class - -global.LwDeco = LwDecoLHT65 \ No newline at end of file diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/PS-L-I5.be b/tasmota/berry/extensions/LoRaWan_Decoders/PS-L-I5.be deleted file mode 100644 index 33d94f752..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/PS-L-I5.be +++ /dev/null @@ -1,114 +0,0 @@ -# LoRaWAN Decoder file for Dragino PS-LB/LS - LoRaWAN Air Water Pressure Sensor -# Model: Immersion type, 0-5m Range (PS-Lx-I5) -# -# References -# User Manual: https://wiki.dragino.com/xwiki/bin/view/Main/User%20Manual%20for%20LoRaWAN%20End%20Nodes/PS-LB%20--%20LoRaWAN%20Pressure%20Sensor/ -# Dragino Repository: https://github.com/dragino/dragino-end-node-decoder/blob/main/PS-LB/PS%20LB%20Chirpstack%20V4%20decoder.txt - -import string - -if !global.psli5Nodes # data survive to decoder reload - global.psli5Nodes = {} -end - -class LwDecoPSLI5 - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Dragino PS-LB/LS-I5"} - - var valid_values = false - var last_seen = 1451602800 - var battery_last_seen = 1451602800 - var battery = 1000 - var rssi = RSSI - var Water_deep_cm = 0 - - var Probe_mod - var IDC_input_mA - var modelRangeCm = 500 # 4mA=0cm, 20mA=500cm - - if global.psli5Nodes.find(Node) - last_seen = global.psli5Nodes.item(Node)[2] - battery_last_seen = global.psli5Nodes.item(Node)[3] - battery = global.psli5Nodes.item(Node)[4] - rssi = global.psli5Nodes.item(Node)[5] - Water_deep_cm = global.psli5Nodes.item(Node)[6] - - end - - ## SENSOR DATA ## - if 2 == FPort && 9 == Bytes.size() - ## eg 0e46 0000 197f 0000 00 - ## BATV ProbeModel mA Volt Int - last_seen = tasmota.rtc('local') - - battery_last_seen = tasmota.rtc('local') - battery = ((Bytes[0] << 8) | Bytes[1]) / 1000.0 - data.insert("BattV",battery) - - Probe_mod = Bytes[2] - IDC_input_mA = (Bytes[4]<<8 | Bytes[5])/1000.0 - - if Probe_mod == 0x00 # Immersion Sensor - if IDC_input_mA <= 4.0 - Water_deep_cm = 0 - else - Water_deep_cm = (IDC_input_mA - 4.0) * modelRangeCm / 16.0 - end - end # Probe_mod - - data.insert("WaterDepth_cm" ,Water_deep_cm) - data.insert("IDC_ma" ,IDC_input_mA) - data.insert("ModelRange_cm" ,modelRangeCm) - - valid_values = true - - ## STATUS DATA ## - elif 5 == FPort && 7 == Bytes.size() - 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]) - battery_last_seen = tasmota.rtc('local') - battery = ((Bytes[5] << 8) | Bytes[6]) / 1000.0 - valid_values = true - else - # Ignore other Fports - end #Fport - - if valid_values - if global.psli5Nodes.find(Node) - global.psli5Nodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] - global.psli5Nodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, RSSI, Water_deep_cm]) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.psli5Nodes - var name = sensor[0] - if string.find(name, "PS-L-I5") > -1 # If LoRaWanName contains PS-L-I5 use PS-L-I5- - name = string.format("PS-L-I5-%i", sensor[1]) - end - var name_tooltip = "Dragino PS-L-I5" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - msg += fmt.header(name, name_tooltip, battery, battery_last_seen, rssi, last_seen) - - # Sensors - var Water_deep_cm = sensor[6] - msg += " - end - return msg - end #add_web_sensor() -end #class - -LwDeco = LwDecoPSLI5 diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/SE01-L.be b/tasmota/berry/extensions/LoRaWan_Decoders/SE01-L.be deleted file mode 100644 index f67cada31..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/SE01-L.be +++ /dev/null @@ -1,149 +0,0 @@ -# LoRaWAN Decoder file for Dragino SE01-LB/LS Soil Sensor -# URL: https://www.dragino.com/products/agriculture-weather-station/item/277-se01-lb.html -# File Name: SE01-L.be -# -# References -# User Manual: https://wiki.dragino.com/xwiki/bin/view/Main/User%20Manual%20for%20LoRaWAN%20End%20Nodes/SE01-LB_LoRaWAN_Soil%20Moisture%26EC_Sensor_User_Manual/ -# TTN Device Repository:https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/dragino/lse01-121.js - -import string - -if !global.se01LNodes # data survive to decoder reload - global.se01LNodes = {} -end - -class LwDecoSE01L - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Dragino SE01-LB/LS"} - - var valid_values = false - var last_seen = 1451602800 - var battery_last_seen = 1451602800 - var battery = 1000 - var rssi = RSSI - - var temp = 1000 - var conductivity=0 - var moisture=0 - var dielectric=0 - var mod - var i_flag # 0: Normal uplink packet, 1: Interrupt Uplink Packet. - var s_flag # 0: No sensor was identified, 1: The sensor has been identified - - if global.se01LNodes.find(Node) - last_seen = global.se01LNodes.item(Node)[2] - battery_last_seen = global.se01LNodes.item(Node)[3] - battery = global.se01LNodes.item(Node)[4] - rssi = global.se01LNodes.item(Node)[5] - temp = global.se01LNodes.item(Node)[6] - conductivity = global.se01LNodes.item(Node)[7] - moisture = global.se01LNodes.item(Node)[8] - dielectric = global.se01LNodes.item(Node)[9] - end - - ## SENSOR DATA ## - #e.g. 0f5a 0ccc 0000 079d 0000 10 - # Battery ExtTemp Moisture Temp EC Mode,Flags - if 2 == FPort && ( Bytes.size() == 11 || Bytes.size() == 15) - last_seen = tasmota.rtc('local') - - battery_last_seen = tasmota.rtc('local') - battery=((Bytes[0]<<8 | Bytes[1]) & 0x3FFF)/1000.0 ##Battery,units:V - s_flag = (Bytes[10] >> 4) & 0x01 - i_flag = Bytes[10] & 0x0f - mod=(Bytes[10]>>7)&0x01 - - if 0==mod #Default mode - moisture=((Bytes[4]<<8 | Bytes[5])/100.0) ##moisture,units:% - conductivity=Bytes[8]<<8 | Bytes[9] - var value=Bytes[6]<<8 | Bytes[7] - if((value & 0x8000)>>15 == 0) - temp=(value/100.0) - else - temp=((value-0xFFFF)/100.0) - end - data.insert("Mode", "Default") - data.insert("Temp", temp) - - else #Raw Data mode - conductivity=Bytes[4]<<8 | Bytes[5] - moisture = Bytes[6]<<8 | Bytes[7] - dielectric = ((Bytes[8]<<8 | Bytes[9])/10.0) - data.insert("Mode", "Raw") - data.insert("DielectricConstant", dielectric) - end - - data.insert("BattV",battery) - data.insert("Moisture", moisture) - data.insert("Conductivity", conductivity) - data.insert("i_flag", i_flag) - data.insert("s_flag", s_flag) - - 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("BattV",((Bytes[5] << 8) | Bytes[6]) / 1000.0) - battery_last_seen = tasmota.rtc('local') - battery = ((Bytes[5] << 8) | Bytes[6]) / 1000.0 - valid_values = true - else - # Ignore other Fports - end #Fport - - if valid_values - if global.se01LNodes.find(Node) - global.se01LNodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] - global.se01LNodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, RSSI, temp, conductivity, moisture, dielectric, mod]) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.se01LNodes - var name = sensor[0] - if string.find(name, "SE01-L") > -1 # If LoRaWanName contains SE01-L use SE01-L- - name = string.format("SE01-L-%i", sensor[1]) - end - var name_tooltip = "Dragino SE01-L" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - msg += fmt.header(name, name_tooltip, battery, battery_last_seen, rssi, last_seen) - - # Sensors - var temp = sensor[6] - var conductivity = sensor[7] - var moisture = sensor[8] - var dielectric = sensor[9] - var mod = sensor[10] - - msg += " - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoSE01L diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/SN50v3L.be b/tasmota/berry/extensions/LoRaWan_Decoders/SN50v3L.be deleted file mode 100644 index 536bc9e23..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/SN50v3L.be +++ /dev/null @@ -1,119 +0,0 @@ -# LoRaWAN Decoder file for Dragino SN50v3-LB/LS -# -# References -# User Manual: https://wiki.dragino.com/xwiki/bin/view/Main/User%20Manual%20for%20LoRaWAN%20End%20Nodes/SN50v3-LB/ -# Codec Repository: https://github.com/dragino/dragino-end-node-decoder/tree/main/SN50_v3-LB - -import string - -if !global.DrgSN50v3LNodes # data survive to decoder reload - global.DrgSN50v3LNodes = {} -end - -class LwDecoDrgSN50v3L - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Dragino SN50v3-L"} - - var valid_values = false - var last_seen = 1451602800 - var battery_last_seen = 1451602800 - var battery = 1000 - var rssi = RSSI - var WorkingMode ='' - var WorkingModes = ['IIC','Distance', '3ADC+IIC', '3DS18B20','Weight','1Count','3Interrupt','3ADC+1DS18B20','3DS18B20+2Count','PWM','TMP117','Count+SHT31'] - - if global.DrgSN50v3LNodes.find(Node) - last_seen = global.DrgSN50v3LNodes.item(Node)[2] - battery_last_seen = global.DrgSN50v3LNodes.item(Node)[3] - battery = global.DrgSN50v3LNodes.item(Node)[4] - rssi = global.DrgSN50v3LNodes.item(Node)[5] - end - - ## SENSOR DATA ## - if 2==FPort && Bytes.size()>10 #Variable length, depending on mode, but always 11 bytes or more - valid_values = true - last_seen = tasmota.rtc('local') - - var mode=(Bytes[6] & 0x7C)>>2 - if (mode+1) > size(WorkingModes) mode = 0 end - WorkingMode = WorkingModes[mode] - data.insert("WorkingMode", WorkingMode) #mode in data = 0..11. Mode in documentation = 1..12 - - battery = (Bytes[0]<<8 | Bytes[1])/1000.0 - data.insert("BattV", battery) - battery_last_seen = tasmota.rtc('local') - - ### TBA - handle all of the many cases - if 0==mode # Mode 1 (default) - if((Bytes[2]!=0x7f)||(Bytes[3]!=0xFF)) data.insert('TempC1',(Bytes[2]<<8 | Bytes[3])/10.0) end - data.insert('Digital_IStatus', (Bytes[6]&0x02)? 'High':'Low') - data.insert('ADC1_V',(Bytes[4]<<8 | Bytes[5])/1000.0) - - data.insert('EXTI_Trigger',(Bytes[6] & 0x01)? 'TRUE':'FALSE') - data.insert('Door_status' ,(Bytes[6] & 0x80)? 'CLOSE':'OPEN') - - if((Bytes[9]<<8 | Bytes[10])==0) - data.insert('Illum',(Bytes[7]<<8 | Bytes[8])) - else - var noshowTemp = ((Bytes[7]==0x7f)&&(Bytes[8]==0xff))||((Bytes[7]==0xff)&&(Bytes[8]==0xff)) - if !noshowTemp data.insert('TempC_SHT',((Bytes[7]<<24>>16 | Bytes[8])/10.0)) end - end - - if((Bytes[9]!=0xff)||(Bytes[10]!=0xff)) data.insert('Hum_SHT',(((Bytes[9]<<8 | Bytes[10])/10.0))) end - - end #mode - - ## 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("BattV",((Bytes[5] << 8) | Bytes[6]) / 1000.0) - battery_last_seen = tasmota.rtc('local') - battery = ((Bytes[5] << 8) | Bytes[6]) / 1000.0 - valid_values = true - else - # Ignore other Fports - end #Fport - - if valid_values - if global.DrgSN50v3LNodes.find(Node) - global.DrgSN50v3LNodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] - global.DrgSN50v3LNodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, RSSI, WorkingMode]) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.DrgSN50v3LNodes - var name = sensor[0] - if string.find(name, "SN50v3-L") > -1 # If LoRaWanName contains SN50v3 use SN50v3- - name = string.format("SN50v3-L-%i", sensor[1]) - end - var name_tooltip = "Dragino SN50v3-L" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - var workingMode = sensor[6] - - msg += fmt.header(name, name_tooltip, battery, battery_last_seen, rssi, last_seen) - - # Sensors - msg += " - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoDrgSN50v3L diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/WS202.be b/tasmota/berry/extensions/LoRaWan_Decoders/WS202.be deleted file mode 100644 index 0a74af8c2..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/WS202.be +++ /dev/null @@ -1,118 +0,0 @@ -# LoRaWAN Decoder file for Milesight WS202 -# -# References -# WS202 User Manual: https://resource.milesight.com/milesight/iot/document/ws202-user-guide-en.pdf -# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/milesight-iot/ws202.js - -import string - -if !global.ws202Nodes # data survive to decoder reload - global.ws202Nodes = {} -end - -class LwDecoWS202 - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Milesight WS202"} - - var valid_values = false - - var rssi = RSSI - var last_seen = 1451602800 - var battery = 0 - var battery_last_seen = 1451602800 - var pir = 0 # 0=Normal 1=Trigger - var pir_last_seen = 1451602800 - var light = 0 # 0=Dark >0=light - var light_last_seen = 1451602800 - - if global.ws202Nodes.find(Node) - battery = global.ws202Nodes.item(Node)[4] - battery_last_seen = global.ws202Nodes.item(Node)[5] - pir = global.ws202Nodes.item(Node)[6] - pir_last_seen = global.ws202Nodes.item(Node)[7] - light = global.ws202Nodes.item(Node)[8] - light_last_seen = global.ws202Nodes.item(Node)[9] - end - - var i = 0 - while i < (Bytes.size()-1) - last_seen = tasmota.rtc('local') - - var channel_id = Bytes[i] - i += 1 - var channel_type = Bytes[i] - i += 1 - - if channel_id == 0x01 && channel_type == 0x75 - battery_last_seen = tasmota.rtc('local') - battery = Bytes[i] - i += 1 - valid_values = true - - elif channel_id == 0x03 && channel_type == 0x00 - pir_last_seen = tasmota.rtc('local') - pir = Bytes[i] == 0 ? false : true - data.insert("PIR", pir) - i += 1 - valid_values = true - - elif channel_id == 0x04 && channel_type == 0x00 - light_last_seen = tasmota.rtc('local') - light = Bytes[i] - data.insert("Light", light) - i += 1 - valid_values = true - - else - # Ignore other - valid_values = false - i = Bytes.size() - end - end - - if valid_values - if global.ws202Nodes.find(Node) - global.ws202Nodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] - global.ws202Nodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, RSSI, pir, pir_last_seen, light, light_last_seen]) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.ws202Nodes - var name = sensor[0] - if string.find(name, "WS202") > -1 # If LoRaWanName contains WS202 use WS202- - name = string.format("WS202-%i", sensor[1]) - end - var name_tooltip = "Milesight WS202" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - msg += fmt.header(name, name_tooltip, battery + 100000, battery_last_seen, rssi, last_seen) - - # Sensors - var pir = fmt.dhm(sensor[7]) - var pir_ls = fmt.dhm_tt(sensor[7]) - var pir_alt = (sensor[6] == true ? "🚫" : "🆓") # No Entry 🚫 / Free 🆓 - - var light = fmt.dhm(sensor[9]) - var light_ls = fmt.dhm_tt(sensor[9]) - var light_alt = (sensor[8] == 0) ? "🌕" : "🌞" # Moon 🌕 / Sun 🌞 - - msg += fmt.start_line() - .add_sensor( "string", pir, pir_ls, pir_alt ) - .add_sensor( "string", light, light_ls, light_alt ) - .end_line() - .get_msg() - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoWS202 diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/WS301.be b/tasmota/berry/extensions/LoRaWan_Decoders/WS301.be deleted file mode 100644 index 502473d04..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/WS301.be +++ /dev/null @@ -1,118 +0,0 @@ -# LoRaWAN Decoder file for Milesight WS301 -# -# References -# WS301 User Manual: https://resource.milesight.com/milesight/iot/document/ws301-user-guide-en.pdf -# TTN Device Repository: https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/milesight-iot/ws301.js - -import string - -if !global.ws301Nodes # data survive to decoder reload - global.ws301Nodes = {} -end - -class LwDecoWS301 - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Milesight WS301"} - - var valid_values = false - - var rssi = RSSI - var last_seen = 1451602800 - var battery = 0 - var battery_last_seen = 1451602800 - var door_open = false - var door_open_last_seen = 1451602800 - var installed = false - var installed_last_seen = 1451602800 - - if global.ws301Nodes.find(Node) - battery = global.ws301Nodes.item(Node)[4] - battery_last_seen = global.ws301Nodes.item(Node)[5] - door_open = global.ws301Nodes.item(Node)[6] - door_open_last_seen = global.ws301Nodes.item(Node)[7] - installed = global.ws301Nodes.item(Node)[8] - installed_last_seen = global.ws301Nodes.item(Node)[9] - end - - var i = 0 - while i < (Bytes.size()-1) - last_seen = tasmota.rtc('local') - - var channel_id = Bytes[i] - i += 1 - var channel_type = Bytes[i] - i += 1 - - if channel_id == 0x01 && channel_type == 0x75 - battery_last_seen = tasmota.rtc('local') - battery = Bytes[i] - i += 1 - valid_values = true - - elif channel_id == 0x03 && channel_type == 0x00 - door_open_last_seen = tasmota.rtc('local') - door_open = Bytes[i] == 0 ? false : true - data.insert("DoorOpen", ( door_open ) ? true : false) - i += 1 - valid_values = true - - elif channel_id == 0x04 && channel_type == 0x00 - installed_last_seen = tasmota.rtc('local') - installed = Bytes[i] == 0 ? true : false - data.insert("Installed", ( installed ) ? true : false) - i += 1 - valid_values = true - - else - # Ignore other - valid_values = false - i = Bytes.size() - end - end - - if valid_values - if global.ws301Nodes.find(Node) - global.ws301Nodes.remove(Node) - end - # sensor[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] - global.ws301Nodes.insert(Node, [Name, Node, last_seen, battery_last_seen, battery, RSSI, door_open, door_open_last_seen, installed, installed_last_seen]) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.ws301Nodes - var name = sensor[0] - if string.find(name, "WS301") > -1 # If LoRaWanName contains WS301 use WS301- - name = string.format("WS301-%i", sensor[1]) - end - var name_tooltip = "Milesight WS301" - var last_seen = sensor[2] - var battery_last_seen = sensor[3] - var battery = sensor[4] - var rssi = sensor[5] - msg += fmt.header(name, name_tooltip, battery + 100000, battery_last_seen, rssi, last_seen) - - # Sensors - var dopen = fmt.dhm(sensor[7]) - var dopen_tt = nil - var dopen_alt = (sensor[6] == true) ? "🔓" : "🔒" # Open Lock 🔓 / Lock 🔒 - - var inst = fmt.dhm(sensor[9]) - var inst_tt = nil - var inst_alt = (sensor[8] == true) ? "✅" : "❌" # Heavy Check Mark ✅ / Cross Mark ❌ - - msg += fmt.start_line() - .add_sensor( "string", dopen, dopen_tt, dopen_alt ) - .add_sensor( "string", inst, inst_tt, inst_alt ) - .end_line() - .get_msg() - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoWS301 diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/WS522.be b/tasmota/berry/extensions/LoRaWan_Decoders/WS522.be deleted file mode 100644 index 68658a8a2..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/WS522.be +++ /dev/null @@ -1,369 +0,0 @@ -# LoRaWAN Decoder file for Milesight WS522 -# -# References -# WS522 User Manual: https://resource.milesight.com/milesight/iot/document/ws52x-user-guide-en.pdf -# Device Decoder: https://github.com/Milesight-IoT/SensorDecoders/blob/main/WS_Series/WS52x/WS52x_Decoder.js - -import string - -if !global.ws522Nodes # data survive to decoder reload - global.ws522Nodes = {} -end - -class LwDecoWS522 - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Milesight WS522"} - - var valid_values = false - - var rssi = RSSI - - var last_seen = 1451602800 - - var voltage = 0.0 # 0.1 Volt - var active_power = 0 # Watt - var power_factor = 0 # % - var energy_sum = 0 # kW - var current = 0 - var button_state = false # false=close true=open - - var voltage_ls = 1451602800 - var active_power_ls = 1451602800 - var power_factor_ls = 1451602800 - var energy_sum_ls = 1451602800 - var current_ls = 1451602800 - var button_state_ls = 1451602800 - var command_init = false - - if global.ws522Nodes.find(Node) - voltage = global.ws522Nodes.item(Node)[4] - active_power = global.ws522Nodes.item(Node)[5] - power_factor = global.ws522Nodes.item(Node)[6] - energy_sum = global.ws522Nodes.item(Node)[7] - current = global.ws522Nodes.item(Node)[8] - button_state = global.ws522Nodes.item(Node)[9] - - voltage_ls = global.ws522Nodes.item(Node)[10] - active_power_ls = global.ws522Nodes.item(Node)[11] - power_factor_ls = global.ws522Nodes.item(Node)[12] - energy_sum_ls = global.ws522Nodes.item(Node)[13] - current_ls = global.ws522Nodes.item(Node)[14] - button_state_ls = global.ws522Nodes.item(Node)[15] - - command_init = global.ws522Nodes.item(Node)[16] - end - - var i = 0 - while i < (Bytes.size()-1) - last_seen = tasmota.rtc('local') - valid_values = true - - var channel_id = Bytes[i] - i += 1 - var channel_type = Bytes[i] - i += 1 - - # VOLTAGE - if channel_id == 0x03 && channel_type == 0x74 - voltage_ls = tasmota.rtc('local') - voltage = ((Bytes[i+1] << 8) | Bytes[i]) / 10.0 - data.insert("Voltage", voltage) - i += 2 - - # ACTIVE POWER - elif channel_id == 0x04 && channel_type == 0x80 - active_power_ls = tasmota.rtc('local') - active_power = (Bytes[i+3] << 24) | (Bytes[i+2] << 16) | (Bytes[i+1] << 8) | Bytes[i] - data.insert("Active_Power", active_power) - i += 4 - - # POWER FACTOR - elif channel_id == 0x05 && channel_type == 0x81 - power_factor_ls = tasmota.rtc('local') - power_factor = Bytes[i] - data.insert("Power_Factor", power_factor) - i += 1 - - # ENERGY SUM - elif channel_id == 0x06 && channel_type == 0x83 - energy_sum_ls = tasmota.rtc('local') - energy_sum = (Bytes[i+3] << 24) | (Bytes[i+2] << 16) | (Bytes[i+1] << 8) | Bytes[i] - data.insert("Energy_Sum", energy_sum) - i += 4 - - # CURRENT - elif channel_id == 0x07 && channel_type == 0xc9 - current_ls = tasmota.rtc('local') - current = (Bytes[i+1] << 8) | Bytes[i] - data.insert("Current", current) - i += 2 - - # STATE - elif channel_id == 0x08 && channel_type == 0x70 - button_state_ls = tasmota.rtc('local') - button_state = Bytes[i] == 1 ? true : false - data.insert("Button_State", button_state ? "Open" : "Close" ) - i += 1 - - # FE03(ReportInterval) 3C00=>60 5802=>600 - elif channel_id == 0xFE && channel_type == 0x02 - data.insert("Period", ((Bytes[i+1] << 8) | Bytes[i]) ) - i += 2 - - # FF01(ProtocolVersion) 01=>V1 - elif channel_id == 0xFF && channel_type == 0x01 - data.insert("Protocol Version", Bytes[i] ) - i += 1 - - # FF09(HardwareVersion) 0140=>V1.4 - elif channel_id == 0xFF && channel_type == 0x09 - data.insert("Hardware Version", format("v%02x.%02x", Bytes[i], Bytes[i+1]) ) - i += 2 - - # FF0a(SoftwareVersion) 0114=>V1.14 - elif channel_id == 0xFF && channel_type == 0x0A - data.insert("Software Version", format("v%02x.%02x", Bytes[i], Bytes[i+1]) ) - i += 2 - - elif channel_id == 0xFF && channel_type == 0x0B i += 1 # FF0b(PowerOn) Deviceison - elif channel_id == 0xFF && channel_type == 0x16 i += 8 # FF16(DeviceSN) 16digits - elif channel_id == 0xFF && channel_type == 0x0F i += 1 # FF0f(DeviceType) 00:ClassA,01:ClassB,02:ClassC - elif channel_id == 0xFF && channel_type == 0xFF i += 2 # TSL VERSION - elif channel_id == 0xFF && channel_type == 0xFE i += 1 # RESET EVENT - - elif channel_id == 0xFE && channel_type == 0x03 i += 2 # id=0xFE yy Downlink Reporting Event - elif channel_id == 0xFE && channel_type == 0x10 i += 1 - elif channel_id == 0xFE && channel_type == 0x22 i += 4 - elif channel_id == 0xFE && channel_type == 0x23 i += 2 - elif channel_id == 0xFE && channel_type == 0x24 i += 2 - elif channel_id == 0xFE && channel_type == 0x25 i += 2 - elif channel_id == 0xFE && channel_type == 0x26 i += 1 - elif channel_id == 0xFE && channel_type == 0x27 i += 1 - elif channel_id == 0xFE && channel_type == 0x28 i += 1 - elif channel_id == 0xFE && channel_type == 0x2F i += 1 - elif channel_id == 0xFE && channel_type == 0x30 i += 2 - - else - log( string.format("WS522: something missing? id={%s} type={%s}", channel_id, channel_type), 1) - - # Ignore other - valid_values = false - i = Bytes.size() - end - end - - if valid_values - if !command_init - # - # Downlink Commands - # ================= =============================== - # ✅ 08 00 00 FF Close - # ✅ 08 01 00 FF Open - # ✅ FF 03 ss ss SetReportingInterval (2 bytes, seconds) - # ✅ FF 10 FF Reboot - # ✅ FF 22 00 ss ss aa AddDelayTask (ss=delay seconds, aa=action 10=close/11=open) - # ✅ FF 23 00 FF DeleteDelayTask - # ❓ FF 24 xx yy OvercurrentAlarm (xx: 00=off/01=on, yy=threshold) - # ✅ FF 25 00 xx ButtonLock (xx: 00=off/80=on) - # ✅ FF 26 yy PowerConsumption (yy: 00=off/01=on) - # ✅ FF 27 FF ResetPowerConsumption - # ✅ FF 28 FF EnquireElectricalStatus - # ✅ FF 2F xx LEDMode (xx: 00=off/01=on) - # ❓ FF 30 xx yy OvercurrentProtection (XX: 00=off/01=on, YY=threshold) - # - # ✅ = Verified ❓= Not verified yet ❌=Issue, under investigation - # - var lwdecode = global.LwTools_cls() - var pfx = 'LwWS522' - - tasmota.remove_cmd( pfx + 'Power' ) - tasmota.add_cmd( pfx + 'Power', - def (cmd, idx, payload) - return lwdecode.SendDownlinkMap(global.ws522Nodes, cmd, idx, payload, { '1|ON': ['080100FF', 'ON'], '0|OFF': ['080000FF', 'OFF'] }) - end - ) - - tasmota.remove_cmd( pfx + 'Period' ) - tasmota.add_cmd( pfx + 'Period', - def (cmd, idx, payload) - return lwdecode.SendDownlink(global.ws522Nodes, cmd, idx, format('FF03%s',lwdecode.uint16le(number(payload))), number(payload)) - end - ) - - tasmota.remove_cmd( pfx + 'Reboot' ) - tasmota.add_cmd( pfx + 'Reboot', - def (cmd, idx, payload) - return lwdecode.SendDownlink(global.ws522Nodes, cmd, idx, 'FF10FF', 'Done') - end - ) - - tasmota.remove_cmd( pfx + 'ResetPowerUsage' ) - tasmota.add_cmd( pfx + 'ResetPowerUsage', - def (cmd, idx, payload) - return lwdecode.SendDownlink(global.ws522Nodes, cmd, idx, 'FF27FF', 'Done') - end - ) - - tasmota.remove_cmd( pfx + 'PowerLock' ) - tasmota.add_cmd( pfx + 'PowerLock', - def (cmd, idx, payload) - return lwdecode.SendDownlinkMap(global.ws522Nodes, cmd, idx, payload, { '1|ON': ['FF250080', 'ON'], '0|OFF': ['FF250000', 'OFF'] }) - end - ) - - tasmota.remove_cmd( pfx + 'DelayTask' ) - tasmota.add_cmd( pfx + 'DelayTask', - def (cmd, idx, payload) - var parts = string.split(payload,',') - if parts.size() != 2 - return tasmota.resp_cmnd_str("Usage: delay_seconds,action (action: 0=close, 1=open)") - end - var delay = number(parts[0]) - var action = number(parts[1]) == 1 ? '11' : '10' - return lwdecode.SendDownlink(global.ws522Nodes, cmd, idx, format('FF2200%s%s',lwdecode.uint16le(delay),action), payload) - end - ) - - tasmota.remove_cmd( pfx + 'DelTask' ) - tasmota.add_cmd( pfx + 'DelTask', - def (cmd, idx, payload) - return lwdecode.SendDownlink(global.ws522Nodes, cmd, idx, 'FF2300FF', 'Done') - end - ) - - tasmota.remove_cmd( pfx + 'OcAlarm' ) - tasmota.add_cmd( pfx + 'OcAlarm', - def (cmd, idx, payload) - var parts = string.split(payload,',') - if parts.size() != 2 - return tasmota.resp_cmnd_str("Usage: enable,threshold (enable: 0/1, threshold: 0-255)") - end - var enable = number(parts[0]) ? '01' : '00' - var threshold = format('%02X', number(parts[1])) - return lwdecode.SendDownlink(global.ws522Nodes, cmd, idx, format('FF24%s%s',enable,threshold), payload) - end - ) - - tasmota.remove_cmd( pfx + 'PwrUsage' ) - tasmota.add_cmd( pfx + 'PwrUsage', - def (cmd, idx, payload) - return lwdecode.SendDownlinkMap(global.ws522Nodes, cmd, idx, payload, { '1|ON': ['FF2601FF', 'ON'], '0|OFF': ['FF2600FF', 'OFF'] }) - end - ) - - tasmota.remove_cmd( pfx + 'Status' ) - tasmota.add_cmd( pfx + 'Status', - def (cmd, idx, payload) - return lwdecode.SendDownlink(global.ws522Nodes, cmd, idx, 'FF28FF', 'Done') - end - ) - - tasmota.remove_cmd( pfx + 'LED' ) - tasmota.add_cmd( pfx + 'LED', - def (cmd, idx, payload) - return lwdecode.SendDownlinkMap(global.ws522Nodes, cmd, idx, payload, { '1|ON': ['FF2F01', 'ON'], '0|OFF': ['FF2F00', 'OFF'] }) - end - ) - - tasmota.remove_cmd( pfx + 'OcProt' ) - tasmota.add_cmd( pfx + 'OcProt', - def (cmd, idx, payload) - var parts = string.split(payload,',') - if parts.size() != 2 - return tasmota.resp_cmnd_str("Usage: enable,threshold (enable: 0/1, threshold: 0-255)") - end - var enable = number(parts[0]) ? '01' : '00' - var threshold = format('%02X', number(parts[1])) - return lwdecode.SendDownlink(global.ws522Nodes, cmd, idx, format('FF30%s%s',enable,threshold), payload) - end - ) - - command_init = true - end - - if global.ws522Nodes.find(Node) - global.ws522Nodes.remove(Node) - end - - global.ws522Nodes.insert(Node, - [ # sensor - Name, # [0] - Node, # [1] - last_seen, # [2] - rssi, # [3] - voltage, # [4] - active_power, # [5] - power_factor, # [6] - energy_sum, # [7] - current, # [8] - button_state, # [9] - voltage_ls, # [10] - active_power_ls, # [11] - power_factor_ls, # [12] - energy_sum_ls, # [13] - current_ls, # [14] - button_state_ls, # [15] - command_init # [16] - ] - ) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - for sensor: global.ws522Nodes - var name = sensor[0] - - # If LoRaWanName contains WS522 use WS522- - if string.find(name, "WS522") > -1 - name = string.format("WS522-%i", sensor[1]) - end - - var name_tooltip = "Milesight WS522" - - var last_seen = sensor[2] - var rssi = sensor[3] - - msg += fmt.header(name, name_tooltip, 1000, last_seen, rssi, last_seen) - - # Sensors - var voltage = sensor[4] - var voltage_tt = fmt.dhm(sensor[10]) - - var active_power = sensor[5] - var active_power_tt = fmt.dhm(sensor[11]) - - var power_factor = sensor[6] - var power_factor_tt = fmt.dhm(sensor[12]) - - var current = sensor[8] - var current_tt = fmt.dhm(sensor[14]) - - var button_state = fmt.dhm(sensor[15]) - var button_state_tt = fmt.dhm(sensor[15]) - var button_state_icon = (sensor[9] ? " 🟢 " : " ⚫ ") # Large Green Circle 🟢 | Medium Black Circle ⚫ - - var energy_sum = sensor[7] - var energy_sum_tt = fmt.dhm(sensor[13] ) - - # Formatter Value Tooltip alternative icon - # ================ ============ ================== ================ - msg += fmt.start_line() - .add_sensor("volt", voltage, voltage_tt ) - .add_sensor("milliamp", current, current_tt ) - .add_sensor("power_factor%", power_factor, power_factor_tt ) - .add_sensor("power", active_power, active_power_tt ) - .next_line() - .add_sensor("string", button_state, button_state_tt, button_state_icon ) - .add_sensor("energy", energy_sum, energy_sum_tt ) - .end_line() - .get_msg() - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoWS522 diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/autoexec.be b/tasmota/berry/extensions/LoRaWan_Decoders/autoexec.be deleted file mode 100644 index 202cc2ace..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/autoexec.be +++ /dev/null @@ -1,9 +0,0 @@ -# rm LoRaWan_Decoders.tapp; zip -j -0 LoRaWan_Decoders.tapp LoRaWan_Decoders/* -do # embed in `do` so we don't add anything to global namespace - import introspect - var lorawan_decoders = introspect.module('lorawan_decoders', true) # load module but don't cache - tasmota.add_extension(lorawan_decoders) -end - -# to remove: -# tasmota.unload_extension('LoRaWan Decoders') diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/lorawan_decoders.be b/tasmota/berry/extensions/LoRaWan_Decoders/lorawan_decoders.be deleted file mode 100644 index e6275bb29..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/lorawan_decoders.be +++ /dev/null @@ -1,590 +0,0 @@ -################################################################################### -# Decode LoRaWan devices -# -# Copyright (C) 2025 Stephan Hadinger & Theo Arends -# -# Decoder files are modeled on the *.js files found here: -# https://github.com/TheThingsNetwork/lorawan-devices/tree/master/vendor -# -# rm LoRaWan_Decoders.tapp; zip -j -0 LoRaWan_Decoders.tapp LoRaWan_Decoders/* -################################################################################### - -import mqtt -import string - -################################################################################### -# Display Configuration LoRaWan GUI -#---------------------------------------------------------------------------------# -import webserver - -class lorawan_settings - var max_node_cached - - ################################################################################# - # init - # - # install the extension and allocate all resources - ################################################################################# - def init() - self.max_node_cached = nil - - tasmota.add_driver(self) - if tasmota.is_network_up() - self.web_add_handler() # if init is called after the network is up, `web_add_handler` event is not fired - end - end - - def close() - webserver.remove_route("/lrw") - tasmota.remove_driver(self) - end - - def web_add_config_button() - webserver.content_send("

") - end - - def _get_max_nodes() - if !self.max_node_cached - var enables = string.split(tasmota.cmd('LoRaWanNode', true).find('LoRaWanNode'), ',') - self.max_node_cached = enables.size() - end - return self.max_node_cached - end - - #- this method displays the web page -# - def pageLoRaWAN() - if !webserver.check_privileged_access() return nil end - - var inode = 1 - var cmdArg - if webserver.has_arg('save') - inode = int(webserver.arg('node')) - tasmota.cmd(format('LoRaWanAppKey%i %s', inode, webserver.arg('ak')), true) - cmdArg = webserver.arg('dc') - if !cmdArg cmdArg = '"' end - tasmota.cmd(format('LoRaWanDecoder%i %s', inode, cmdArg), true) - cmdArg = webserver.arg('an') - if !cmdArg cmdArg = '"' end - tasmota.cmd(format('LoRaWanName%i %s', inode, cmdArg), true) - cmdArg = webserver.arg('ce') - if !cmdArg - cmdArg = '0' - else - cmdArg = '1' - end - tasmota.cmd(format('LoRaWanNode%i %s', inode, cmdArg), true) - end - - var appKey, decoder, name, enabled - var hintAK = '32 character Application Key' - var hintDecoder = 'Decoder file, ending in .be' - var hintAN = 'Device name for MQTT messages' - var arg = 'LoRaWanNode' - var enables = string.split(tasmota.cmd(arg, true).find(arg), ',') # [1,!2,!3,!4,5,6] - var maxnode = enables.size() - - webserver.content_start("LoRaWAN") #- title of the web page -# - webserver.content_send_style() #- send standard Tasmota styles -# - - webserver.content_send( - "" - "") - - webserver.content_send( - format("
" - " LoRaWan End Device " - "
")) #- Add space and indent to align form tabs -# - for node:1 .. maxnode - webserver.content_send(format("", node, node, node)) - end - webserver.content_send("




") #- Terminate indent and add space -# - - for node:1 .. maxnode - enabled = "" - if enables[node-1][0] != '!' - enabled = ' checked' - end - arg = format('LoRaWanAppKey%i', node) - appKey = tasmota.cmd(arg, true).find(arg) - arg = format('LoRaWanName%i', node) - name = tasmota.cmd(arg, true).find(arg) - arg = format('LoRaWanDecoder%i', node) - decoder = tasmota.cmd(arg, true).find(arg) - - webserver.content_send( - format("", node, enabled, hintAK, hintAK, appKey, hintAN, name, hintDecoder, hintDecoder, decoder, node)) - end - - webserver.content_send("
") - - webserver.content_button(webserver.BUTTON_CONFIGURATION) #- button back to conf page -# - webserver.content_stop() #- end of web page -# - end - - #- this is called at Tasmota start-up, as soon as Wifi/Eth is up and web server running -# - def web_add_handler() - #- we need to register a closure, not just a function, that captures the current instance -# - webserver.on("/lrw", / -> self.pageLoRaWAN()) - end -end -#---------------------------------------------------------------------------------# -# Display Configuration LoRaWan GUI -################################################################################### - - -################################################################################### -# global LwSensorFormatter_cls -#---------------------------------------------------------------------------------# -class LwSensorFormatter_cls - static var Formatter = { - "string": { "u": nil, "f": " %s", "i": nil }, - "volt": { "u": "V", "f": " %.1f", "i": "⚡" }, - "milliamp": { "u": "mA", "f": " %.0f", "i": "🔌" }, - "power_factor%": { "u": "%", "f": " %.0f", "i": "📊" }, - "power": { "u": "W", "f": " %.0f", "i": "💡" }, - "energy": { "u": "Wh", "f": " %.0f", "i": "🧮" }, - "altitude": { "u": "mt", "f": " %d", "i": "⛰" }, - "empty": { "u": nil, "f": nil, "i": nil } - } - - var msg_buffer - - def init() - self.msg_buffer = bytes(512) - self.msg_buffer.clear() - tasmota.add_driver(self) - end - - def close() - tasmota.remove_driver(self) - end - - def start_line() - self.msg_buffer .. "
", name_tooltip, name) - - if battery < 1000 - # Battery low <= 2.5V (0%), high >= 3.1V (100%) - var batt_percent = (battery * 1000) - 2500 - batt_percent /= 6 # 3.1V - 2.5V = 0.6V = 100% - if batt_percent < 0 batt_percent = 0 end - if batt_percent > 98 batt_percent = 98 end # 98% / 14px = 7 - batt_percent /= 7 # 1..14px showing battery load - msg += format("", - battery, self.dhm(battery_last_seen), batt_percent) - elif battery >= 100000 && battery <= 100100 # battery already expressed in % - var pbatt = battery - 100000 - var batt_percent = pbatt - if batt_percent > 98 batt_percent = 98 end # 98% / 14px = 7 - batt_percent /= 7 # 1..14px showing battery load - msg += format("", - pbatt, self.dhm(battery_last_seen), batt_percent) - else - msg += "" - end - - if rssi < 1000 - if rssi < -132 rssi = -132 end - var num_bars = 4 - ((rssi * -1) / 33) - msg += format("" # Close RSSI - else - msg += "" - end - - msg += format("", self.dhm(last_seen)) - - return msg - end -end - -global.LwSensorFormatter_cls = LwSensorFormatter_cls -#---------------------------------------------------------------------------------# -# global LwSensorFormatter_cls -################################################################################### - - -################################################################################### -# global LwTools_cls -#---------------------------------------------------------------------------------# -class LwTools_cls - def init() - tasmota.add_driver(self) - end - - def close() - tasmota.remove_driver(self) - end - - def uint16le(value) - return string.format( "%02x%02x", - value & 0xFF, - (value >> 8) & 0xFF - ) - end - - def uint32le(value) - return string.format( "%02x%02x%02x%02x", - value & 0xFF, - (value >> 8) & 0xFF, - (value >> 16) & 0xFF, - (value >> 24) & 0xFF - ) - end - - def SendDownlink(nodes, cmd, idx, payload, ok_result) - if !nodes.find(idx) return nil end - - var _send = 'LoRaWanSend' - var _cmdSend = _send + str(idx) + ' ' + payload - var _out = tasmota.cmd(_cmdSend, true) - - return tasmota.resp_cmnd( - format('{"%s%i":"%s","%s":"%s","Payload":"%s"}', - cmd, - idx, - ok_result, - _send, - _out[_send], - payload - ) - ) - end - - def SendDownlinkMap(nodes, cmd, idx, payload, choice_map) - var key = string.toupper(str(payload)) - for choice_key : choice_map.keys() - if string.find(choice_key, key) >= 0 && (choice_key == key || string.find(choice_key, '|' + key + '|') >= 0 || string.find(choice_key, key + '|') == 0 || string.find(choice_key, '|' + key) == size(choice_key) - size(key) - 1) - var choice = choice_map[choice_key] - return self.SendDownlink(nodes, cmd, idx, choice[0], choice[1]) - end - end - return tasmota.resp_cmnd_error() - end -end - -global.LwTools_cls = LwTools_cls -#---------------------------------------------------------------------------------# -# global LwTools_cls -################################################################################### - - -################################################################################### -# extension lorawan_decoders -#---------------------------------------------------------------------------------# -global.LwRegions = ["EU868","US915","IN865","AU915","KZ865","RU864","AS923","AS923-1","AS923-2","AS923-3"] -global.LwDeco = nil - -class lorawan_decoders - var lw_decoders - var topic_cached - var last_payload_hash - var web_msg_cache - var cache_timeout - var lw_settings - - ################################################################################# - # init - # - # install the extension and allocate all resources - ################################################################################# - def init() - self.lw_decoders = {} - self.last_payload_hash = 0 - self.web_msg_cache = "" - self.cache_timeout = 0 - - self._cache_topic() - - tasmota.add_driver(self) - - tasmota.add_rule("LwReceived", /value, trigger, payload -> self.lw_decode(payload)) - - tasmota.cmd('LoraOption3 off', true) # Disable embedded decoding - tasmota.cmd('SetOption100 off', true) # Keep LwReceived in JSON message - tasmota.cmd('SetOption118 off', true) # Keep SENSOR as subtopic name - tasmota.cmd('SetOption119 off', true) # Keep device address in JSON message -# tasmota.cmd('SetOption147 on', true) # Hide LwReceived MQTT message but keep rule processing - tasmota.cmd('LoRaWanBridge on', true) - - self.lw_settings = lorawan_settings() - end - - ################################################################################# - # unload - # - # Uninstall the extension and deallocate all resources - ################################################################################# - def unload() - tasmota.remove_rule("LwReceived") - tasmota.remove_driver(self.lw_settings) - tasmota.remove_driver(self) - global.undef("LwSensorFormatter_cls") - global.undef("LwTools_cls") - global.undef("LwRegions") - global.undef("LwDeco") - end - - def _cache_topic() - var full_topic = tasmota.cmd('_FullTopic',true)['FullTopic'] - var topic = tasmota.cmd('_Status',true)['Status']['Topic'] - var prefix = tasmota.cmd('_Prefix',true)['Prefix3'] - - self.topic_cached = string.replace(string.replace(full_topic, '%topic%', topic), '%prefix%', prefix) + 'SENSOR' - end - - def _calculate_payload_hash(payload) - var hash = 0 - for i:0..payload.size()-1 - hash = (hash * 31 + payload[i]) & 0xFFFFFFFF - end - return hash - end - - def lw_decode(data) - import json - - var device_data = data['LwReceived'] - var device_name = device_data.keys()() - var device_info = device_data[device_name] - - var decoder = device_info.find('Decoder') - if !decoder return true end - - var payload = device_info['Payload'] - if !payload || payload.size() == 0 return true end - - if !self.lw_decoders.find(decoder) - try - global.LwDeco = nil -# load(decoder) - load(".extensions/LoRaWan_Decoders.tapp#" + decoder) - if global.LwDeco - self.lw_decoders[decoder] = global.LwDeco - else - log("LwD: Unable to load decoder",1) - return true - end - except .. as e, m - log(format("LwD: Decoder load error: %s", m),1) - return true - end - end - - var hashCheck - # check if the decoder driver have the hashCheck properties - try - hashCheck = self.lw_decoders[decoder].hashCheck - except .. as e, m - hashCheck = true - end - - if hashCheck - var current_hash = self._calculate_payload_hash(payload) - if current_hash == self.last_payload_hash return true end - self.last_payload_hash = current_hash - end - - try - var decoded = self.lw_decoders[decoder].decodeUplink( - device_info['Name'], - device_info['Node'], - device_info['RSSI'], - device_info['FPort'], - payload - ) - - decoded['Node'] = device_info['Node'] - decoded['RSSI'] = device_info['RSSI'] - - var mqtt_data - if tasmota.get_option(83) == 0 # SetOption83 - Remove LwDecoded form JSON message (1) - mqtt_data = {"LwDecoded": {device_name: decoded}} - else - mqtt_data = {device_name: decoded} - end - - var topic - if tasmota.get_option(89) == 1 # SetOption89 - Distinct MQTT topics per device (1) - topic = format("%s/%s%s", self.topic_cached, device_info['DevEUIh'], device_info['DevEUIl']) - else - topic = self.topic_cached - end - - mqtt.publish(topic, json.dump(mqtt_data)) - tasmota.global.restart_flag = 0 # Signal LwDecoded successful (default state) - - except .. as e, m - log(format("LwD: Decode error for %s: %s", device_name, m),1) - end - - return true - end - - #------------------------------------------------------------ - Display sensor value in the web UI and react to button - Called every WebRefresh time - ------------------------------------------------------------# - def web_sensor() - var current_time = tasmota.millis() - - if current_time < self.cache_timeout - tasmota.web_send_decimal(self.web_msg_cache) - return - end - - var msg = "" - for decoder: self.lw_decoders - msg += decoder.add_web_sensor() - end - - if msg - var full_msg = format("
┆" # | - if tempC1 < 1000 - msg += string.format(" ☀️ %.1f°C", tempC1) # Sunshine - Temperature - end - - var tempC2 = sensor[7] - if tempC2 < 1000 - msg += string.format(" ☀️ %.1f°C", tempC2) - end - - var tempC3 = sensor[8] - if tempC3 < 1000 - msg += string.format(" ☀️ %.1f°C", tempC3) - end - msg += "{e}" # =
┆" # | - msg += string.format(" ⭳️ %.0fmm", distance) # ⭳ - msg += "{e}" # =
┆" # | - msg += string.format(" ☀️ %.1f°C", temperature) # Sunshine - Temperature - msg += string.format(" 💧 %.1f%%", humidity) # Raindrop - Humidity - msg += string.format(" %s %s", (door_open) ? "🔓" : "🔒", # Open or Closed lock - Door - fmt.dhm(door_open_last_seen)) - msg += "{e}" # =
┆" # | - msg += string.format(" %s %s", (door_open) ? "🔓" : "🔒", # Open or Closed lock - Door - fmt.dhm(door_open_last_seen)) - msg += "{e}" # =
┆" # | - if temp_int < 1000 - msg += string.format(" ☀️ %.1f°C", temp_int) # Sunshine - Temperature internal - msg += string.format(" 💧 %.1f%%", humidity) # Raindrop - Humidity - end - if temp_ext < 1000 - msg += string.format(" ☀️ ext %.1f°C", temp_ext) # Sunshine - Temperature external - end - msg += "{e}" # =
┆" # | - if temp_int < 1000 - msg += string.format(" ☀️ %.1f°C", temp_int) # Sunshine - Temperature - msg += string.format(" 💧 %.1f%%", humidity) # Raindrop - Humidity - end - if temp_ext < 1000 - msg += string.format(" ☀️ ext %.1f°C", temp_ext) # Sunshine - Temperature external - end - if door_open < 1000 - msg += string.format(" %s %s", (door_open) ? "🔓" : "🔒", # Open or Closed lock - Door - fmt.dhm(door_open_last_seen)) - end - msg += "{e}" # =
┆" # | - msg += string.format(" ⭳️ %.1fcm", Water_deep_cm) # тн│ - msg += "{e}" # =
┆" # | - if mod - msg += string.format(" κ %.1f", dielectric ) # Kappa - dielectric - msg += string.format(" 💧️ %u", moisture) # Raindrop - moisture - msg += string.format(" σ %u", conductivity) # Sigma - conductivity - msg += " (raw)" - else - msg += string.format(" ☀️ %.1f°C", temp) # Sunshine/Color - Temperature - msg += string.format(" 💧️ %.1f%%", moisture) # Raindrop/Color - moisture - msg += string.format(" σ %uuS/cm", conductivity) # Sigma - conductivity - end - - msg += "{e}" # =
┆" # | - - msg += string.format(" ️ %s", workingMode) - - msg += "{e}" # =
┆" - return self - end - - def end_line() - self.msg_buffer .. "{e}" - return self - end - - def next_line() - self.msg_buffer .. "{e}
┆" - return self - end - - def begin_tooltip(ttip) - self.msg_buffer .. format(" 
", ttip) - return self - end - - def end_tooltip() - self.msg_buffer .. "
" - return self - end - - def add_link(title, url, target) - if !target target = "_blank" end - self.msg_buffer .. format(" %s", target, url, title) - return self - end - - def add_sensor(formatter, value, tooltip, alt_icon) - - if tooltip self.begin_tooltip(tooltip) end - - var fmt = self.Formatter.find(formatter) - - if alt_icon - self.msg_buffer .. format(" %s", alt_icon) - elif fmt && fmt.find("i") && fmt["i"] - self.msg_buffer .. format(" %s", fmt["i"]) - end - - if fmt && fmt.find("f") && fmt["f"] - self.msg_buffer .. format(fmt["f"], value) - else - self.msg_buffer .. str(value) - end - - if fmt && fmt.find("u") && fmt["u"] - self.msg_buffer .. format("%s", fmt["u"]) - end - - if tooltip self.end_tooltip() end - return self - end - - def get_msg() - return self.msg_buffer.asstring() - end - - def dhm(last_time) - var since = tasmota.rtc('local') - last_time - var unit = "d" - if since > 86400 - since /= 86400 - if since > 99 since = 99 end - elif since > 3600 - since /= 3600 - unit = "h" - else - since /= 60 - unit = "m" - end - return format("%02d%s", since, unit) - end - - def dhm_tt(last_time) - return format("Received %s ago", self.dhm(last_time)) - end - - def header(name, name_tooltip, battery, battery_last_seen, rssi, last_seen) - var msg = format("
%s 
", rssi) - for j:0..3 - msg += format("", j, (num_bars < j) ? " o30" : "") # Bars - end - msg += "
 🕗%s
" # Terminate current two column table and open new table - "" - "{t}%s{t}", - msg) - - self.web_msg_cache = full_msg - self.cache_timeout = current_time + 5000 - tasmota.web_send_decimal(full_msg) - end - end -end - -return lorawan_decoders() -#---------------------------------------------------------------------------------# -# extension lorawan_decoders -################################################################################### diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/manifest.json b/tasmota/berry/extensions/LoRaWan_Decoders/manifest.json deleted file mode 100644 index 422e2e1e5..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "LoRaWan Decoders", - "version": "0x190A0200", - "description": "Decode LoRaWan devices", - "author": "Theo Arends", - "min_tasmota": "0x0E060001", - "features": "" -} diff --git a/tasmota/berry/extensions/LoRaWan_Decoders/walker.be b/tasmota/berry/extensions/LoRaWan_Decoders/walker.be deleted file mode 100644 index db23325ce..000000000 --- a/tasmota/berry/extensions/LoRaWan_Decoders/walker.be +++ /dev/null @@ -1,242 +0,0 @@ -# LoRaWAN Decoder file for Glamos Walker -# -# References https://glamos.eu/product/walker/ - -import string - -if !global.walkerNodes - global.walkerNodes = {} -end - -class LwDecoWALKER - static var hashCheck = false - - static def decodeUplink(Name, Node, RSSI, FPort, Bytes) - var data = {"Device":"Glamos WALKER"} - - var valid_values = false - - var rssi = RSSI - - var last_seen = 1451602800 - - var latitude = 0.0 # 3 bytes - var longitude = 0.0 # 3 bytes - var altitude = 0.0 # 2 bytes - var antenna = 0 # 1 byte (manually set by user on the device) - var position = 0 # 1 byte (manually set by user on the device) - var counter = 0 - var command_init = false - var track_enabled = false - var history = [] - - if global.walkerNodes.find(Node) - latitude = global.walkerNodes.item(Node)[4] - longitude = global.walkerNodes.item(Node)[5] - altitude = number( global.walkerNodes.item(Node)[6] ) - antenna = number( global.walkerNodes.item(Node)[7] ) - position = number( global.walkerNodes.item(Node)[8] ) - counter = number( global.walkerNodes.item(Node)[9] ) - command_init = global.walkerNodes.item(Node)[10] - track_enabled = global.walkerNodes.item(Node)[11] - history = global.walkerNodes.item(Node)[12] - end - - try - if Bytes.size() == 10 - valid_values = true - - last_seen = tasmota.rtc('local') - - latitude = (((Bytes[0] << 16) | (Bytes[1] << 8) | (Bytes[2] << 0)) / 16777215.0) * 180.0 - 90.0 - longitude = (((Bytes[3] << 16) | (Bytes[4] << 8) | (Bytes[5] << 0)) / 16777215.0) * 360.0 - 180.0 - altitude = (Bytes[6] << 8) | (Bytes[7] << 0) - antenna = Bytes[8] - position = Bytes[9] - counter += 1 - - data.insert("Latitude", latitude) - data.insert("Longitude", longitude) - data.insert("Altitude", altitude) - data.insert("Antenna", antenna) - data.insert("Position", position) - data.insert("Counter", counter) - data.insert("Tracking", track_enabled) - data.insert("History Size", history.size()) - end - except .. as e, m - print(e .. ': ' .. m) - end - - if valid_values - if !command_init - var pfx = 'Walker' - - tasmota.remove_cmd(pfx + 'ReloadCmd') - tasmota.add_cmd( pfx + 'ReloadCmd', - def (cmd, idx, payload) - if global.walkerNodes.find(idx) - global.walkerNodes.item(idx)[10] = false # reload command after a 'LwReload' - return tasmota.resp_cmnd_done() - end - end - ) - - tasmota.remove_cmd(pfx + 'ResetCounter') - tasmota.add_cmd( pfx + 'ResetCounter', - def (cmd, idx, payload) - if global.walkerNodes.find(idx) - global.walkerNodes.item(idx)[9] = 0 - return tasmota.resp_cmnd_done() - end - end - ) - - tasmota.remove_cmd(pfx + 'EnableTrack') - tasmota.add_cmd( pfx + 'EnableTrack', - def (cmd, idx, payload) - if global.walkerNodes.find(idx) - global.walkerNodes.item(idx)[11] = true - return tasmota.resp_cmnd_done() - end - end - ) - - tasmota.remove_cmd(pfx + 'DisableTrack') - tasmota.add_cmd( pfx + 'DisableTrack', - def (cmd, idx, payload) - if global.walkerNodes.find(idx) - global.walkerNodes.item(idx)[11] = false - return tasmota.resp_cmnd_done() - end - end - ) - - tasmota.remove_cmd(pfx + 'ClearHistory') - tasmota.add_cmd( pfx + 'ClearHistory', - def (cmd, idx, payload) - if global.walkerNodes.find(idx) - global.walkerNodes.item(idx)[12].clear() - return tasmota.resp_cmnd_done() - end - end - ) - - tasmota.remove_cmd(pfx + 'GetHistory') - tasmota.add_cmd( pfx + 'GetHistory', - def (cmd, idx, payload) - try - if global.walkerNodes.find(idx) - import json - return tasmota.resp_cmnd( '{"' .. pfx .. 'History":' .. json.dump( global.walkerNodes.item(idx)[12]) .. '}' ) - end - except .. as e, m - print(e .. ': ' .. m) - end - end - ) - - command_init = true - end - - try - if track_enabled - if history.size() > 50 # may be dynamic? - history.remove(0) - end - - history.push( [ - last_seen - ,latitude - ,longitude - ,altitude - ,antenna - ,position - ,counter - ]) - end - except .. as e, m - print(e .. ': ' .. m) - end - - if global.walkerNodes.find(Node) - global.walkerNodes.remove(Node) - end - - global.walkerNodes.insert(Node, - [ # sensor - Name # [0] - ,Node # [1] - ,last_seen # [2] - ,rssi # [3] - ,latitude # [4] - ,longitude # [5] - ,altitude # [6] - ,antenna # [7] - ,position # [8] - ,counter # [9] - ,command_init # [10] - ,track_enabled # [11] - ,history # [12] - ] - ) - end - - return data - end #decodeUplink() - - static def add_web_sensor() - var fmt = global.LwSensorFormatter_cls() - var msg = "" - try - for sensor: global.walkerNodes - var name = sensor[0] - - # If LoRaWanName contains WALKER use WALKER- - if string.find(name, "WALKER") > -1 - name = string.format("WALKER-%i", sensor[1]) - end - - var name_tooltip = "Glamos Walker" - - var last_seen = sensor[2] - var rssi = sensor[3] - - msg += fmt.header(name, name_tooltip, 1000, last_seen, rssi, last_seen) - - # Sensors - var latitude = sensor[4] - var longitude = sensor[5] - var altitude = sensor[6] - var antenna = sensor[7] - var position = sensor[8] - var counter = sensor[9] - var track_enabled = sensor[11] - var history = sensor[12] - - var latlon = format("🎯 %.4f, %.4f", latitude, longitude) - var map_link = format("https://www.google.com/maps/place/%.6f,%.6f",latitude,longitude) - - var te_value = (track_enabled ? str(history.size()) : "") - var te_tt = "Tracking " .. (track_enabled ? "On" : "Off") - var te_icon = (track_enabled ? "🔴" : "⚪") - - msg += fmt.start_line() - .add_link( latlon, map_link ) # Direct History 🎯 - .next_line() - .add_sensor("altitude", altitude, "Altitude" ) # Mountain ⛰ - .add_sensor("string", format("%d", antenna), "Antenna", "📡" ) # Satellite Antenna 📡 - .add_sensor("string", format("%d", position), "Position", "📍" ) # Round Pushpin 📍 - .add_sensor("string", format("%d", counter), "Counter", "⏱" ) # Chronometer ⏱️ - .add_sensor("string", te_value, te_tt, te_icon) # Track ON/OFF 🔴 ⚪ - .end_line() - .get_msg() - end - except .. as e, m - print(e .. ': ' .. m) - end - return msg - end #add_web_sensor() -end #class - -global.LwDeco = LwDecoWALKER