diff --git a/CHANGELOG.md b/CHANGELOG.md index cd5d59493..4ac716257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - Support for two phase power calibration using commands ``PowerSet2``, ``VoltageSet2`` and ``CurrentSet2`` - Support for NTAG2xx tags read and write on PN532 NFC reader (#16939) - Berry ``bytes().reverse()`` method (#16977) +- Support for DMX ArtNet Led matrix animations ### Breaking Changed diff --git a/tasmota/berry/artnet/artnet.be b/tasmota/berry/artnet/artnet.be new file mode 100644 index 000000000..001987736 --- /dev/null +++ b/tasmota/berry/artnet/artnet.be @@ -0,0 +1,89 @@ +# Art-Net driver + +class ArtNet + var matrix # the led matrix + var port # port number for listening for incoming packets + var udp_server # instance of `udp` class + var universe_start # base universe number + var universe_end # last universe number allowed (excluded) + static var artnet_sig = bytes().fromstring("Art-Net\x00") # 8 bytes # signature of packet + + var packet # try reusing the same packer bytes() object for performance + + # local copy of led matrix attributes for faster access + var alternate # field from matrix, alternate lines (reversed). Contains 0 if not alternate, or the number of bytes per pixel + + def init(matrix, universe_start, port, ip_addr) + self.matrix = matrix + self.alternate = matrix.alternate ? matrix.pixel_size() : 0 + # self.v = self.matrix.v + if universe_start == nil universe_start = 0 end + self.universe_start = universe_start + self.universe_end = universe_start + matrix.h + + if port == nil port = 6454 end + # self.artnet_sig = bytes().fromstring("Art-Net\x00") # 8 bytes + self.port = int(port) + if ip_addr == nil ip_addr = "" end + + self.udp_server = udp() + self.udp_server.begin(ip_addr, self.port) + + # register as fast_loop + tasmota.add_fast_loop(/-> self.fast_loop()) + # set sleep to 5 for a smooth animation + tasmota.global.sleep = 5 + end + + def fast_loop() + var universe_start = self.universe_start + var universe_end = self.universe_end + var dirty = false + var packet = self.udp_server.read(self.packet) + while (packet != nil) + if size(packet) >= 18 && packet[0..7] == self.artnet_sig # check that we have a packet containing the 8 bytes header + var opcode = packet.get(8, 2) # should be 0x5000 + var protocol = packet.get(10, -2) # big endian, should be 14 + var universe = packet.get(14, 2) + + if opcode == 0x5000 && protocol == 14 && universe >= universe_start && universe < universe_end + # tasmota.log("DMX: received Art-Net packet :" + packet.tohex()) + # var seq = packet.get(12, 1) + # var phy = packet.get(13, 1) + var data_len = packet.get(16, -2) + # data starts at offset 18 + if size(packet) >= 18 + data_len # check size + if self.alternate > 0 && (universe - self.universe_start) % 2 + packet.reverse(18, self.alternate, -1) + end + self.matrix.set_bytes(universe, packet, 18, data_len) + dirty = true + end + # import string + # tasmota.log(string.format("DMX: opcode=0x%04X protocol=%i seq=%i phy=%i universe=%i data_len=%i data=%s", + # opcode, protocol, seq, phy, universe, data_len, packet[18..-1].tohex())) + end + end + packet = self.udp_server.read(packet) + if packet == nil + tasmota.delay_microseconds(20) # wait 20 us just in case + packet = self.udp_server.read(packet) + end + end + self.packet = packet # save bytes() object for next iteration and avoid allocation of new object + + if dirty + self.matrix.dirty() + self.matrix.show() + end + end +end + +return ArtNet + +#- +# Example for M5Stack ATOM Matrix (5x5 matrix without alternate) +var strip = Leds(25, gpio.pin(gpio.WS2812, 0)) +var m = strip.create_matrix(5, 5, 0) +var dmx = ArtNet(m) +-# \ No newline at end of file diff --git a/tasmota/tasmota_xdrv_driver/xdrv_04_light.ino b/tasmota/tasmota_xdrv_driver/xdrv_04_light.ino index 27acc5ae7..538da6b09 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_04_light.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_04_light.ino @@ -1763,6 +1763,7 @@ void LightReapplyColor(void) { void LightAnimate(void) { bool power_off = false; + static int32_t sleep_previous = -1; // previous value of sleep before changing it to PWM_MAX_SLEEP, -1 means unchanged // make sure we update CT range in case SetOption82 was changed Light.strip_timer_counter++; @@ -1770,13 +1771,17 @@ void LightAnimate(void) // set sleep parameter: either settings, // or set a maximum of PWM_MAX_SLEEP if light is on or Fade is running if (Light.power || Light.fade_running) { - if (Settings->sleep > PWM_MAX_SLEEP) { + if (TasmotaGlobal.sleep > PWM_MAX_SLEEP) { + sleep_previous = TasmotaGlobal.sleep; // save previous value of sleep TasmotaGlobal.sleep = PWM_MAX_SLEEP; // set a maximum value (in milliseconds) to sleep to ensure that animations are smooth } else { - TasmotaGlobal.sleep = Settings->sleep; // or keep the current sleep if it's low enough + sleep_previous = -1; // if low enough, don't change it } } else { - TasmotaGlobal.sleep = Settings->sleep; + if (sleep_previous > 0) { + TasmotaGlobal.sleep = sleep_previous; + sleep_previous = -1; // rearm + } } if (!Light.power) { // All channels powered off