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