From d6a7939a58846d75799692c627cb6c19a38b97a0 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 26 Sep 2025 14:22:42 +0200 Subject: [PATCH] Add another statedata example --- .../examples/{ => statedata}/statedata.be | 0 .../examples/{ => statedata}/statedata_v2.be | 0 .../examples/{ => statedata}/statedata_v3.be | 0 .../berry/examples/statedata/statedata_v4.be | 288 ++++++++++++++++++ 4 files changed, 288 insertions(+) rename tasmota/berry/examples/{ => statedata}/statedata.be (100%) rename tasmota/berry/examples/{ => statedata}/statedata_v2.be (100%) rename tasmota/berry/examples/{ => statedata}/statedata_v3.be (100%) create mode 100644 tasmota/berry/examples/statedata/statedata_v4.be diff --git a/tasmota/berry/examples/statedata.be b/tasmota/berry/examples/statedata/statedata.be similarity index 100% rename from tasmota/berry/examples/statedata.be rename to tasmota/berry/examples/statedata/statedata.be diff --git a/tasmota/berry/examples/statedata_v2.be b/tasmota/berry/examples/statedata/statedata_v2.be similarity index 100% rename from tasmota/berry/examples/statedata_v2.be rename to tasmota/berry/examples/statedata/statedata_v2.be diff --git a/tasmota/berry/examples/statedata_v3.be b/tasmota/berry/examples/statedata/statedata_v3.be similarity index 100% rename from tasmota/berry/examples/statedata_v3.be rename to tasmota/berry/examples/statedata/statedata_v3.be diff --git a/tasmota/berry/examples/statedata/statedata_v4.be b/tasmota/berry/examples/statedata/statedata_v4.be new file mode 100644 index 000000000..82dcaa1d5 --- /dev/null +++ b/tasmota/berry/examples/statedata/statedata_v4.be @@ -0,0 +1,288 @@ +#- + Show optional devicename and/or version and/or IP adress with hostname and uptime from MQTT discovery and STATE messages in GUI + + Enable either + self.line_option = 1 : Scroll 'line_cnt' lines + or + self.line_option = 2 : Show devices updating within 'line_teleperiod' +-# + +import mqtt +import json +import string +import webserver +import persist + +class mqttdata_cls + var line_option # Line option + var line_cnt # Number of lines + var line_teleperiod # Skip any device taking longer to respond (probably offline) + var line_highlight # Highlight latest change duration + var line_highlight_color # Latest change highlight color + var line_lowuptime_color # Low uptime highlight color + var line_duration # Duration option + var mqtt_state # MQTT tele STATE subscribe format + var bool_devicename # Show device name + var bool_version # Show version + var bool_ipaddress # Show IP address + var list_buffer # Buffer storing lines + var list_config # Buffer storing retained config + + def init() +# self.line_option = 1 # Scroll line_cnt lines + self.line_option = 2 # Show devices updating within line_teleperiod + + self.line_cnt = 10 # Option 1 number of lines to show + self.line_teleperiod = 600 # Option 2 number of teleperiod seconds for devices to be shown + self.line_highlight = 10 # Highlight latest change duration in seconds + self.line_highlight_color = "yellow" # Latest change highlight HTML color like "#FFFF00" or "yellow" + self.line_lowuptime_color = "lime" # Low uptime highlight HTML color like "#00FF00" or "lime" + self.line_duration = 0 # Show duration of last state message (1) + self.mqtt_state = "" # MQTT tele STATE subscribe format + 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.list_buffer = [] # Init line buffer list + self.list_config = [] # Init retained config buffer list + + if global.mqttdata_driver + global.mqttdata_driver.stop() # Let previous instance bail out cleanly + end + tasmota.add_driver(global.mqttdata_driver := self) + + mqtt.subscribe("tasmota/discovery/+/config", /topic, idx, data, databytes -> self.handle_discovery_data(topic, idx, data, databytes)) + end + + def stop() + mqtt.unsubscribe("tasmota/discovery/+/config") + if self.mqtt_state + mqtt.unsubscribe(self.mqtt_state) + end + tasmota.remove_driver(self) + end + + 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) + + if !self.mqtt_state + # Assume first call to here defines full_topic (%prefix%/%topic%/) and Prefix3 +# var fulltopic = config['ft'] # "%prefix%/%topic%/" + var tele_topic = config['tp'][2] # tele = Prefix3 used by STATE message + self.mqtt_state = format("%s/#", tele_topic) + mqtt.subscribe(self.mqtt_state, /topic, idx, data, databytes -> self.handle_state_data(topic, idx, data, databytes)) + end + + end + end + + def handle_state_data(full_topic, idx, data, databytes) + var subtopic = string.split(full_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 # Assume topic is in retained discovery list + + var state = json.load(data) + 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 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_buffer[list_index], topic_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 + end + + def sort(l, cmp) # Sort list + 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 + return l + end + + def dhm(last_time) # Duration + var since = tasmota.rtc('local') - last_time + var unit = "d" + if since > (24 * 3600) + since /= (24 * 3600) + 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 persist_save() + persist.std_devicename = self.bool_devicename + persist.std_version = self.bool_version + persist.save() +# tasmota.log("STD: Persist saved", 3) + end + + 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() + 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 + + if 2 == self.line_option + var less = /a,b -> a < b + self.sort(self.list_buffer, less) # Sort list by topic and/or hostname + end + + 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 + end + var msg = "" # Terminate two column table and open new table + 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 + + if self.line_duration + msg += format("", self.dhm(last_seen)) # Clock + end + msg += "" + list_index += 1 + end + msg += "
%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 + +mqttdata = mqttdata_cls()