From d40c24d6d3efadefae6baa3419a38e54845e02ec Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 20 Nov 2022 22:04:30 +0100 Subject: [PATCH] Berry ArtNet implementation --- .../berry_tasmota/src/embedded/leds.be | 2 +- .../src/solidify/solidified_leds.h | 4 +- tasmota/berry/ArtNet.tapp | Bin 0 -> 17968 bytes tasmota/berry/artnet/artnet_dyn.be | 28 ++ tasmota/berry/artnet/artnet_ui.be | 288 ++++++++++++++++++ tasmota/berry/artnet/autoexec.be | 15 + 6 files changed, 334 insertions(+), 3 deletions(-) create mode 100644 tasmota/berry/ArtNet.tapp create mode 100644 tasmota/berry/artnet/artnet_dyn.be create mode 100644 tasmota/berry/artnet/artnet_ui.be create mode 100644 tasmota/berry/artnet/autoexec.be diff --git a/lib/libesp32/berry_tasmota/src/embedded/leds.be b/lib/libesp32/berry_tasmota/src/embedded/leds.be index 7a1461d90..ecde9b706 100644 --- a/lib/libesp32/berry_tasmota/src/embedded/leds.be +++ b/lib/libesp32/berry_tasmota/src/embedded/leds.be @@ -321,7 +321,7 @@ class Leds : Leds_ntv def set_bytes(row, buf, offset, len) var h_bytes = self.h * self.pix_size 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) end diff --git a/lib/libesp32/berry_tasmota/src/solidify/solidified_leds.h b/lib/libesp32/berry_tasmota/src/solidify/solidified_leds.h index c6caa2eab..d7ca9b0bd 100644 --- a/lib/libesp32/berry_tasmota/src/solidify/solidified_leds.h +++ b/lib/libesp32/berry_tasmota/src/solidify/solidified_leds.h @@ -1333,8 +1333,8 @@ be_local_closure(Leds_matrix_set_bytes, /* name */ 0x781A0000, // 0004 JMPF R6 #0006 0x5C100A00, // 0005 MOVE R4 R5 0x88180102, // 0006 GETMBR R6 R0 K2 - 0x081C0205, // 0007 MUL R7 R1 R5 - 0x00180C07, // 0008 ADD R6 R6 R7 + 0x00180C01, // 0007 ADD R6 R6 R1 + 0x08180C05, // 0008 MUL R6 R6 R5 0x881C0103, // 0009 GETMBR R7 R0 K3 0x8C1C0F04, // 000A GETMET R7 R7 K4 0x5C240C00, // 000B MOVE R9 R6 diff --git a/tasmota/berry/ArtNet.tapp b/tasmota/berry/ArtNet.tapp new file mode 100644 index 0000000000000000000000000000000000000000..d03a2bba3a5672df7e9074724ea642ac41f078e6 GIT binary patch literal 17968 zcmeHP-)|#Fa+c4&ASWje0Rr3w2!b|=HL0~Yq_omn)`~QA>y5Lx*eiIo@CA;+9g-t* zyyVPyX1J1kcLDMi1OWmZlD9l2zy--eo`byP54gu5$bS+bFZsUeAM-<`cK0m%$+EQ^ zc2`$dS65Y6RoCd`C$GP;xxt_R{N>`wAGCkv{q^r}Z1DF78-KXrr+F0Q-Y9r-`h$(v z|Go9)`2XHa$IFX1H(q<=e_z|!cmr@7O?8~+osWZDP15jrkZx{{=YE#46^)xpJ@-?! z@bfghP%D0#DxU>v9!w038IZ(jz5*IeO(lKNaxn^0brz>;9%gwEh0#>Lh0!=(@Jr&4 zp9Ohl5H2UlFbh%;SV!AbVU*>5G!9gJrk(=usgfnwkr9`b1w#N;iE65mp9N}Z0F!y!FwjWqcRp-3-P8Z0wzBn2e%7lGc<)UI5` zqc4JSE}NbNDI{URO(b@3I3D|RHI9=@ZfYT3%Fn^8(Gt9qfoIqWh=e~L2bmQWe~u<_ zC%8gdQ=Nsue4@y6Bd^=oHw!)wBcN@kfym2b%TxE`DEA>=s;9y;6~%c0y{#a+T$+(& zkSSZLuojhs7r~q>nFMDL&oFOm4cc~6qor|Mlwn(i$DLVu%eC63W5$C=K zxf;5jcDFOoSc;}CJvD_RSJ^xW5|zhlU#~&|Nu1A=ABB*}Fpes?r}KE^&pkb;ulCVN z!N3RQKFedfu5clHgdIxbEP>Vw8t@lFmrbl;pJ5T;vJ6v;AfLsPZ3R0pSD_xjJ#{F& zX^^)?jthous|JTR^pcFm6y08lq>2PO3Ns5N~q>}SQT)qPu13|z*_1x z$@WmPWDsj{K8Vd0z;c_zSZbuDt^@8lz)6_qmw-6)=UL!dov_!q=cR!^(Ja*S@Z5hG*Z)u69-4%FLk%Uq&FM)+m7dfTeAw?%y{t)K_+GSJ>CAiX3a zB%|VF98UtQOHV7f54P1V&rJS6mS^!2h8`N(yV&pbwEbfjhbL*A$K!ZjMX*Ea1O@y8 z9MRaB(2t;W4%JST3cKAkG@yTXOIt8b2o70tc?^>nS+Ju49NS}yLkAw(XJd)Wy+=i- zl^ssQMO&4h$M7XS_~@sHg#|h`KAzdc!-jgtGB1y3!A09x0gH-k%7QOzOWWO6J9g*w zl)}y#MXaxD4$bNMRK>mRDh! zwvd|5`d@)J1_%osYRAw{4G%&5U~m+;EfdR|0Ap*kJWrQe$u;7-co-wBoz2oZs)D1t zJ#oi!BSu<)`@M@>y}h40_37UVDI@$yW|#c1MDy8_YM=YJvc`60)-n_H3|OpD3$pP` ze4SkuE>p35%aqS{4m_{3^SNhbxZq2%_~OY3zcHAlV_~)3(;kR$-?$Kte5+}bVD4WI z7vUJkS}=~IN!H%&Y0v%K4|BEK!}+GZSRz~qBOGPM2y^AHOpILzKZOt|f$Y4Ysw_v+ zZhVT0L+QbqotqAg(4aG+1J7-8V+b!cgJ)qtjp&$0nUFe-6`w($Czlc6q?Ik+Gznv> zxkllgh0&zlFczPEjcp}N0vDP}fNeBLzdR2X$s7(>fiI2Evvv5`2Yma+^fycHxD0C} zmOQjpOQ?h4UyF@>Yp|hfxj-av6(J4OI`Y_8S+22WkOmt!Q{fVIJ_``cvCVk?#sKQ+ zdRnlBet7$8-E`=~Y;xLbDfA6e;RPlwLIsEj{4jWQS!w5}!0T|cud)=O(CNI!(iEAX z)0wWr!#?0?03U-~#Ck6NU*J^VRzV0hq^bpg7K5zTVmZ(8NVi%Zp-0*u6n+@*f2Fjs z4|6eO(ENDv!AUv)P+Gdbgx9-9XxRrew68)jzo$!Z6Am-asik(g(*O}U)Eqhh;a#Cv z%V@~Zb4ZBQnpIaVoZASy#WAa?B=wIx79MbWi=noa(yUiVU@d2T^``->RH|;P@wO-* zV#TOUE%ZKpynC>NGhR@##W)2VZKM*mlj7=)VI=Zks9W19#+YH0Z!N4aPl8C~wRFBY z5o`-Tg=}q#w0AaHTwnou;Vx_zOq4(~O(_U6LC8=W7z_o1mF%bUKh)5M`COQuu8Krx z0{!`A60`vzE^EuCnOf3~V|Q^;4k(nWA^{~5KEt7bY9mq%nBCBao;^RNV}$4K2kD78 zMH*{57O=uTizpfC(u~>MY<4!A>cNE%V;P8Qe6;^K$6=+8Pal1xKGF%h_Ws4b-MVwA zRlF=^Ft#?0S=XEcI`YG@(M)tjbawaG$xRRPBC>*|uM!gs#Q(5m1>f~-?Zwt+r+MO% znP2^D{^Ym+;eWo&zqhf$-`{n~%puMio6!8#-+ndz7yQ-}n#~*j0b?+$$tgDR?U8JpAG_z_v#@)A z<|l~ZI|rLLOnI|ulDVKCB1N_jnLxv{OH5gpN!$nN#qRy=7XZ=nCy{)R>!r0LO zS|sswZi*cSL$#w&EX{{JrnDjft7pD4E8NQx07sVtxd@B&HQYN zHfuc|E<@}-PVwbDXt!Kj3(n!qOjg1|J(R<7_~ap1qdpBre7fnM%5@0mg7nptr{*1t zc;fU}>5&-M0iF-CAfoW#8)b zAA}iP|4U{8nZGa_+)&8(=W(R)KOJQH0^xL-NC)Rd4|qunH%AC&jpApu5~J$u&;7%X!dmO*GwyYJ_`>XZKnr zdD(9vMcEo0X+j3~4c&oNO_x;7j=BcssLOc=N8Mx~Nz89w1U^KrMLUAx9f|~q6nrAa z7<&W1H%)!Hn1P`WClzoeqKy$|$jkk*0iE2KAh<(DQV@KbvPJR0W(?0Wy0}9+x43*0 zAu!s@Ci%`&5=Q_G*=L_i1LKb5u;+PqcI3O{1;k;7=R9P_zv@0Gl>^L6mf5WB+PI-S znCz!HZ190*v)hO%cx6bMzl+6JMfTst;@`#Mzey}kjmrGzfeWR1lvJ!hIm2boZ*u}r z<6#syL^52W#x)p8v(#hq8VsXyi$@}Knu8jQNh!%(d|ovd<7zV%9(9~wL&JdC!thxG zp#sBX=FG}Wwnk>-^QzfQmPKIrEHJA8>BiVO`CKK8+!woR54KgV$g8)35IDX16gEBr z<6_*}(mkN#@U-g=@~4EpnIB-HOmPyEe}D^IJy)EMAu_wcAD#Ofk<4Nz&$EwI5alFM^6j>6Z}j+ov2fNTdf_c zOaZOnZE_~0$G1|88sWsh_gI=n#I^!1!?) zE(a0DBPk!LQ5qs~av3j?4ho)!OtSXPU-{ za1WaY2M70lAz?lP?4VA8=+P8E?5!vO6=$|>Z74^JLG(mc+w zF>Bx)-)Rqw>&tArH4AGz;u5s;iV@GsMSls4gs}Ezea-qCAocd`k5M>fc^6D>-zMQk z0!wL}wJ2c1SSW^Itfi-y4c4}V&iOPsERkgxsMe`%eRt!8VV%xd^;@^X*5DT6G2J>q z)_JNAcnu7~t{0v$N$c&1sP}J!==y)vIvpHY3)<>wSe1@u8ciPN>%msjmwo zAQzOCPmrxF%L*{Pg3vaO!YD!Bn91;dOW)K%-$F#fJ1xbOU%y4ar&VSl_{I(Q*m3=2 zjmiSa#8+P5eSJ0e%`TCy+R7!e;~QV%BRGLU{*A1#XE%|K=E@ba<5gBDs_@FYc!EfLzP_+C zFGgP;E_`rPOsXiglLyYSQXbgxD)K<3<}Fw@#?_%KB3vUNmSM0T^DOaUW$v`xAsDBV z&Uxx5$h5(webzZg1Dw9=y_tr%WjTE_`mUCFwo(8m$k8YruY1patNDDMUHRyO;3A?b=oFgThOW**tt6L)GBwU=wyR=57i0HgKI zdXT=9t8%SZVpCeE95({(_C(z89lGD*m|$97Q7wuS@5Cn7CbDJD|>j z>cNg}r{d#-0O zF<;uTcscLlMcaOYoWM)$xoE*!y6QVvxG;6}H}AqQsj<@9vEx;(-5Mn&-|6|+^qgsu z4(ds+GZA&xAYd8|j?F+8qKr_dt>fE@zjo#P!;mWnAlg=*fM&P!-Nbd&!OhoKG};~1 zsTMy;=9evfZ{BYifYCIKT7zSDiH-ea89{7h@QciN^={;Pa=;#3pCQL+gxySXiK!L2)|A&!(s+&w zIk+syH{g|m(WN?h^!SuT2;W+2Yn;!o&24>+OxB9Ln0{rzq$*{%4N&n4fV9d#c;84q z>5!1w6S|Mo1%1aU|3)6wGJxn-+-`Ko(fj6Lg()c4 zWu<l%$V#+W=GVU%S%3$B zl*LGOwY(DJtFvV!MOCP+=UcR6)WHHo=V`{v6eZL2y?9HAvgj*!jd29q zqm3M{T_LYsWnXWt-^gxr3Fad4Pfkxyh95pSt$Xt?iB7!WEBNS)6CnkTzm6>LIAsYx zDhFZq-SZ|3xRtJ=7w$Vz(SL=39;e+vtedsy`i2x!@RW7vhmJk&rApM9x_QK3H$&xB zV2!?gbv%i0;`I$)hw}1rfeXZmyo0U%-xg1<;361%BR^*e{8bd*RWT9$GgsGcfUMn= zu5a2R^aS0ZBnMH;Nw0h}0mEFY2Rp3$u_S#Gpr#5{<5wJH;;r&0=fi4w_ zRp1l+0SGC0o5#4J$NS3wd&|5aK=aAJD$s6VoE)4+ldwcw60^n~{58P%0zQ;_gr$`M zmW2jr85T^{g&v&T)_hdn#v*rwfU=0Btln+VLNxE|>uMyy@bVq(0F0Z3)DkjW18iUS zf+c}pqY3lyrR<1MS8%mvs1`_3os*xu_PsxPechim`H}p&6aMp$f60FhZvK&z@9RHu zQmd*iqr3WIK^fhz@%K;+HQ>4wYXc=E<{Fe&3%<)J|LTVhr78agMwWkHgYl{umodI^ b9~jF*y^h51{}$Qv2L5{hUHGpL`0f7zz1HT{ literal 0 HcmV?d00001 diff --git a/tasmota/berry/artnet/artnet_dyn.be b/tasmota/berry/artnet/artnet_dyn.be new file mode 100644 index 000000000..bfac24463 --- /dev/null +++ b/tasmota/berry/artnet/artnet_dyn.be @@ -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 diff --git a/tasmota/berry/artnet/artnet_ui.be b/tasmota/berry/artnet/artnet_ui.be new file mode 100644 index 000000000..05869a6c4 --- /dev/null +++ b/tasmota/berry/artnet/artnet_ui.be @@ -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("

") + 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("

Warning: actions below can brick your device.

") + # webserver.content_send("

 (This feature requires an internet connection)

") + + webserver.content_send("
") + webserver.content_send(string.format(" ArtNet configuration")) + + webserver.content_send("

") + + # WS2812 bus configuration + webserver.content_send(string.format("

WS2812 configuration:

")) + webserver.content_send(string.format( + "" + "") + + webserver.content_send(string.format("") + webserver.content_send(string.format("") + webserver.content_send(string.format("") + + webserver.content_send(string.format("") + + webserver.content_send("") + webserver.content_send(string.format("") + # description + webserver.content_send("") + + webserver.content_send("
GPIO")) + + var ws2812_list = self.get_ws2812_gpios() + var ws2812_gpio + if size(ws2812_list) == 0 + webserver.content_send("**Not configured**") + else + webserver.content_send("") + end + webserver.content_send("
Rows")) + webserver.content_send(string.format("", conf.rows)) + webserver.content_send("
Columns")) + webserver.content_send(string.format("", conf.cols)) + webserver.content_send("
Offset")) + webserver.content_send(string.format("", conf.offs)) + webserver.content_send("
Alternate rows")) + webserver.content_send(string.format("

", conf.alt ? " checked" : "")) + webserver.content_send("
 
DMX universe")) + webserver.content_send(string.format("", conf.univ)) + webserver.content_send("
") + webserver.content_send("This this the universe number for
the first row, and gets incremented
for each row.") + webserver.content_send("

") + + # IP configuration + webserver.content_send(string.format("

IP listener:

")) + webserver.content_send("") + # "") + + webserver.content_send("") + webserver.content_send("
IP mode")) + # webserver.content_send("") + # webserver.content_send("
Port") + # webserver.content_send(string.format("
Port")) + webserver.content_send(string.format("", conf.port)) + webserver.content_send("

") + + # auto-run + webserver.content_send(string.format("

Auto-run at boot:

", conf.auto ? " checked" : "")) + + # button + webserver.content_send("") + webserver.content_send("

") + + webserver.content_send("

") + 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("

Exception:
'%s'
%s

", 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) + +-# diff --git a/tasmota/berry/artnet/autoexec.be b/tasmota/berry/artnet/autoexec.be new file mode 100644 index 000000000..7f5e9d45c --- /dev/null +++ b/tasmota/berry/artnet/autoexec.be @@ -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