Berry ArtNet implementation

This commit is contained in:
Stephan Hadinger 2022-11-20 22:04:30 +01:00
parent e00529c1b6
commit d40c24d6d3
6 changed files with 334 additions and 3 deletions

View File

@ -321,7 +321,7 @@ class Leds : Leds_ntv
def set_bytes(row, buf, offset, len) def set_bytes(row, buf, offset, len)
var h_bytes = self.h * self.pix_size var h_bytes = self.h * self.pix_size
if (len > h_bytes) len = h_bytes end if (len > h_bytes) len = h_bytes end
var offset_in_matrix = self.offset + row * h_bytes var offset_in_matrix = (self.offset + row) * h_bytes
self.pix_buffer.setbytes(offset_in_matrix, buf, offset, len) self.pix_buffer.setbytes(offset_in_matrix, buf, offset, len)
end end

View File

@ -1333,8 +1333,8 @@ be_local_closure(Leds_matrix_set_bytes, /* name */
0x781A0000, // 0004 JMPF R6 #0006 0x781A0000, // 0004 JMPF R6 #0006
0x5C100A00, // 0005 MOVE R4 R5 0x5C100A00, // 0005 MOVE R4 R5
0x88180102, // 0006 GETMBR R6 R0 K2 0x88180102, // 0006 GETMBR R6 R0 K2
0x081C0205, // 0007 MUL R7 R1 R5 0x00180C01, // 0007 ADD R6 R6 R1
0x00180C07, // 0008 ADD R6 R6 R7 0x08180C05, // 0008 MUL R6 R6 R5
0x881C0103, // 0009 GETMBR R7 R0 K3 0x881C0103, // 0009 GETMBR R7 R0 K3
0x8C1C0F04, // 000A GETMET R7 R7 K4 0x8C1C0F04, // 000A GETMET R7 R7 K4
0x5C240C00, // 000B MOVE R9 R6 0x5C240C00, // 000B MOVE R9 R6

BIN
tasmota/berry/ArtNet.tapp Normal file

Binary file not shown.

View File

@ -0,0 +1,28 @@
#################################################################################
# dyn class
#
# Allows to use a map with members
# see https://github.com/berry-lang/berry/wiki/Chapter-8
#################################################################################
class dyn
var _attr
def init()
self._attr = {}
end
def setmember(name, value)
self._attr[name] = value
end
def member(name)
if self._attr.contains(name)
return self._attr[name]
else
import undefined
return undefined
end
end
def tostring()
return self._attr.tostring()
end
end
return dyn

View File

@ -0,0 +1,288 @@
#######################################################################
# DMX ArtNet UI for ESP32(C3/S2/S3)
#
#######################################################################
var artnet_ui = module('artnet_ui')
#################################################################################
# ArtNet_UI
#
# WebUI
#################################################################################
class ArtNet_UI
def init()
import persist
if persist.find("artnet_autorun") == true
# autorun
end
end
# ####################################################################################################
# Init web handlers
# ####################################################################################################
# Displays a "DMX ArtNet" button on the configuration page
def web_add_config_button()
import webserver
webserver.content_send("<p><form id=artnet_ui action='artnet_ui' style='display: block;' method='get'><button>Configure ArtNet animations</button></form></p>")
end
# ####################################################################################################
# Get WS2812 gpios
#
# Returns an array of valid WS2812 gpios as defined in the template, or empty array
# ####################################################################################################
def get_ws2812_gpios()
import gpio
var ret = []
for p:0..31
if gpio.pin_used(gpio.WS2812, p)
ret.push(p)
end
end
return ret
end
static def read_persist()
import persist
var conf = dyn()
conf.gpio = persist.find("artnet_gpio", 0) # gpio number from template
conf.rows = persist.find("artnet_rows", 5) # number of rows (min: 1)
conf.cols = persist.find("artnet_cols", 5) # number of columns (min: 1)
conf.offs = persist.find("artnet_offs", 0) # offset in the led strip where the matrix starts (min: 0)
conf.alt = persist.find("artnet_alt", false) # are the rows in alternate directions
conf.univ = persist.find("artnet_univ", 0) # start universe
# conf.addr = persist.find("artnet_addr", "uni") # listening mode, either 'uni' or 'multi' for multicast
conf.port = persist.find("artnet_port", 6454) # UDP port number
conf.auto = persist.find("artnet_auto", true) # autorun at startup
return conf
end
def save_persist(conf)
import persist
persist.artnet_gpio = conf.gpio
persist.artnet_rows = conf.rows
persist.artnet_cols = conf.cols
persist.artnet_offs = conf.offs
persist.artnet_alt = conf.alt
persist.artnet_univ = conf.univ
# persist.artnet_addr = conf.addr
persist.artnet_port = conf.port
persist.artnet_auto = conf.auto
persist.save()
end
#######################################################################
# Display the complete page on `/artnet_ui`
#######################################################################
def page_artnet_ui()
import webserver
import string
if !webserver.check_privileged_access() return nil end
# read configuration
var conf = self.read_persist()
webserver.content_start("ArtNet") #- title of the web page -#
webserver.content_send_style() #- send standard Tasmota styles -#
# webserver.content_send("<p style='width:320px;'><b style='color:#f56'>Warning:</b> actions below can brick your device.</p>")
# webserver.content_send("<p><small>&nbsp;(This feature requires an internet connection)</small></p>")
webserver.content_send("<fieldset><style>.bdis{background:#888;}.bdis:hover{background:#888;}</style>")
webserver.content_send(string.format("<legend><b title='ArtNet'>&nbsp;ArtNet configuration</b></legend>"))
webserver.content_send("<p><form id=artnet_ui style='display: block;' action='/artnet_ui' method='post'>")
# WS2812 bus configuration
webserver.content_send(string.format("<p>WS2812 configuration: </p>"))
webserver.content_send(string.format(
"<table style='width:100%%'>"
"<tr><td style='width:150px'><b>GPIO</b></td><td style='width:150px'>"))
var ws2812_list = self.get_ws2812_gpios()
var ws2812_gpio
if size(ws2812_list) == 0
webserver.content_send("<b>**Not configured**</b>")
else
webserver.content_send("<select id='ws2812'>")
for gp:ws2812_list
webserver.content_send(string.format("<option value='%i'>%s</option>", gp, "WS2812 - " + str(gp+1)))
end
webserver.content_send("</select>")
end
webserver.content_send("</td><td></td></tr>")
webserver.content_send(string.format("<tr><td><b>Rows</b></td><td>"))
webserver.content_send(string.format("<input type='number' min='1' name='rows' value='%i'>", conf.rows))
webserver.content_send("</td></tr>")
webserver.content_send(string.format("<tr><td><b>Columns</b></td><td>"))
webserver.content_send(string.format("<input type='number' min='1' name='cols' value='%i'>", conf.cols))
webserver.content_send("</td></tr>")
webserver.content_send(string.format("<tr><td><b>Offset</b></td><td>"))
webserver.content_send(string.format("<input type='number' min='0' name='offs' value='%i'>", conf.offs))
webserver.content_send("</td></tr>")
webserver.content_send(string.format("<tr><td><b>Alternate rows</b></td><td>"))
webserver.content_send(string.format("<input type='checkbox' name='alt'%s></p>", conf.alt ? " checked" : ""))
webserver.content_send("</td></tr>")
webserver.content_send("<tr><td>&nbsp;</td></tr>")
webserver.content_send(string.format("<tr><td><b>DMX universe</b></td><td>"))
webserver.content_send(string.format("<input type='number' min='0' name='univ' value='%i'>", conf.univ))
webserver.content_send("</td></tr>")
# description
webserver.content_send("<tr><td colspan='3' style='word-wrap: break-word;'>")
webserver.content_send("This this the universe number for<br>the first row, and gets incremented<br>for each row.")
webserver.content_send("</td></tr>")
webserver.content_send("</table><hr>")
# IP configuration
webserver.content_send(string.format("<p>IP listener: </p>"))
webserver.content_send("<table style='width:100%%'>")
# "<tr><td style='width:120px'><b>IP mode</b></td><td style='width:180px'>"))
# webserver.content_send("<select id='ip'>")
# webserver.content_send(string.format("<option value='uni'%s>unicast</option>", conf.addr == 'uni' ? " selected" : ""))
# webserver.content_send(string.format("<option value='multi'%s>multicast</option>", conf.addr == 'multi' ? " selected" : ""))
# webserver.content_send("</select>")
# webserver.content_send("</td><td></td></tr>")
webserver.content_send("<tr><td style='width:120px'><b>Port</b></td><td style='width:180px'>")
# webserver.content_send(string.format("<tr><td><b>Port</b></td><td>"))
webserver.content_send(string.format("<input type='number' min='1' name='port' value='%i'>", conf.port))
webserver.content_send("</td></tr>")
webserver.content_send("</table><hr>")
# auto-run
webserver.content_send(string.format("<p><b>Auto-run at boot:</b> <input type='checkbox' name='auto'%s></p>", conf.auto ? " checked" : ""))
# button
webserver.content_send("<button name='artnetapply' class='button bgrn'>Apply and Run</button>")
webserver.content_send("</form></p>")
webserver.content_send("<p></p></fieldset><p></p>")
webserver.content_button(webserver.BUTTON_CONFIGURATION)
webserver.content_stop()
end
#######################################################################
# Web Controller, called by POST to `/artnet_ui`
#######################################################################
def page_artnet_ctl()
import webserver
if !webserver.check_privileged_access() return nil end
import string
import persist
import introspect
try
if webserver.has_arg("artnetapply")
# read argumments, sanity check and put in conf object
var conf = dyn()
# read gpio
var ws2812_list = self.get_ws2812_gpios()
var gp_ws2812 = int(webserver.arg("ws2812"))
if ws2812_list.find(gp_ws2812) != nil
conf.gpio = gp_ws2812
else
conf.gpio = -1
end
# read rows and cols
var rows = int(webserver.arg("rows"))
if rows < 1 || rows > 999 rows = 1 end
conf.rows = rows
var cols = int(webserver.arg("cols"))
if cols < 1 || cols > 999 cols = 1 end
conf.cols = cols
# alternate
conf.alt = webserver.arg("alt") == 'on'
# offset
var offs = int(webserver.arg("offs"))
if offs < 0 || offs > 999 offs = 0 end
conf.offs = offs
# universe
var univ = int(webserver.arg("univ"))
if univ < 0 || univ > 999 univ = 0 end
conf.univ = univ
# universe
var port = int(webserver.arg("port"))
if port < 1 || port > 65535 port = 6454 end
conf.port = port
# autorun
conf.auto = webserver.arg("auto") == 'on'
self.save_persist(conf)
artnet.stop_global()
artnet.run_from_conf()
# tasmota.log("BRY: conf=" + str(conf), 2);
webserver.redirect("/cn?")
else
raise "value_error", "Unknown command"
end
except .. as e, m
print(string.format("BRY: Exception> '%s' - %s", e, m))
#- display error page -#
webserver.content_start("Parameter error") #- title of the web page -#
webserver.content_send_style() #- send standard Tasmota styles -#
webserver.content_send(string.format("<p style='width:340px;'><b>Exception:</b><br>'%s'<br>%s</p>", e, m))
webserver.content_button(webserver.BUTTON_CONFIGURATION) #- button back to management page -#
webserver.content_stop() #- end of web page -#
end
end
#- ---------------------------------------------------------------------- -#
# respond to web_add_handler() event to register web listeners
#- ---------------------------------------------------------------------- -#
#- this is called at Tasmota start-up, as soon as Wifi/Eth is up and web server running -#
def web_add_handler()
import webserver
#- we need to register a closure, not just a function, that captures the current instance -#
webserver.on("/artnet_ui", / -> self.page_artnet_ui(), webserver.HTTP_GET)
webserver.on("/artnet_ui", / -> self.page_artnet_ctl(), webserver.HTTP_POST)
end
end
artnet_ui.ArtNet_UI = ArtNet_UI
#- create and register driver in Tasmota -#
if tasmota
var artnet_ui_instance = artnet_ui.ArtNet_UI()
tasmota.add_driver(artnet_ui_instance)
## can be removed if put in 'autoexec.bat'
artnet_ui_instance.web_add_handler()
end
return artnet_ui
#- Example
import partition
# read
p = partition.Partition()
print(p)
-#

View File

@ -0,0 +1,15 @@
# rm ArtNet.tapp; zip ArtNet.tapp -j -0 artnet/*
#
# check if `dyn` class is embedded, or load it
if !global.contains("dyn")
import artnet_dyn
global.dyn = artnet_dyn
end
import artnet
import artnet_ui
import persist
if persist.find("artnet_auto")
tasmota.add_rule("Wifi#Connected", def () tasmota.remove_rule("Wifi#Connected", "artnet_run") artnet.run_from_conf() end, "artnet_run")
end