mirror of https://github.com/arendst/Tasmota.git
Berry GPIO Viewer fixes (#20423)
This commit is contained in:
parent
5c521d300a
commit
f0d0cccee6
|
@ -19,10 +19,6 @@
|
|||
|
||||
var gpio_viewer = module('gpio_viewer')
|
||||
|
||||
gpio_viewer.Webserver_async_cnx = Webserver_async_cnx
|
||||
gpio_viewer.Webserver_dispatcher = Webserver_dispatcher
|
||||
gpio_viewer.Webserver_async = Webserver_async
|
||||
|
||||
class GPIO_viewer
|
||||
var web
|
||||
var sampling_interval
|
||||
|
@ -30,6 +26,7 @@ class GPIO_viewer
|
|||
var last_pin_states # state converted to 0..255
|
||||
var new_pin_states # get a snapshot of newest values
|
||||
var pin_types # array of types
|
||||
var payload1, payload2 # temporary object bytes() to avoid reallocation
|
||||
|
||||
static var TYPE_DIGITAL = 0
|
||||
static var TYPE_PWM = 1
|
||||
|
@ -67,8 +64,10 @@ class GPIO_viewer
|
|||
"</body></html>"
|
||||
|
||||
def init(port)
|
||||
self.web = Webserver_async(5555)
|
||||
self.web = webserver_async(5555)
|
||||
self.sampling_interval = self.SAMPLING
|
||||
self.payload1 = bytes(100) # reserve 100 bytes by default
|
||||
self.payload2 = bytes(100) # reserve 100 bytes by default
|
||||
|
||||
# pins
|
||||
import gpio
|
||||
|
@ -81,6 +80,8 @@ class GPIO_viewer
|
|||
self.pin_types = []
|
||||
self.pin_types.resize(gpio.MAX_GPIO) # full of nil
|
||||
|
||||
self.web.set_chunked(true)
|
||||
self.web.set_cors(true)
|
||||
self.web.on("/release", self, self.send_release_page)
|
||||
self.web.on("/events", self, self.send_events_page)
|
||||
self.web.on("/", self, self.send_index_page)
|
||||
|
@ -118,7 +119,7 @@ class GPIO_viewer
|
|||
end
|
||||
|
||||
def send_events_page(cnx, uri, verb)
|
||||
cnx.set_mode_chunked(false) # no chunking since we use EventSource
|
||||
cnx.set_chunked(false) # no chunking since we use EventSource
|
||||
cnx.send(200, "text/event-stream")
|
||||
|
||||
self.send_events_tick(cnx)
|
||||
|
@ -127,7 +128,9 @@ class GPIO_viewer
|
|||
def send_events_tick(cnx)
|
||||
import gpio
|
||||
var max_gpio = gpio.MAX_GPIO
|
||||
var msg = "{"
|
||||
var payload1 = self.payload1
|
||||
payload1.clear()
|
||||
payload1 .. '{'
|
||||
var dirty = false
|
||||
var pin = 0
|
||||
self.read_states()
|
||||
|
@ -136,24 +139,44 @@ class GPIO_viewer
|
|||
var prev = self.last_pin_states[pin]
|
||||
var val = self.new_pin_states[pin]
|
||||
if (prev != val) || (val != nil) # TODO for now send everything every time
|
||||
if dirty msg += "," end
|
||||
msg += f'"{pin}":{{"s":{val},"v":{prev},"t":{self.pin_types[pin]}}}'
|
||||
if dirty
|
||||
# msg += ","
|
||||
payload1 .. ","
|
||||
end
|
||||
payload1 .. '"'
|
||||
payload1 .. str(pin)
|
||||
payload1 .. '":{"s":'
|
||||
payload1 .. str(val)
|
||||
payload1 .. ',"v":'
|
||||
payload1 .. str(self.pin_actual[pin])
|
||||
payload1 .. ',"t":'
|
||||
payload1 .. str(self.pin_types[pin])
|
||||
payload1 .. '}'
|
||||
# msg += f'"{pin}":{{"s":{val},"v":{prev},"t":{self.pin_types[pin]}}}}'
|
||||
dirty = true
|
||||
|
||||
self.last_pin_states[pin] = val
|
||||
end
|
||||
pin += 1
|
||||
end
|
||||
msg += "}"
|
||||
payload1 .. '}'
|
||||
# msg += "}"
|
||||
|
||||
if dirty
|
||||
# prepare payload
|
||||
var payload = f"id:{tasmota.millis()}\r\n"
|
||||
"event:gpio-state\r\n"
|
||||
"data:{msg}\r\n\r\n"
|
||||
var payload2 = self.payload2
|
||||
payload2.clear()
|
||||
payload2 .. 'id:'
|
||||
payload2 .. str(tasmota.millis())
|
||||
payload2 .. "\r\nevent:gpio-state\r\ndata:"
|
||||
payload2 .. payload1
|
||||
payload2 .. "\r\n\r\n"
|
||||
# var payload = f"id:{tasmota.millis()}\r\n"
|
||||
# "event:gpio-state\r\n"
|
||||
# "data:{msg}\r\n\r\n"
|
||||
|
||||
# tasmota.log(f"GPV: sending '{msg}'", 3)
|
||||
cnx.write(payload)
|
||||
cnx.write(payload2)
|
||||
end
|
||||
|
||||
# send free heap
|
||||
|
|
Binary file not shown.
|
@ -25,12 +25,13 @@
|
|||
# - support for limited headers
|
||||
# - HTTP 1.0 only
|
||||
|
||||
#@ solidify:Webserver_async
|
||||
#@ solidify:webserver_async
|
||||
#@ solidify:Webserver_async_cnx
|
||||
|
||||
class Webserver_async_cnx
|
||||
var server # link to server object
|
||||
var cnx # holds the tcpclientasync instance
|
||||
var close_after_send # if true, close after sending
|
||||
var fastloop_cb # cb for fastloop
|
||||
var buf_in # incoming buffer
|
||||
var buf_in_offset
|
||||
|
@ -44,7 +45,10 @@ class Webserver_async_cnx
|
|||
# response
|
||||
var resp_headers
|
||||
var resp_version
|
||||
var mode_chunked
|
||||
var chunked # if true enable chunked encoding (default true)
|
||||
var cors # if true send CORS headers (default true)
|
||||
# bytes objects to be reused
|
||||
var payload1
|
||||
# conversion
|
||||
static var CODE_TO_STRING = {
|
||||
100: "Continue",
|
||||
|
@ -68,17 +72,25 @@ class Webserver_async_cnx
|
|||
self.buf_in_offset = 0
|
||||
self.buf_out = bytes()
|
||||
self.phase = 0
|
||||
# util
|
||||
self.payload1 = bytes()
|
||||
self.close_after_send = false
|
||||
# response
|
||||
self.resp_headers = ''
|
||||
self.resp_version = 1 # HTTP 1.1 # TODO
|
||||
self.mode_chunked = true
|
||||
self.chunked = true
|
||||
self.cors = true
|
||||
# register cb
|
||||
self.fastloop_cb = def () self.loop() end
|
||||
tasmota.add_fast_loop(self.fastloop_cb)
|
||||
end
|
||||
|
||||
def set_mode_chunked(mode_chunked)
|
||||
self.mode_chunked = bool(mode_chunked)
|
||||
def set_chunked(chunked)
|
||||
self.chunked = bool(chunked)
|
||||
end
|
||||
|
||||
def set_cors(cors)
|
||||
self.cors = bool(cors)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
|
@ -86,6 +98,28 @@ class Webserver_async_cnx
|
|||
def connected()
|
||||
return self.cnx ? self.cnx.connected() : false
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# test if out buffer is empty, meaning all was sent
|
||||
def buf_out_empty()
|
||||
return size(self.buf_out) == 0
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# write bytes or string
|
||||
#
|
||||
# v must be bytes()
|
||||
def _write(v)
|
||||
if (size(v) == 0) return end
|
||||
|
||||
var buf_out = self.buf_out
|
||||
var buf_out_sz = size(buf_out)
|
||||
buf_out.resize(buf_out_sz + size(v))
|
||||
buf_out.setbytes(buf_out_sz, v)
|
||||
|
||||
self._send() # try sending this now
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# closing web server
|
||||
def close()
|
||||
|
@ -104,9 +138,12 @@ class Webserver_async_cnx
|
|||
return
|
||||
end
|
||||
|
||||
# any incoming data?
|
||||
var cnx = self.cnx
|
||||
self._send() # try sending outgoing
|
||||
|
||||
var cnx = self.cnx
|
||||
if (cnx == nil) return end # it's possible that it was closed after _send()
|
||||
|
||||
# any incoming data?
|
||||
if cnx.available() > 0
|
||||
var buf_in_new = cnx.read()
|
||||
if (!self.buf_in)
|
||||
|
@ -122,6 +159,39 @@ class Webserver_async_cnx
|
|||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# try sending what we can immediately
|
||||
def _send()
|
||||
# any data waiting to go out?
|
||||
var cnx = self.cnx
|
||||
if (cnx == nil) return end
|
||||
var buf_out = self.buf_out
|
||||
if size(buf_out) > 0
|
||||
if cnx.listening()
|
||||
var sent = cnx.write(buf_out)
|
||||
if sent > 0
|
||||
# we did sent something
|
||||
if sent >= size(buf_out)
|
||||
# all sent
|
||||
self.buf_out.clear()
|
||||
else
|
||||
# remove the first bytes already sent
|
||||
self.buf_out.setbytes(0, buf_out, sent)
|
||||
self.byf_out.resize(size(buf_out) - sent)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
# empty buffer, do the cleaning
|
||||
self.buf_out.clear()
|
||||
self.buf_in_offset = 0
|
||||
|
||||
if self.close_after_send
|
||||
self.close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# parse incoming
|
||||
#
|
||||
|
@ -209,13 +279,6 @@ class Webserver_async_cnx
|
|||
if (header_key == "Host")
|
||||
self.header_host = header_value
|
||||
end
|
||||
# import string
|
||||
# header_key = string.tolower(header_key)
|
||||
# header_value = string.tolower(header_value)
|
||||
# print("header=", header_key, header_value)
|
||||
# if header_key == 'transfer-encoding' && string.tolower(header_value) == 'chunked'
|
||||
# self.is_chunked = true
|
||||
# end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
|
@ -223,12 +286,6 @@ class Webserver_async_cnx
|
|||
#
|
||||
# All headers are received
|
||||
def event_http_headers_end()
|
||||
# print("event_http_headers_end")
|
||||
# truncate to save space
|
||||
# if self.buf_in_offset > 0
|
||||
# self.buf_in = self.buf_in[self.buf_in_offset .. ]
|
||||
# self.buf_in_offset = 0
|
||||
# end
|
||||
end
|
||||
|
||||
#############################################################
|
||||
|
@ -243,7 +300,6 @@ class Webserver_async_cnx
|
|||
#############################################################
|
||||
# Responses
|
||||
#############################################################
|
||||
#############################################################
|
||||
# parse incoming payload (if any)
|
||||
def send_header(name, value, first)
|
||||
if first
|
||||
|
@ -260,13 +316,15 @@ class Webserver_async_cnx
|
|||
|
||||
# force chunked TODO
|
||||
self.send_header("Accept-Ranges", "none")
|
||||
if self.mode_chunked
|
||||
if self.chunked
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
end
|
||||
# cors
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("Access-Control-Allow-Methods", "*")
|
||||
self.send_header("Access-Control-Allow-Headers", "*")
|
||||
if self.cors
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("Access-Control-Allow-Methods", "*")
|
||||
self.send_header("Access-Control-Allow-Headers", "*")
|
||||
end
|
||||
# others
|
||||
self.send_header("Connection", "close")
|
||||
|
||||
|
@ -275,7 +333,7 @@ class Webserver_async_cnx
|
|||
self.resp_headers = nil
|
||||
|
||||
# send
|
||||
self._write(response)
|
||||
self.write_raw(response)
|
||||
|
||||
if (content) self.write(content) end
|
||||
end
|
||||
|
@ -286,26 +344,43 @@ class Webserver_async_cnx
|
|||
|
||||
#############################################################
|
||||
# async write
|
||||
def write(s)
|
||||
def write(v)
|
||||
if type(v) == 'string' # if string, convert to bytes
|
||||
v = bytes().fromstring(v)
|
||||
end
|
||||
|
||||
# use chunk encoding
|
||||
if self.mode_chunked
|
||||
var chunk = f"{size(s):X}\r\n{s}\r\n"
|
||||
tasmota.log(f"WEB: sending chunk '{bytes().fromstring(chunk).tohex()}'")
|
||||
self._write(chunk)
|
||||
if self.chunked
|
||||
var payload1 = self.payload1
|
||||
payload1.clear()
|
||||
payload1 .. f"{size(v):X}\r\n"
|
||||
payload1 .. v
|
||||
payload1 .. "\r\n"
|
||||
|
||||
# var chunk = f"{size(v):X}\r\n{v}\r\n"
|
||||
# tasmota.log(f"WEB: sending chunk '{payload1.tohex()}'")
|
||||
self._write(payload1)
|
||||
else
|
||||
self._write(s)
|
||||
self._write(v)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#############################################################
|
||||
# async write
|
||||
def _write(s)
|
||||
self.cnx.write(s) # TODO move to async later
|
||||
def write_raw(v)
|
||||
if (size(v) == 0) return end
|
||||
|
||||
if type(v) == 'string' # if string, convert to bytes
|
||||
v = bytes().fromstring(v)
|
||||
end
|
||||
|
||||
self._write(v)
|
||||
end
|
||||
|
||||
|
||||
def content_stop()
|
||||
self.write('')
|
||||
self.close()
|
||||
self.close_after_send = true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -337,7 +412,7 @@ class Webserver_dispatcher
|
|||
end
|
||||
end
|
||||
|
||||
class Webserver_async
|
||||
class webserver_async
|
||||
var local_port # listening port, 80 is already used by Tasmota
|
||||
var server # instance of `tcpserver`
|
||||
var fastloop_cb # closure used by fastloop
|
||||
|
@ -347,6 +422,9 @@ class Webserver_async
|
|||
# var auth # web authentication string (Basic Auth) or `nil`, in format `user:password` as bade64
|
||||
# var cmd # GET url command
|
||||
var dispatchers
|
||||
# copied in each connection
|
||||
var chunked # if true enable chunked encoding (default true)
|
||||
var cors # if true send CORS headers (default true)
|
||||
|
||||
static var TIMEOUT = 1000 # default timeout: 1000ms
|
||||
static var HTTP_REQ = "^(\\w+) (\\S+) HTTP\\/(\\d\\.\\d)\r\n"
|
||||
|
@ -379,6 +457,14 @@ class Webserver_async
|
|||
end
|
||||
end
|
||||
|
||||
def set_chunked(chunked)
|
||||
self.chunked = bool(chunked)
|
||||
end
|
||||
|
||||
def set_cors(cors)
|
||||
self.cors = bool(cors)
|
||||
end
|
||||
|
||||
#############################################################
|
||||
# closing web server
|
||||
def close()
|
||||
|
@ -420,9 +506,10 @@ class Webserver_async
|
|||
# check if any incoming connection
|
||||
while self.server.hasclient()
|
||||
# retrieve new client
|
||||
var cnx = Webserver_async_cnx(self, self.server.accept()) # TODO move to self.server.acceptasync
|
||||
var cnx = Webserver_async_cnx(self, self.server.acceptasync())
|
||||
cnx.set_chunked(self.chunked)
|
||||
cnx.set_cors(self.cors)
|
||||
self.connections.push(cnx)
|
||||
tasmota.log(f"WEB: received connection from XXX")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -451,9 +538,11 @@ class Webserver_async
|
|||
|
||||
end
|
||||
|
||||
#return webserver_async
|
||||
|
||||
#- Test
|
||||
|
||||
var web = Webserver_async(888)
|
||||
var web = webserver_async(888)
|
||||
|
||||
def send_more(cnx, i)
|
||||
cnx.write(f"<p>Hello world {i}</p>")
|
||||
|
|
Loading…
Reference in New Issue