Skip to content

Commit 88931c8

Browse files
committed
Update to v25.10.05.0
1 parent 1316d8e commit 88931c8

File tree

2 files changed

+108
-71
lines changed

2 files changed

+108
-71
lines changed

raw/Devices_Online/devices_online.be

Lines changed: 107 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ class devices_online
2727
static var line_highlight_color = "yellow" # Latest change highlight HTML color like "#FFFF00" or "yellow"
2828
static var line_lowuptime_color = "lime" # Low uptime highlight HTML color like "#00FF00" or "lime"
2929

30-
var mqtt_tele # MQTT tele STATE subscribe format
30+
var mqtt_state # MQTT tele STATE subscribe format
31+
var mqtt_topic_idx # Index of %topic% within full topic
32+
var mqtt_step # MQTT message state
3133
var bool_devicename # Show device name
3234
var bool_version # Show version
3335
var bool_ipaddress # Show IP address
@@ -60,13 +62,38 @@ class devices_online
6062
self.list_buffer = [] # Init line buffer list
6163
self.list_config = [] # Init retained config buffer list
6264

63-
# var full_topic = tasmota.cmd("FullTopic", true)['FullTopic'] # "%prefix%/%topic%/"
64-
var prefix_tele = tasmota.cmd("Prefix", true)['Prefix3'] # tele = Prefix3 used by STATE message
65-
self.mqtt_tele = format("%s/#", prefix_tele)
66-
mqtt.subscribe(self.mqtt_tele, /topic, idx, data, databytes -> self.handle_state_data(topic, idx, data, databytes))
67-
mqtt.subscribe("tasmota/discovery/+/config", /topic, idx, data, databytes -> self.handle_discovery_data(topic, idx, data, databytes))
65+
var parts = string.split(tasmota.cmd('_FullTopic', true)['FullTopic'], '/')
66+
var prefix3 = tasmota.cmd("Prefix", true)['Prefix3'] # tele = Prefix3 used by STATE message
67+
self.mqtt_topic_idx = -1
68+
for ix : 0..size(parts)-1
69+
var level = parts[ix]
70+
if level == '%prefix%'
71+
parts[ix] = prefix3
72+
elif level == '%topic%'
73+
parts[ix] = '+'
74+
self.mqtt_topic_idx = ix
75+
elif level == ''
76+
parts[ix] = 'STATE'
77+
else
78+
parts[ix] = '+'
79+
end
80+
end
81+
self.mqtt_state = parts.concat('/') # default = tele/+/STATE
82+
83+
if self.mqtt_topic_idx == -1
84+
log("DVO: ERROR No %topic% in FullTopic defined", 1)
85+
return
86+
end
6887

6988
tasmota.add_driver(self)
89+
90+
mqtt.subscribe(self.mqtt_state, /topic, idx, data, databytes -> self.handle_state_data(topic, idx, data, databytes))
91+
mqtt.subscribe("tasmota/discovery/+/config", /topic, idx, data, databytes -> self.handle_discovery_data(topic, idx, data, databytes))
92+
93+
self.mqtt_step = 0
94+
if !mqtt.connected()
95+
log("DVO: Need MQTT connected", 1)
96+
end
7097
end
7198

7299
#################################################################################
@@ -76,7 +103,7 @@ class devices_online
76103
#################################################################################
77104
def unload()
78105
mqtt.unsubscribe("tasmota/discovery/+/config")
79-
mqtt.unsubscribe(self.mqtt_tele)
106+
mqtt.unsubscribe(self.mqtt_state)
80107
tasmota.remove_driver(self)
81108
end
82109

@@ -86,6 +113,11 @@ class devices_online
86113
# Handle MQTT Tasmota Discovery Config data
87114
#################################################################################
88115
def handle_discovery_data(discovery_topic, idx, data, databytes)
116+
if self.mqtt_step == 0
117+
log("DVO: Discovery started...", 3)
118+
self.mqtt_step = 1
119+
end
120+
# log(f"DVO: Discovery topic '{discovery_topic}'", 4)
89121
var config = json.load(data)
90122
if config
91123
# 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)
@@ -94,22 +126,19 @@ class devices_online
94126
var ipaddress = config['ip']
95127
var devicename = config['dn']
96128
var version = config['sw']
97-
var line = format("%s\001%s\001%s\001%s\001%s", topic, hostname, ipaddress, devicename, version)
98-
# tasmota.log(format("STD: 111 Size %03d, Topic '%s', Line '%s'", self.list_config.size(), topic, line), 3)
129+
var line = [topic, hostname, ipaddress, devicename, version]
99130
if self.list_config.size()
100131
var list_index = 0
101132
var list_size = size(self.list_config)
102-
var topic_delim = format("%s\001", topic) # Add find delimiter
103133
while list_index < list_size # Use while loop as counter is decremented
104-
if 0 == string.find(self.list_config[list_index], topic_delim)
134+
if self.list_config[list_index][0] == topic
105135
self.list_config.remove(list_index) # Remove current config
106136
list_size -= 1 # Continue for duplicates
107137
end
108138
list_index += 1
109139
end
110140
end
111141
self.list_config.push(line) # Add (re-discovered) config as last entry
112-
# tasmota.log(format("STD: 222 Size %03d, Topic '%s', Line '%s'", self.list_config.size(), topic, line), 3)
113142
end
114143
return true # return true to stop propagation as a Tasmota cmd
115144
end
@@ -120,43 +149,55 @@ class devices_online
120149
# Handle MQTT STATE data
121150
#################################################################################
122151
def handle_state_data(tele_topic, idx, data, databytes)
152+
if self.mqtt_step == 1
153+
log("DVO: Discovery complete", 3)
154+
self.mqtt_step = 2
155+
end
156+
# log(f"DVO: STATE topic '{tele_topic}'", 4)
123157
var subtopic = string.split(tele_topic, "/")
124-
if subtopic[-1] == "STATE" # tele/atomlite2/STATE
125-
var topic = subtopic[1] # Assume default Fulltopic (%prefix%/%topic%/) = tele/atomlite2/STATE = atomlite2
126-
158+
if subtopic[-1] == "STATE" # we are only serving topic ending in STATE
159+
var topic = subtopic[self.mqtt_topic_idx]
127160
var topic_index = -1
128161
for i: self.list_config.keys()
129-
if 0 == string.find(self.list_config[i], topic)
162+
if self.list_config[i][0] == topic
130163
topic_index = i
131164
break
132165
end
133166
end
134-
# 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)
167+
# log(format("DVO: Topic '%s', Index %d, Size %d, Line '%s'", topic, topic_index, self.list_config.size(), self.list_config[topic_index]), 3)
135168
if topic_index == -1 return true end # return true to stop propagation as a Tasmota cmd
136169

137170
var state = json.load(data) # Assume topic is in retained discovery list
138171
if state # Valid JSON state message
139-
var config_splits = string.split(self.list_config[topic_index], "\001")
140-
var hostname = config_splits[1]
141-
var ipaddress = config_splits[2]
142-
var devicename = config_splits[3]
143-
var version = config_splits[4]
172+
var hostname = self.list_config[topic_index][1]
173+
var ipaddress = self.list_config[topic_index][2]
174+
var devicename = self.list_config[topic_index][3]
175+
var version = self.list_config[topic_index][4]
176+
var version_splits = string.split(version, ".")
177+
var version_int = 0
178+
var multiplier = 0x1000000
179+
for split : version_splits
180+
version_int += int(split) * multiplier
181+
if multiplier
182+
multiplier /= 0x100
183+
end
184+
end
185+
var version_num = format("%011i", version_int) # 00235143427 - Convert to string to enable multicolumn sort
144186

145187
# 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"}
146188
var uptime = state['Uptime'] # 0T00:15:09
189+
var uptime_sec = format("%011i", state['UptimeSec']) # 00000000909 - Convert to string to enable multicolumn sort
147190
if state.find('Hostname')
148191
hostname = state['Hostname'] # atomlite2
149192
ipaddress = state['IPAddress'] # 192.168.2.208
150193
end
151194
var last_seen = tasmota.rtc('local')
152-
var line = format("%s\001%s\001%s\001%d\001%s\001%s", hostname, ipaddress, uptime, last_seen, devicename, version)
153-
195+
var line = [hostname, ipaddress, uptime, uptime_sec, last_seen, devicename, version, version_num]
154196
if self.list_buffer.size()
155197
var list_index = 0
156198
var list_size = size(self.list_buffer)
157-
var hostname_delim = format("%s\001", hostname) # Add find delimiter
158199
while list_index < list_size # Use while loop as counter is decremented
159-
if 0 == string.find(self.list_buffer[list_index], hostname_delim)
200+
if self.list_buffer[list_index][0] == hostname || self.list_buffer[list_index][1] == ipaddress
160201
self.list_buffer.remove(list_index) # Remove current state
161202
list_size -= 1 # Continue for duplicates
162203
end
@@ -175,34 +216,35 @@ class devices_online
175216
#
176217
# Shell sort list of online devices based on user selected column and direction
177218
#################################################################################
178-
def sort_col(l, col, dir) # Sort list based on col and Hostname (is first entry in line)
179-
# For 50 records takes 6ms (primary key) or 25ms(ESP32S3&240MHz) / 50ms(ESP32@160MHz) (primary and secondary key)
219+
def sort_col(l, col, dir)
180220
var cmp = /a,b -> a < b # Sort up
181221
if dir
182222
cmp = /a,b -> a > b # Sort down
183223
end
184-
if col # col is new primary key (not Hostname)
185-
for i:l.keys()
186-
var splits = string.split(l[i], "\001")
187-
l[i] = splits[col] + "\002" + l[i] # Add primary key to secondary key as "col" + Hostname
188-
end
189-
end
190-
for i:1..size(l)-1
191-
var k = l[i]
192-
var j = i
193-
while (j > 0) && !cmp(l[j-1], k)
194-
l[j] = l[j-1]
195-
j -= 1
224+
225+
if col == 0 # Sort hostname as primary key
226+
for i:1..size(l)-1 # Sort string
227+
var k = l[i]
228+
var ks = k[col]
229+
var j = i
230+
while (j > 0) && !cmp(l[j-1][col], ks)
231+
l[j] = l[j-1]
232+
j -= 1
233+
end
234+
l[j] = k
196235
end
197-
l[j] = k
198-
end
199-
if col
200-
for i:l.keys()
201-
var splits = string.split(l[i], "\002") # Remove primary key
202-
l[i] = splits[1]
236+
else # Sort any other string using primary and secondary key
237+
for i:1..size(l)-1
238+
var k = l[i]
239+
var ks = k[col] + k[0] # Primary search key and Secondary unique search key (hostname)
240+
var j = i
241+
while (j > 0) && !cmp(l[j-1][col] + l[j-1][0], ks)
242+
l[j] = l[j-1]
243+
j -= 1
244+
end
245+
l[j] = k
203246
end
204247
end
205-
return l
206248
end
207249

208250
#################################################################################
@@ -217,7 +259,7 @@ class devices_online
217259
persist.std_column = self.sort_column
218260
persist.std_direction = self.sort_direction
219261
persist.save()
220-
# tasmota.log("STD: Persist saved", 3)
262+
# log("DVO: Persist saved", 3)
221263
end
222264

223265
#################################################################################
@@ -254,9 +296,8 @@ class devices_online
254296
var list_index = 0
255297
var list_size = size(self.list_buffer)
256298
while list_index < list_size
257-
var splits = string.split(self.list_buffer[list_index], "\001")
258-
var last_seen = int(splits[3])
259-
if time_window > last_seen # Remove offline devices
299+
var last_seen = self.list_buffer[list_index][4]
300+
if time_window > int(last_seen) # Remove offline devices
260301
self.list_buffer.remove(list_index)
261302
list_size -= 1
262303
end
@@ -284,32 +325,34 @@ class devices_online
284325
end
285326
msg += "<th align='right'>Uptime&nbsp</th>"
286327
else
328+
# var start = tasmota.millis()
287329
self.sort_col(self.list_buffer, self.sort_column, self.sort_direction) # Sort list by column
288-
330+
# var stop = tasmota.millis()
331+
# log(format("DVO: Sort time %d ms", stop - start), 3)
289332
var icon_direction = self.sort_direction ? "&#x25BC" : "&#x25B2"
290333
if self.bool_devicename
291-
msg += format("<th><a href='#p' onclick='la(\"&sd_sort=4\");'>Device Name</a>%s&nbsp</th>", self.sort_column == 4 ? icon_direction : "")
334+
msg += format("<th><a href='#p' onclick='la(\"&sd_sort=5\");'>Device Name</a>%s&nbsp</th>", self.sort_column == 5 ? icon_direction : "")
292335
end
293336
if self.bool_version
294-
msg += format("<th><a href='#p' onclick='la(\"&sd_sort=5\");'>Version</a>%s&nbsp</th>", self.sort_column == 5 ? icon_direction : "")
337+
msg += format("<th><a href='#p' onclick='la(\"&sd_sort=7\");'>Version</a>%s&nbsp</th>", self.sort_column == 7 ? icon_direction : "")
295338
end
296339
msg += format("<th><a href='#p' onclick='la(\"&sd_sort=0\");'>Hostname</a>%s&nbsp</th>", self.sort_column == 0 ? icon_direction : "")
297340
if self.bool_ipaddress
298341
msg += format("<th><a href='#p' onclick='la(\"&sd_sort=1\");'>IP Address</a>%s&nbsp</th>", self.sort_column == 1 ? icon_direction : "")
299342
end
300-
msg += format("<th align='right'><a href='#p' onclick='la(\"&sd_sort=2\");'>Uptime</a>%s&nbsp</th>", self.sort_column == 2 ? icon_direction : "")
343+
msg += format("<th align='right'><a href='#p' onclick='la(\"&sd_sort=3\");'>Uptime</a>%s&nbsp</th>", self.sort_column == 3 ? icon_direction : "")
301344
end
302345

303346
msg += "</tr>"
304347

305348
while list_index < list_size
306-
var splits = string.split(self.list_buffer[list_index], "\001")
307-
var hostname = splits[0]
308-
var ipaddress = splits[1]
309-
var uptime = splits[2]
310-
var last_seen = int(splits[3])
311-
var devicename = splits[4]
312-
var version = splits[5]
349+
var hostname = self.list_buffer[list_index][0]
350+
var ipaddress = self.list_buffer[list_index][1]
351+
var uptime = self.list_buffer[list_index][2]
352+
var uptime_sec = self.list_buffer[list_index][3]
353+
var last_seen = self.list_buffer[list_index][4]
354+
var devicename = self.list_buffer[list_index][5]
355+
var version = self.list_buffer[list_index][6]
313356

314357
msg += "<tr>"
315358
if self.bool_devicename
@@ -323,15 +366,9 @@ class devices_online
323366
msg += format("<td><a target=_blank href='http://%s'>%s&nbsp</a></td>", ipaddress, ipaddress)
324367
end
325368

326-
var uptime_str = string.replace(uptime, "T", ":") # 11T21:50:34 -> 11:21:50:34
327-
var uptime_splits = string.split(uptime_str, ":")
328-
var uptime_sec = (int(uptime_splits[0]) * 86400) + # 11 * 86400
329-
(int(uptime_splits[1]) * 3600) + # 21 * 3600
330-
(int(uptime_splits[2]) * 60) + # 50 * 60
331-
int(uptime_splits[3]) # 34
332-
if last_seen >= (now - self.line_highlight) # Highlight changes within latest seconds
369+
if int(last_seen) >= (now - self.line_highlight) # Highlight changes within latest seconds
333370
msg += format("<td align='right' style='color:%s'>%s</td>", self.line_highlight_color, uptime)
334-
elif uptime_sec < self.line_teleperiod # Highlight changes just after restart
371+
elif int(uptime_sec) < self.line_teleperiod # Highlight changes just after restart
335372
msg += format("<td align='right' style='color:%s'>%s</td>", self.line_lowuptime_color, uptime)
336373
else
337374
msg += format("<td align='right'>%s</td>", uptime)

raw/Devices_Online/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Devices Online",
3-
"version": "0x190A0200",
3+
"version": "0x190A0500",
44
"description": "Display devices online",
55
"author": "Theo Arends",
66
"min_tasmota": "0x0E060001",

0 commit comments

Comments
 (0)