ssilverman / TeensyDMX

A full-featured DMX library for Teensy 3, Teensy LC, and Teensy 4. "Programmable DMX and arbitrary USB serial device emulation."
BSD 3-Clause Clear License
99 stars 6 forks source link

Use of connection(); #21

Closed MrMdR closed 1 year ago

MrMdR commented 1 year ago

Hi,

I try to detect whether my DMXreceiver receives a dmx signal. I wanted to use the connected() function. However, I don't seem to get it working. I think the docs are not written for an Intermediate level programmer so I'm a bit stuck.

I would like to keep looping a function when no DMX signal is detected. The example code in the docs gives an error. I try to put something together myself but also had no luck so far. What would be the correct implementation of connection detection for my use case?

Docs example:

dmxRx.onConnectChange([](Receiver *r) {
  digitalWriteFast(LED_BUILTIN, r.connected() ? HIGH : LOW);
});

My try:

  while (dmxRx.connected() == false)
  {
  Serial.println("No DMX signal");
  markColor(100, 255, 0, 0); //NUM_LEDS 
  }

Full code

#include <TeensyDMX.h>
namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Receiver dmxRx{Serial1};
uint8_t packetBuf[5] {0};
uint8_t dsrgb[5] {0}; //Dimmer, RGB, strobe

// BasicTest example to demonstrate how to use FastLED with OctoWS2811
#include <OctoWS2811.h>
#define USE_OCTOWS2811
#include <FastLED.h>
#define NUM_LEDS  190 //3040

CRGB leds[NUM_LEDS];

void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 4000) {
    // Wait for initialization to complete or a time limit
  }

  while (dmxRx.connected() == false)
  {
  Serial.println("No DMX signal");
  markColor(100, 255, 0, 0); //all leds red
  }

  LEDS.addLeds<OCTOWS2811, RGB>(leds, NUM_LEDS / 8);
  Serial.print("Let there be light!");
  markColor(100, 255, 0, 0);
  delay(2000);
  Serial.println("Done");
  Serial.println("Start DMX read");
  dmxRx.begin();
}

void loop() {
  dmxRx.readPacket(dsrgb, 1, 5); // first 5 channels

  //Dimmer + color
  if (dsrgb[4] < 10) {
    markColor(dsrgb[0], dsrgb[1], dsrgb[2], dsrgb[3]);
  }

  //STROBE + color
  if (dsrgb[4] > 10) {
    Strobe(map(dsrgb[4], 10, 255, 10, 150)); //argument: strobe time in MS
  }

}

void Strobe(uint8_t strobeIntervalMs) {
  if ((millis() / strobeIntervalMs) % 2) {
    markColor(0, 0, 0, 0);
  } else {
    markColor(dsrgb[0], dsrgb[1], dsrgb[2], dsrgb[3]);
  }
}

void markColor(uint8_t dmxDimmer, uint8_t dmxRed, uint8_t dmxGreen, uint8_t dmxBlue) {
  uint8_t dimmerRed = map(dmxRed, 0, 255, 0, dmxDimmer);
  uint8_t dimmerGreen = map(dmxGreen, 0, 255, 0, dmxDimmer);
  uint8_t dimmerBlue = map(dmxBlue, 0, 255, 0, dmxDimmer);

  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i].setRGB(dimmerRed, dimmerGreen, dimmerBlue);
  }

  FastLED.show();
}
ssilverman commented 1 year ago

I have a number of comments on this program, but before I get to them, can you first define what you mean by “not working”? For example, what do you expect to happen, and what is actually happening instead?

MrMdR commented 1 year ago

Hi! Thanks for your reply. Docs example: won't compile, My try: executes the while regardless of dmx connection. The serial monitor prints 'No DMX signal' even tho I am sending a dmx signal to my Teensy.

I had multiple tries but this one was the one that compiled and seemed to be the most logical to me. What I was expecting was a boolean response. "Yes there is a dmx connection!" "No,I can't seem to find a dmx connection".

Thanks for helping me a letting me learn! :)

ssilverman commented 1 year ago

The first thing I noticed is that you never called dmxRx.begin(). That needs to happen to enable the serial port and all the behind-the-scenes logic.

The Error counts and disconnection section and subsections in the README summarize what it means to "connect" and disconnect. The best way, in my opinion, to detect "connection" is to watch for a receive timeout, as in the Flasher example.

Basically, loop to read a packet, and when a packet hasn't been seen within some timeout, that's kind of like a "disconnect". When packets start again, that can be considered a "connect". Here's some example code (similar to what the Flasher example does):

// Demonstrates DMX connection detection by using timeouts.

#include <TeensyDMX.h>

namespace teensydmx = ::qindesign::teensydmx;

// Timeout after which it's considered that DMX is no longer sending.
constexpr unsigned long kDMXTimeout = 1000;  // 1s

// Create the DMX receiver on Serial1.
teensydmx::Receiver dmxRx{Serial1};

// DMX packet buffer.
uint8_t buf[513];

// Keeps track of when the last frame was received.
elapsedMillis lastFrameTimer;

// Keeps track of the connection state.
bool connected = false;

// Main program setup.
void setup() {
  // Initialize the serial port
  Serial.begin(115200);
  while (!Serial && millis() < 4000) {
    // Wait for initialization to complete or a time limit
  }
  Serial.println("Starting...");

  // Start the DMX receiver and consider ourselves as "disconnected"
  // to start by setting the timer to the timeout time
  dmxRx.begin();
  lastFrameTimer = kDMXTimeout;
  connected = false;
}

// Called when the connection state changes.
void setConnected(bool flag) {
  if (flag) {
    Serial.println("Connected");
  } else {
    Serial.println("Disconnected");
  }
}

// Main program loop.
void loop() {
  int read = dmxRx.readPacket(buf, 0, sizeof(buf));
  if (read > 0) {
    // We've seen something, anything
    if (!connected) {
      connected = true;
      setConnected(true);
    }

    // Reset the timer
    lastFrameTimer = 0;

    // Do stuff with the data
  } else {
    // Nothing was read, check for a timeout
    if (lastFrameTimer >= kDMXTimeout) {
      if (connected) {
        connected = false;
        setConnected(false);
      }
    }

    // Possibly do other things here
  }
}

(Note that I haven't tested the code with any hardware; let me know how it works for you.)

Another comment I have is about your use of FastLED. FastLED.show() should be called at approximately a constant rate. Here's some demo code:

constexpr uint32_t kFPS = 30;

elapsedMillis ledTimer;

// ...

void loop() {
  // ...

  if (ledTimer >= 1000/kFPS) {
    FastLED.show();
    ledTimer = 0;
  }

  // ...
}

Hope this halps and gets you started.

MrMdR commented 1 year ago

Hi, Thank you for getting back to me. Your code worked. :) Only, there was a delay of 1 - 4 seconds from when I changed the dmx values in my DMX program till it was reflected in the Adressble light. I narrowed it down it had to do with the DMX processing.
The line int read = dmxRx.readPacket(buf, 0, sizeof(buf)); made it very slow. When I changed it to 'something smaller' (I suppose) like int read = dmxRx.readPacket(drgbs, 1, 20);, it was back to normal response times. (Where drgbs points to uint8_t drgbs[20] {0};. At the moment I only have 20 channels but I am planning to increase this to 240ch. I guess the flagging for (dis)connection can not be done with a large led setup.

Thanks for your addition on using FastLED.show(). Could you point me to some documentation or explain why this is a best practice?

Below my code if anyone whats to achieve the same thing. I learn from the internet so I give something back aswell. Some context: with this you can control a multitude of 8 led bars through 5 dmx channels individually. Dimmer, Red, Green, Blue, Strobe.

//***************DMX*************//
#include <TeensyDMX.h>
namespace teensydmx = ::qindesign::teensydmx;

// Timeout after which it's considered that DMX is no longer sending.
constexpr unsigned long kDMXTimeout = 1000;  // 1s

// Create the DMX receiver on Serial1.
teensydmx::Receiver dmxRx{Serial1};

// DMX packet buffer. (not used)
uint8_t buf[513];

// Keeps track of when the last frame was received.
elapsedMillis lastFrameTimer;

// Keeps track of the connection state.
bool connected = false;

// Buffer things
uint8_t drgbs[20] {0}; //Dimmer, rgb, strobe

//***************LED*************//
#include <OctoWS2811.h>
#define USE_OCTOWS2811
#include <FastLED.h>
#define SECTION_NUM 16 //How many sections you have. Has to be divisible by 8.
#define SECTION_LENGTH 190 //How many individual leds there are in each section.
#define NUM_LEDS (SECTION_LENGTH*16)
CRGB leds[NUM_LEDS];

//FastLED.show() should be called at approximately a constant rate.
constexpr uint32_t kFPS = 30;
elapsedMillis ledTimer;

void setup() {
  ///*** DMX *** //
  // Initialize the serial port
  Serial.begin(115200);
  while (!Serial && millis() < 4000) {
    // Wait for initialization to complete or a time limit
  }
  Serial.println("Starting...");

  // Start the DMX receiver and consider ourselves as "disconnected"
  // to start by setting the timer to the timeout time
  Serial.println("Start DMX read");
  dmxRx.begin();
  lastFrameTimer = kDMXTimeout;
  connected = false;

  //** LED **//
  LEDS.addLeds<OCTOWS2811, RGB>(leds, NUM_LEDS / 8);
  Serial.print("Let there be light!");
  setSection(0, 10, 50, 50, 50, 0); //Section_num, dimmer_val, red, green, blue, strobe
  delay(2000);
  setSection(0, 0, 0, 0, 0, 0);
}

// Called when the connection state changes.
void setConnected(bool flag) {
  if (flag) {
    Serial.println("Connected");
  } else {
    Serial.println("Disconnected");
  }
}

void loop() {
  //int iread = dmxRx.readPacket(buf, 1, sizeof(buf));
  int iread = dmxRx.readPacket(drgbs, 20);
  //Serial.println(iread);

  if (iread > 0) {
    // We've seen something, anything
    if (!connected) {
      connected = true;
      setConnected(true);
    }

    // Reset the timer
    lastFrameTimer = 0;

    // Do stuff with the data
  } else {
    // Nothing was read, check for a timeout
    if (lastFrameTimer >= kDMXTimeout) {
      if (connected) {
        connected = false;
        setConnected(false);
      }
    }

    // Possibly do other things down here
    dmxRx.readPacket(drgbs, 1, 20); //buffer, first channels id, number of channels. (Read channels 10, 11, 12 = buff, 10, 3)
    setSection(0, drgbs[0], drgbs[1], drgbs[2], drgbs[3], drgbs[4]); //Section_num, dimmer_val, red, green, blue, strobe
    setSection(1, drgbs[5], drgbs[6], drgbs[7], drgbs[8], drgbs[9]);
    setSection(2, drgbs[10], drgbs[11], drgbs[12], drgbs[13], drgbs[14]);
    setSection(3, drgbs[15], drgbs[16], drgbs[17], drgbs[18], drgbs[19]);
 //more sections to be added here
  }
}

void setSection(uint8_t section_num, uint8_t  dmxDimmer, uint8_t  dmxRed, uint8_t  dmxGreen, uint8_t  dmxBlue, uint8_t  dmxStrobe) {

  // Take into account the Dimmer value and mapp them on the collor values.
  uint8_t dimmerRed = map(dmxRed, 0, 255, 0, dmxDimmer);
  uint8_t dimmerGreen = map(dmxGreen, 0, 255, 0, dmxDimmer);
  uint8_t dimmerBlue = map(dmxBlue, 0, 255, 0, dmxDimmer);

  Serial.printf("Section: %d | Dimmer: %d  RGB: %d %d %d  Strobe: %d \n", section_num, dmxDimmer, dimmerRed, dimmerGreen, dimmerBlue, dmxStrobe);

  //Normal, open
  if (dmxStrobe < 10) {
    for (int i = (section_num * SECTION_LENGTH); i < (((section_num + 1 ) * SECTION_LENGTH) - 1); i++) {
      leds[i].setRGB(dimmerRed, dimmerGreen, dimmerBlue);
    }
  }

  //Strobe effect
  if (dmxStrobe > 10) {
    int strobeIntervalMs = map(dmxStrobe, 10, 255, 10, 250); //min in, max in, min uit, max uit
    if ((millis() / strobeIntervalMs) % 2) {
      for (int i = (section_num * SECTION_LENGTH); i < (((section_num + 1 ) * SECTION_LENGTH) - 1); i++) {
        leds[i].setRGB(0, 0, 0);
      }
    } else {
      for (int i = (section_num * SECTION_LENGTH); i < (((section_num + 1 ) * SECTION_LENGTH) - 1); i++) {
        leds[i].setRGB(dimmerRed, dimmerGreen, dimmerBlue);
      }
    }
  }

  if (ledTimer >= 1000 / kFPS) {
    FastLED.show();
    ledTimer = 0;
  }
}
ssilverman commented 1 year ago

Making your program slower with a larger readPacket() size really surprises me. I'm using the std::copy_n() function under the covers and that shouldn't take very long. Can you describe exactly the effect you're seeing? Are you sure the sluggishness is caused by passing a larger size for that function? Note that if you start reading a packet at position zero, the first byte will be the packet's start code. Might this indexing be related? i.e.buf[0] will be the start code, but when you call readPacket(drgbs, 1, 20), drgbs[0] will be slot 1.

Note: you should comment out the unused buf if you're not using it because it will save about 513 bytes of space. Not that it matters for a small program, but it might affect a large program where saving half a kilobyte might be important.

To answer your FastLED.show() question: I see you're using WS2811-based LEDs. This means that, while powered, you don't need to continually send pixel data; once it latches, the values should stay there. This is unlike DMX, which needs to continuously send. However, FastLED has time-based dithering that gets updated every time FastLED.show() (or FastLED.delay()) gets called. Calling it regularly will make the dithering better.

ssilverman commented 1 year ago

A follow-up question: If you call FastLED.show() at some regular rate, do you still see the sluggishness with readPacket(buf, 0, 513) vs. readPacket(buf, 1, 20)?

Update: I'm skeptical that calling readPacket() with larger values is that much slower than calling it with smaller values (even though I'm open to the possibility).

MrMdR commented 1 year ago

Hi! Below is my description of what I see happening. But first, it might be handy to know more about my setup.

Setup On my (win11) laptop I use Dot2 as lighting software to control DMX fixtures. Since Dot2 outputs only Art-Net (and I have DMX fixtures), I use Art-Net-to-DMX (from Freesyler) and an internal loopback adapter to send data via USB towards an Enttec DMXUSB pro. The setup software I'm using for a long time has never failed me. From that Enttec interface the DMX travels to my Teensy via a MAX485 breakout. Teensy uses OctroWS2811 to dive 8x GS8208 ledstrips (similar to WS2815). No other DMX fixtures are connected but will be in the future.

What I observe When I uncomment int read = dmxRx.readPacket(buf, 0, sizeof(buf));' (and comment the line below) I notice the response time is getting lower. For example; when I change the dimmer value in Dot2 (the dmx software); the led strip response is a few sec later. Especially with a fading effect, you see clear steps in brightness. When I open the serial monitor I can monitor the RGB values teensy sends to the ledstip (fasted). Although these values are updated constantly the value changes a few sec later when I change them in Dot2.

I changed 'readPacket(buf, 0, 513)' to ' readPacket(buf, 1, 513)' > same result I change 'readPacket(buf, 1, 20)' to 'readPacket(buf, 0, 20)' > DMX channels are read wrong " If you call FastLED.show() at some regular rate" > The way I implemented 'FastLED.show()' is at a regular rate right? In that case: Yes I still see the low response time.

ssilverman commented 1 year ago

On readPacket()

First, I'd like to clarify how readPacket() works, given your comment about 0, 20 being read wrong: The first argument is the buffer into which to store values, the second argument is the starting slot, and the third argument is the count. It will read up to, but maybe less than, the count, which is why you need to look at the return value to see how many have actually been read. To interpret all the examples:

  1. readPacket(buf, 0, 20) will read slots 0 through up to 19 inclusive, with slot 0 being the start code.
  2. 1, 513 will always return zero because there are only a total of 513 slots (0-512), and these arguments tell the system to read slots 1-513; slot 513 doesn't exist.
  3. 0, 513 will read slot 0 up to 512.
  4. 1, 20 will read slot 1 up to 20.

It's always wise to check how much has actually been received, otherwise there might be garbage sent to the connected hardware (in this case, the LEDs). If you control the DMX contents, then this situation is unlikely, but it's still good practice.

LED updates and decoupling from DMX

You're not calling FastLED.show() regularly; the way you have it is that it's dependent on the DMX input, which might or might not be variable. You're also only updating the lights when there isn't DMX data. That might partially explain the slowness. That combined with only updating the LEDs non-regularly.

You also need to update setSection() regularly because the strobing won't work correctly otherwise. I'd completely decouple the LED updates (that includes show() and changing LED RGB values) from the DMX input. Here's what I believe should be done:

  1. When DMX is received, update some stored values, and
  2. At a regular interval, call setSection() and show().

Demo code

Here's some off-the-cuff demo code. Some notes:

  1. I changed #define NUM_LEDS (SECTION_LENGTH*16) to #define NUM_LEDS (SECTION_LENGTH*SECTION_COUNT). Was this correct?
  2. It looks like "connection" isn't being used, but I left it in.
  3. I changed SECTION_NUM to SECTION_COUNT.
  4. I'm using strongly-typed constexpr values instead of defines.
  5. I changed the LED refresh rate to 60 (I'm curious if 30 works well too).
  6. I also took some liberties with spacing, structure, comments, and variable naming. :)
  7. I also sprinkled some notes and questions in there.
  8. Note that this is colour-coded source code. This is because I put "c++" immediately after the triple-backtick for the code section.
#include <OctoWS2811.h>
#define USE_OCTOWS2811
#include <FastLED.h>
#include <TeensyDMX.h>

//***************DMX*************//
namespace teensydmx = ::qindesign::teensydmx;

// Timeout after which it's considered that DMX is no longer sending.
constexpr unsigned long kDMXTimeout = 1000;  // 1s

// Create the DMX receiver on Serial1.
teensydmx::Receiver dmxRx{Serial1};

// DMX packet buffer.
uint8_t dmxBuf[513];

// Keeps track of when the last frame was received.
elapsedMillis lastFrameTimer;

// Keeps track of the DMX connection state.
bool dmxConnected = false;

//***************LEDs*************//
constexpr int kSectionCount = 16;   // How many sections we have; must be divisible by 8
constexpr int kSectionLen   = 190;  // How many individual LEDs there are in each section
constexpr int kNumLEDs      = kSectionLen * kSectionCount;
CRGB leds[kNumLEDs];

//FastLED.show() and any LED updates should be called at approximately a constant rate.
constexpr uint32_t kFPS = 60;
elapsedMillis ledTimer;

// Current information about one section.
struct SectionInfo {
  CRGB rgb;
  uint32_t framesPerStrobe = 0;  // NOTE: Can't go faster than the chosen LED update rate
  uint32_t strobeCounter   = 0;
  bool strobeState         = false;
};
SectionInfo sections[kSectionCount];

// Main program setup.
void setup() {
  // Initialize the serial port
  Serial.begin(115200);
  while (!Serial && millis() < 4000) {
    // Wait for Serial connection or a time limit
  }
  Serial.println("Starting...");

  // Set up the LEDs
  LEDS.addLeds<OCTOWS2811, RGB>(leds, kNumLEDs/8);  // Divide by 8 because it's an OctoWS2811

  // Show the LEDs for a bit
  Serial.println("Let there be light!");
  setSectionInfo(0, 10, 50, 50, 50, 0);  // Section, dimmer, red, green, blue, strobe
  updateSectionLEDs();
  FastLED.show();
  delay(2000);
  setSectionInfo(0, 0, 0, 0, 0, 0);
  updateSectionLEDs();
  FastLED.show();

  // Start the DMX receiver and consider ourselves as "disconnected"
  // to start by setting the timer to the timeout time
  Serial.println("Starting DMX receiver");
  dmxRx.begin();
  lastFrameTimer = kDMXTimeout;
  dmxConnected   = false;
}

// Called when the DMX connection state changes.
// NOTE: This program doesn't really use connectedness other than to
//       print the current state; it could probably be removed
void setDMXConnected(bool flag) {
  if (flag) {
    Serial.println("DMX connected");
  } else {
    Serial.println("DMX disconnected");
  }
}

// Main program loop.
void loop() {
  // Doing these things separately decouples DMX rate from LED rate
  processDMX();
  updateLEDs();
}

// Processes incoming DMX and stores values.
void processDMX() {
  int read = dmxRx.readPacket(dmxBuf, 0, sizeof(dmxBuf));

  if (read > 0) {
    // We've seen something, anything
    if (!dmxConnected) {
      dmxConnected = true;
      setDMXConnected(true);
    }

    // Reset the timer
    lastFrameTimer = 0;

    // Do stuff with the data
    if (dmxBuf[0] == 0) {  // Ignore non-zero start codes
      int i = 1;
      while (i + 5 <= read) {
        // NOTE: setSection() rejects out-of-range sections
        setSectionInfo((i - 1)/5,  // Section
                       dmxBuf[i],  // Dimmer
                       dmxBuf[i + 1], dmxBuf[i + 2], dmxBuf[i + 3],  // RGB
                       dmxBuf[i + 4]);  // Strobe
        i += 5;
      }
    }
  } else {
    // Nothing was read, check for a timeout
    if (lastFrameTimer >= kDMXTimeout) {
      if (dmxConnected) {
        dmxConnected = false;
        setDMXConnected(false);
      }
    }
  }
}

// Update the LEDs. This includes section strobing.
void updateLEDs() {
  // LED and section update
  if (ledTimer >= 1000 / kFPS) {
    updateSectionLEDs();
    FastLED.show();
    ledTimer = 0;
  }
}

// Sets information for one section.
void setSectionInfo(int section,
                    uint8_t dimmer,
                    uint8_t red, uint8_t green, uint8_t blue,
                    uint8_t strobe) {
  if (section < 0 || kSectionCount <= section) {
    return;
  }

  // Serial.printf("Section: %d | Dimmer: %u  RGB: %u %u %u  Strobe: %u \n",
  //               section, dimmer, red, green, blue, strobe);

  // NOTE: It's okay to rewrite the values because they don't get used again
  red   = map(red, 0, 255, 0, dimmer);
  green = map(green, 0, 255, 0, dimmer);
  blue  = map(blue, 0, 255, 0, dimmer);

  SectionInfo &si = sections[section];  // NOTE: Use a reference and not a copy
  si.rgb.setRGB(red, green, blue);
  if (strobe < 10) {
    si.framesPerStrobe = 0;
  } else {
    si.framesPerStrobe = map(strobe, 10, 255, 1, kFPS/4);  // 1 frame to 1/4 second
    // NOTE: There's some math you could do here to avoid strange
    //       flickering when the strobe rate changes
  }
}

// Updates all the LEDs in a section from the section infos. This
// needs to be called regularly so strobing works. It is assumed that
// this is called at a frame rate of kFPS.
void updateSectionLEDs() {
  // Q: Why did the loop end used to be minus one?

  int ledIndex = 0;

  for (int i = 0; i < kSectionCount; i++) {
    SectionInfo &si = sections[i];  // Use a reference and not a copy
    CRGB rgb;

    // To strobe or not to strobe
    if (si.framesPerStrobe == 0) {
      rgb = si.rgb;
    } else {
      if (si.strobeCounter++ >= si.framesPerStrobe) {
        si.strobeCounter = 0;
        si.strobeState   = !si.strobeState;
      }
      rgb = si.strobeState ? si.rgb : CRGB::Black;
    }

    for (int j = 0; j < kSectionLen; j++) {
      leds[ledIndex++] = rgb;
    }
  }
}

There's likely to be problems with this code because it's off-the-cuff, but it's an outline of how I would approach this. Basically, the LED update and refresh is not coupled with the DMX data input. There's also another improvement that could be done to avoid flickering when the strobe rate changes.

Direct to LEDs

It's also possible to have a Teensy 4.1 receive Art-Net and convert directly to the LEDs without any intervening DMX hardware. (I do this kind of thing all the time.) See also the https://github.com/ssilverman/QNEthernet library for Ethernet support (for Teensy 4.1 only — I'm not sure which Teensy model you're using).

Hope this helps.

ssilverman commented 1 year ago

@MrMdR Planning to close this. Did my example help?

MrMdR commented 1 year ago

Hi! I will get back to you in the upcoming days. Tx

MrMdR commented 1 year ago

Hello Silverman, First: Sorry for the late reply. Secondly: you are amazing! Thank you so much for helping out. Not only by giving me a working example code but also by adding notes in the code so I can better understand what you did and learn. I love that :). Not all is yet clear to me but I will get there I think!

At the moment I tested with a few strips and it all seems to work like a charm. Even with strobing I don't see any glitching happening. I will do some more testing in the upcoming 2 months. (Then the installation will be displayed during a voluntary art festival).

Thanks for the tip on the Teensy4.1 (I use a 3.2 atm) with ARTnet. Since it's my first time building a more professional setup I tough I stick to what I know (DMX). For the future, I'm planning to expand the setup and swap to Artnet. I will for sure look at your other lib! Since you say "I do this kind of thing all the time" It makes me curious about what you do! But that is for another threat ;).

Thanks again for your time @ssilverman!

ssilverman commented 1 year ago

Thanks for the kind words. Can we consider this question closed?

Feel free to reach out to me privately (eg. email or something) if you want to chat more about what I do. :)

ssilverman commented 1 year ago

Closing for now. Please re-open if this is still an issue.