diff --git a/tasmota/berry/gpio_viewer/debug_panel.be b/tasmota/berry/gpio_viewer/debug_panel.be index 12f7c1c2f..31759a72f 100644 --- a/tasmota/berry/gpio_viewer/debug_panel.be +++ b/tasmota/berry/gpio_viewer/debug_panel.be @@ -21,8 +21,6 @@ class debug_panel var port var web var sampling_interval - # - var payload1, payload2 # temporary object bytes() to avoid reallocation static var SAMPLING = 100 static var HTML_HEAD1 = @@ -73,8 +71,6 @@ class debug_panel self.port = port self.web = webserver_async(port) self.sampling_interval = self.SAMPLING - self.payload1 = bytes(100) # reserve 100 bytes by default - self.payload2 = bytes(100) # reserve 100 bytes by default self.web.set_chunked(true) self.web.set_cors(true) @@ -110,30 +106,74 @@ class debug_panel cnx.content_stop() end + static class feeder + var cnx # connection object + + def init(cnx) + self.cnx = cnx + tasmota.add_driver(self) + end + + def close() + tasmota.remove_driver(self) + end + + def every_100ms() + self.send_feed() + end + + def send_feed() + var cnx = self.cnx + if !cnx.connected() + self.close() + return + end + + var payload1 = self.cnx.server.payload1 + var payload2 = self.cnx.server.payload2 + var server = self.cnx.server + if cnx.buf_out_empty() + # if out buffer is not empty, do not send any new information + # var payload + # send free heap + payload1.clear() + payload1 .. "id:" + server.bytes_format_int(payload2, tasmota.millis()) + payload1 .. payload2 + payload1 .. "\r\nevent:free_heap\r\ndata:" + server.bytes_format_int(payload2, tasmota.memory('heap_free'), '---') + payload1 .. payload2 + payload1 .. " KB\r\n\r\n" + # payload = f"id:{tasmota.millis()}\r\n" + # "event:free_heap\r\n" + # "data:{tasmota.memory().find('heap_free', 0)} KB\r\n\r\n" + cnx.write(payload1) + + # send wifi rssi + payload1.clear() + payload1 .. "id:" + server.bytes_format_int(payload2, tasmota.millis()) + payload1 .. payload2 + payload1 .. "\r\nevent:wifi_rssi\r\ndata:" + server.bytes_format_int(payload2, tasmota.wifi('quality'), '--') + payload1 .. payload2 + payload1 .. "%\r\n\r\n" + + # payload = f"id:{tasmota.millis()}\r\n" + # "event:wifi_rssi\r\n" + # "data:{tasmota.wifi().find('quality', '--')}%\r\n\r\n" + cnx.write(payload1) + end + end + + end + def send_info_feed(cnx, uri, verb) cnx.set_chunked(false) # no chunking since we use EventSource cnx.send(200, "text/event-stream") - self.send_info_tick(cnx) - end - - def send_info_tick(cnx) - if cnx.buf_out_empty() - # if out buffer is not empty, do not send any new information - var payload - # send free heap - payload = f"id:{tasmota.millis()}\r\n" - "event:free_heap\r\n" - "data:{tasmota.memory().find('heap_free', 0)} KB\r\n\r\n" - cnx.write(payload) - - # send wifi rssi - payload = f"id:{tasmota.millis()}\r\n" - "event:wifi_rssi\r\n" - "data:{tasmota.wifi().find('quality', '--')}%\r\n\r\n" - cnx.write(payload) - end - - tasmota.set_timer(self.sampling_interval, def () self.send_info_tick(cnx) end) + # + var feed = feeder(cnx) + feed.send_feed() # send first values immediately end # Add button 'GPIO Viewer' redirects to '/part_wiz?' diff --git a/tasmota/berry/gpio_viewer/debug_panel.tapp b/tasmota/berry/gpio_viewer/debug_panel.tapp index 31c2f9fc4..f7a886133 100644 Binary files a/tasmota/berry/gpio_viewer/debug_panel.tapp and b/tasmota/berry/gpio_viewer/debug_panel.tapp differ diff --git a/tasmota/berry/gpio_viewer/gpioviewer.be b/tasmota/berry/gpio_viewer/gpioviewer.be index 91df7624b..58bb566a9 100644 --- a/tasmota/berry/gpio_viewer/gpioviewer.be +++ b/tasmota/berry/gpio_viewer/gpioviewer.be @@ -125,6 +125,7 @@ class GPIO_viewer def send_events_page(cnx, uri, verb) cnx.set_chunked(false) # no chunking since we use EventSource + cnx.set_cors(true) cnx.send(200, "text/event-stream") self.send_events_tick(cnx) diff --git a/tasmota/berry/gpio_viewer/webserver_async.be b/tasmota/berry/gpio_viewer/webserver_async.be index 2801484ed..fadbb3196 100644 --- a/tasmota/berry/gpio_viewer/webserver_async.be +++ b/tasmota/berry/gpio_viewer/webserver_async.be @@ -28,6 +28,9 @@ #@ solidify:webserver_async #@ solidify:Webserver_async_cnx +############################################################# +# class Webserver_async_cnx +############################################################# class Webserver_async_cnx var server # link to server object var cnx # holds the tcpclientasync instance @@ -46,7 +49,7 @@ class Webserver_async_cnx var resp_headers var resp_version var chunked # if true enable chunked encoding (default true) - var cors # if true send CORS headers (default true) + var cors # if true send CORS headers (default false) # bytes objects to be reused var payload1 # conversion @@ -79,7 +82,7 @@ class Webserver_async_cnx self.resp_headers = '' self.resp_version = 1 # HTTP 1.1 # TODO self.chunked = true - self.cors = true + self.cors = false # register cb self.fastloop_cb = def () self.loop() end tasmota.add_fast_loop(self.fastloop_cb) @@ -384,6 +387,9 @@ class Webserver_async_cnx end end +############################################################# +# class Webserver_dispatcher +############################################################# class Webserver_dispatcher var uri_prefix # prefix string, must start with '/' var verb # verb to match, or nil for ANY @@ -412,6 +418,11 @@ class Webserver_dispatcher end end +############################################################# +# class webserver_async +# +# This is the main class to call +############################################################# class webserver_async var local_port # listening port, 80 is already used by Tasmota var server # instance of `tcpserver` @@ -424,7 +435,9 @@ class webserver_async var dispatchers # copied in each connection var chunked # if true enable chunked encoding (default true) - var cors # if true send CORS headers (default true) + var cors # if true send CORS headers (default false) + # + var payload1, payload2 # temporary object bytes() to avoid reallocation static var TIMEOUT = 1000 # default timeout: 1000ms static var HTTP_REQ = "^(\\w+) (\\S+) HTTP\\/(\\d\\.\\d)\r\n" @@ -438,6 +451,10 @@ class webserver_async self.connections = [] self.dispatchers = [] self.server = tcpserver(port) # throws an exception if port is not available + self.chunked = true + self.cors = false + self.payload1 = bytes(100) # reserve 100 bytes by default + self.payload2 = bytes(100) # reserve 100 bytes by default # TODO what about max_clients ? self.compile_re() # register cb @@ -457,14 +474,73 @@ class webserver_async end end + ############################################################# + # enable or disable chunked mode (enabled by default) def set_chunked(chunked) self.chunked = bool(chunked) end + ############################################################# + # enable or disable CORS mode (enabled by default) def set_cors(cors) self.cors = bool(cors) end + ############################################################# + # Helper function to encode integer as hex (uppercase) + static def bytes_format_hex(b, i, default) + b.clear() + if (i == nil) b .. default return end + # sanity check + if (i < 0) i = -i end + if (i < 0) return end # special case for MININT + if (i == 0) b.resize(1) b[0] = 0x30 return end # return bytes("30") + + b.resize(8) + var len = 0 + while i > 0 + var digit = i & 0x0F + if (digit < 10) + b[len] = 0x30 + digit + else + b[len] = 0x37 + digit # 0x37 = 0x41 ('A') - 10 + end + len += 1 + i = (i >> 4) + end + # reverse order + b.resize(len) + b.reverse() + end + + ############################################################# + # Helper function to encode integer as int + static def bytes_format_int(b, i, default) + b.clear() + if (i == nil) b .. default return end + var negative = false + # sanity check + if (i < 0) i = -i negative = true end + if (i < 0) return end # special case for MININT + if (i == 0) b.resize(1) b[0] = 0x30 return end # return bytes("30") + + b.resize(11) # max size for 32 bits ints '-2147483647' + var len = 0 + while i > 0 + var digit = i % 10 + b[len] = 0x30 + digit + len += 1 + i = (i / 10) + end + if negative + b[len] = 0x2D + len += 1 + end + # reverse order + b.resize(len) + b.reverse() + end + ############################################################# # closing web server def close()