rstephan / ArtnetWifi

Arduino library for Art-Net (artnet) over WiFi, send and receive DMX data. Runs on ESP8266, ESP32, Pi Pico W, WiFi101 and WiFiNINA devices.
Other
363 stars 61 forks source link

Splitting Universe Channels #28

Open Toby66 opened 4 years ago

Toby66 commented 4 years ago

I've been using this for a while now and it has been super helpful. Currently, I'm using a collection of ESP32s to control both LED's and stepper motors via ArtNet, splitting the information over different universes as set out in #9 .

However, I'm scaling up the project and it seems wasteful to use two universes per ESP32 when all the information could fit within one universe.

What would be the best way to be able to split a universes' channels into multiple outputs?

I.E:

Cheers!

rstephan commented 4 years ago

It is always a good idea to reduce the amount of resources. :-) But the "waste" can be very little. The overhead between one 200 byte universe compared to two 100 byte universes is not much. But with one universe you will get only one call to the callback, that's a timing bonus. If you had shared your code snippet, it would be much easier to help you. I would guess something like this:

if (universe == 1) {
  strip1.update(data);
}
if (universe == 2) {
  strip2.update(data);
}
if (universe == 3) {
  stepper.setPos(data);
}

Doing it all in one universe is as simple as:

if (universe == 0) {
  strip1.update(&data[0]);
  strip2.update(&data[200]);
  stepper.setPos(&data[201]);
}

Note: This is more pseudo code than copy/paste-ready!

chaosdesignnz commented 4 years ago

I have a Similar Question.

I am Planning on Having Multiple ESP32 Each with a String on LEDS attached to Create Free standing light poles.

My question is how do I offest the Start address for each string of lights.

my setup is 30 RGB Lights per ESP32, Which is 90 Channels using this code

#include <WiFi.h>
#include <WiFiUdp.h>
#include <ArtnetWifi.h>
#include <FastLED.h>

//Wifi settings - be sure to replace these with the WiFi network that your computer is connected to

const char* ssid = "SSID";
const char* password = "pAsSwOrD";

// LED Strip
const int numLeds = 30 // Change if your setup has more or less LED's
const int numberOfChannels = numLeds * 3; // Total number of DMX channels you want to receive (1 led = 3 channels)
#define DATA_PIN 12 //The data pin that the WS2812 strips are connected to.
CRGB leds[numLeds];

// Artnet settings
ArtnetWifi artnet;
const int startUniverse = 0;

bool sendFrame = 1;
int previousDataLength = 0;

// connect to wifi – returns true if successful or false if not
boolean ConnectWifi(void)
{
  boolean state = true;
  int i = 0;

  WiFi.begin(ssid, password);
  Serial.println("");
  Serial.println("Connecting to WiFi");

  // Wait for connection
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    if (i > 20){
      state = false;
      break;
    }
    i++;
  }
  if (state){
    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("");
    Serial.println("Connection failed.");
  }

  return state;
}

void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data)
{
  sendFrame = 1;
  // set brightness of the whole strip 
  if (universe == 15)
  {
    FastLED.setBrightness(data[0]);
  }
  // read universe and put into the right part of the display buffer
  for (int i = 0; i < length / 3; i++)
  {
    int led = i + (universe - startUniverse) * (previousDataLength / 3);
    if (led < numLeds)
    {
      leds[led] = CRGB(data[i * 3], data[i * 3 + 1], data[i * 3 + 2]);
    }
  }
  previousDataLength = length;     
  FastLED.show();
}

void setup()
{
  Serial.begin(115200);
  ConnectWifi();
  artnet.begin();
  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, numLeds);

  // onDmxFrame will execute every time a packet is received by the ESP32
  artnet.setArtDmxCallback(onDmxFrame);
}

void loop()
{
  // we call the read function inside the loop
  artnet.read();
}

How would i do this?

rstephan commented 4 years ago

@chaosdesignnz There are many ways to do the splitting. Just a selection:

For a "reasonable" scaling option, the DHCP-based solution is the best one. There is only one firmware for all nodes, but you have to control the DHCP-server inside the network. All ESP-Nodes need a IP-address anyway. So use addresses from, lets say 192.x.x.100 to .110 for 11-nodes. The starting address doesn't matter, but you have to know it, and the addresses must be in a row.

uint8_t my_id = 0;
static const uint8_t offset = 100;

void setup() {
  uint32 local_ip = WiFi.localIP();
  my_id = (local_ip & 0xFF) - offset;
  // maybe error-checking ??
}
...
void onDmxFrame(...) {
  uint16_t index = my_id * 90;
  // handle overflow !!
}

Note: Pseudo code!!

Don't like any of the above options? Let me know.

andreas-git commented 3 years ago

Hallo rstephan,

thanks for your great Project. Im still new in the micro controller programming, so sorry if im dont understand everything. My Project ist to create a device with Stepper and Led im using your Project and AccelStepper including WebServer on a ESP8266. The Plan for my project is to use Web based settings option with the all parameters. There for im looking for a option to set DMX start Channel for the device.

Can you give me a hint ?

Best Regard from Hannover

Toby66 commented 3 years ago

Hey all, rstephan thanks for your help on this unfortunately the project got postponed and I didn't make much process on the code, hopefully the project starts up again this year though!

However, on the off chance this helps anyone browsing I've included the code (at the end) which sends different universe to different pins and includes AccelStepper (just random positions).

Andreas you may be able to use this as a base and control the steper position with a DMX address as per the reccomendation on the second post;

if (universe == 3) {
  stepper.setPos(data);

or setting chanels from one universe

if (universe == 0) { //SET UNIVERSE NUMBER
  strip1.update(&data[0]); //SET [START CHANNEL]
  strip2.update(&data[100]);
  stepper.setPos(&data[201]);
}

Compiled Code:

//********************************STEPPER DEFINE********************************
#include <Stepper.h>

const int stepsPerRevolution = 2048;  // change this to fit the number of steps per revolution

// Define a stepper and the pins it will use
Stepper myStepper(stepsPerRevolution, 15, 2, 0, 4);

//********************************ARTNET DEFINE********************************

#include <ArtnetWifi.h>
#include <Arduino.h>
#include <FastLED.h>

// Wifi settings
const char* ssid = "SSID";
const char* password = "pAsSwOrD";

// FASTLED settings

      //LED Strip 01
const int numLeds1 = 128; // CHANGE FOR YOUR SETUP
const int numberOfChannels1 = numLeds1 * 3; // Total number of channels you want to receive (1 led = 3 channels)
const byte dataPin1 = 12;
CRGB leds1[numLeds1];

      //LED Strip 02
const int numLeds2 = 128; // CHANGE FOR YOUR SETUP
const int numberOfChannels2 = numLeds2 * 3; // Total number of channels you want to receive (1 led = 3 channels)
const byte dataPin2 = 14;
CRGB leds2[numLeds2];

//Total Number of Channels
const int numberOfChannelsTot = numberOfChannels1 + numberOfChannels2;

// Art-Net settings
ArtnetWifi artnet;
const int startUniverse = 1; // CHANGE FOR YOUR SETUP most software this is 1, some software send out artnet first universe as 0.

// Check if we got all universes
const int maxUniverses = numberOfChannelsTot / 512 + ((numberOfChannelsTot % 512) ? 1 : 0);
bool universesReceived[maxUniverses];
bool sendFrame = 1;
int previousDataLength = 0;

// connect to wifi – returns true if successful or false if not
boolean ConnectWifi(void)
{
  boolean state = true;
  int i = 0;

  WiFi.begin(ssid, password);
  Serial.println("Connecting to WiFi");

  // Wait for connection
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(random(250, 1000));
    Serial.print("...");
    if (i > 0) {
      state = false;
      break;
    }
    i++;
  }
  if (state) {
    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("Connection failed.");
    ConnectWifi();
  }
  return state;
}
//RGB Light Test
      //LED 01
void initTest1()
{
  for (int i = 0 ; i < numLeds1 ; i++) {
    leds1[i] = CRGB(127, 0, 0);
  }
  FastLED.show();
  delay(500);
  for (int i = 0 ; i < numLeds1 ; i++) {
    leds1[i] = CRGB(0, 127, 0);
  }
  FastLED.show();
  delay(500);
  for (int i = 0 ; i < numLeds1 ; i++) {
    leds1[i] = CRGB(0, 0, 127);
  }
  FastLED.show();
  delay(500);
  for (int i = 0 ; i < numLeds1 ; i++) {
    leds1[i] = CRGB(0, 0, 0);
  }
  FastLED.show();
  delay(500);
}

      //LED 02
void initTest2()
{
  for (int b = 0 ; b < numLeds2 ; b++) {
    leds2[b] = CRGB(127, 0, 0);
  }
  FastLED.show();
  delay(500);
  for (int b = 0 ; b < numLeds2 ; b++) {
    leds2[b] = CRGB(0, 127, 0);
  }
  FastLED.show();
  delay(500);
  for (int b = 0 ; b < numLeds2 ; b++) {
    leds2[b] = CRGB(0, 0, 127);
  }
  FastLED.show();
  delay(500);
  for (int b = 0 ; b < numLeds2 ; b++) {
    leds2[b] = CRGB(0, 0, 0);
  }
  FastLED.show();
}

//DMX Set Up
void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data)
{
  sendFrame = 1;
  // set brightness of the whole strip
  if (universe == 15)
  {
    FastLED.setBrightness(data[0]);
    FastLED.show();
  }

  // Store which universe has got in
  if ((universe - startUniverse) < maxUniverses) {
    universesReceived[universe - startUniverse] = 1;
  }

  for (int i = 0 ; i < maxUniverses ; i++)
  {
    if (universesReceived[i] == 0)
    {
      //Serial.println("Broke");
      sendFrame = 0;
      break;
    }
  }

// read universe and put into the right part of the display buffer

  if (universe == 1) {
    for (int i = 0; i < length / 3; i++)
    {
      int led = i;
      if (led < numLeds1)
        leds1[led] = CRGB(data[i * 3], data[i * 3 + 1], data[i * 3 + 2]);
    }
  }

  if (universe == 2) {
    for (int i = 0; i < length / 3; i++)
    {
      int led = i;
      if (led < numLeds1)
        leds2[led] = CRGB(data[i * 3], data[i * 3 + 1], data[i * 3 + 2]);
    }
  }
  previousDataLength = length;

  if (sendFrame)
  {
    FastLED.show();
    // Reset universeReceived to 0
    memset(universesReceived, 0, maxUniverses);
  }
}

void setup()
{
    //********************************STEPPER SET UP********************************
    
  myStepper.setSpeed(15);

    //********************************ARTNET SET UP********************************

  Serial.begin(115200);
  ConnectWifi();
  artnet.begin();
  FastLED.addLeds<WS2812, dataPin1, GRB>(leds1, numLeds1);
  FastLED.addLeds<WS2812, dataPin2, GRB>(leds2, numLeds2);
  initTest1();
  initTest2();

  // this will be called for each packet received
  artnet.setArtDmxCallback(onDmxFrame);
}

void loop()
{
  // we call the read function inside the loop
  artnet.read();
  myStepper.step(random(0, 2048));

}

andreas-git commented 3 years ago

Thanks Toby66 for your Help,

im still not sure how to implement this part in my Code:

if (universe == 0) { //SET UNIVERSE NUMBER
  strip1.update(&data[0]); //SET [START CHANNEL]
  strip2.update(&data[100]);
  stepper.setPos(&data[201]);
}

Im know what the Code mean but not how to insert this. I've done it bit other way, im not Sure if its the right way. I'm still learning, but for the moment it Works. Im still looking for Optimization.

#include <ESP8266WiFi.h>
#include <ArtnetWifi.h>
#include <Arduino.h>
#include <FastLED.h>
#include <AccelStepper.h>

//Stepper 
#define STEPPER1_DIR_PIN D4
#define STEPPER1_STEP_PIN D3
//#define STEPPER1_EN_PIN D2
AccelStepper stepper(AccelStepper::DRIVER, STEPPER1_STEP_PIN, STEPPER1_DIR_PIN);
int curpos = stepper.currentPosition();
int prevpos = 0;
int chan = 1;
int led_start = chan + 1; 

// WiFi stuff
const char* ssid = "FRITZ!Box 7490";
const char* password = "123456789";
// LED settings
const int numLeds = 5; // CHANGE FOR YOUR SETUP
const int numberOfChannels = numLeds * 3; // Total number of channels you want to receive (1 led = 3 channels)
const byte dataPin = 2;
CRGB leds[numLeds];

// Art-Net settings
ArtnetWifi artnet;
WiFiUDP UdpSend;
const int startUniverse = 0; // CHANGE FOR YOUR SETUP most software this is 1, some software send out artnet first universe as 0.

// Check if we got all universes
const int maxUniverses = numberOfChannels / 512 + ((numberOfChannels % 512) ? 1 : 0);
bool universesReceived[maxUniverses];
bool sendFrame = 1;
int previousDataLength = 0;

// connect to wifi – returns true if successful or false if not
boolean ConnectWifi(void)
{
  boolean state = true;
  int i = 0;

  WiFi.begin(ssid, password);
  Serial.println("");
  Serial.println("Connecting to WiFi");

  // Wait for connection
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    if (i > 20){
      state = false;
      break;
    }
    i++;
  }
  if (state){
    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("");
    Serial.println("Connection failed.");
  }

  return state;
}

void initTest()
{
  for (int i = 0 ; i < numLeds ; i++) {
    leds[i] = CRGB(127, 0, 0);
  }
  FastLED.show();
  delay(500);
  for (int i = 0 ; i < numLeds ; i++) {
    leds[i] = CRGB(0, 127, 0);
  }
  FastLED.show();
  delay(500);
  for (int i = 0 ; i < numLeds ; i++) {
    leds[i] = CRGB(0, 0, 127);
  }
  FastLED.show();
  delay(500);
  for (int i = 0 ; i < numLeds ; i++) {
    leds[i] = CRGB(0, 0, 0);
  }
  FastLED.show();
}

void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data)
{

  sendFrame = 1;
  if ((universe - startUniverse) < maxUniverses) 
  {
  universesReceived[universe - startUniverse] = 1;
  }

  for (int i = 0 ; i < maxUniverses ; i++)
    {
      if (universesReceived[i] == 0)
      {
      //Serial.println("Broke");
      sendFrame = 0;
      break;
      }
    }

  // read universe and put into the right part of the display buffer
  for (int i = 0; i < length / 3; i++)
  {
    int led = i + (universe - startUniverse) * (previousDataLength / 3);
    if (led < numLeds)
      leds[led] = CRGB(data[i * 3 + led_start], data[i * 3 + 1 + led_start], data[i * 3 + 2 + led_start]);
  }
  previousDataLength = length;  

  // get 16Bit Data for Stepper part 
  for (int i = 0 ; i < chan ; i++)
    {
      int long newval = 0;
      newval = map(data[i], 0, 255, 0, 512);

      char hi = data[i];
      char lo = data[i + 1];
      int StepperPos = hi << 8 | lo;

     Serial.print("data: ");
     Serial.println(StepperPos, HEX);

     stepper.moveTo(StepperPos);
    }

     if (sendFrame)
  {
    FastLED.show();
    // Reset universeReceived to 0
    memset(universesReceived, 0, maxUniverses);
  }

}

void setup()
{
  Serial.begin(115200);
  ConnectWifi();
  artnet.begin();
  FastLED.addLeds<WS2812, dataPin, GRB>(leds, numLeds);
  initTest();

  // this will be called for each packet received
  artnet.setArtDmxCallback(onDmxFrame);
  //  set Current Position to 0
  stepper.setCurrentPosition(0);
 } //End Setup

void loop(){
  // we call the read function inside the loop
  artnet.read();
  //Stepper stuff
  stepper.setMaxSpeed(13000);
  stepper.setSpeed(9000);
  stepper.setAcceleration(6500);
  stepper.runSpeedToPosition();

}

As next is the Webserver with Filesystem todo.

Best Regards Andreas

rstephan commented 3 years ago

@ all Don't put WiFi credentials into the posts ;-)

@andreas-git How to set the start-address? If you are using the Web-interface, than put in a textbox and a button and set a variable inside the code. How to do this? I don't know. Look for examples included in the Web-server framework. Also look for an option to store the setting (EEPROM?) so it will "remember" the setting after a reset.

For your code-example a hint:

void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data)
{
  if (universe == 0) { //SET UNIVERSE NUMBER
    //strip1.update(&data[0]); //SET [START CHANNEL]
    //strip2.update(&data[100]);
    //stepper.setPos(&data[201]);

    // NOTE: 100 LEDs (300 channels) !!!!
    for (int led = 0; led < 100; led++) {
      int pos = (i * 3) + channelStart;
      leds[led] = CRGB(data[pos + 0], data[pos + 1], data[pos + 2]);
    }
    FastLED.show();
    // whatever to do with the stepper motor ...
  }
}

Note: As always, pseudo code !! Don't copy-paste!

Maybe my strip1.update() wasn't the best for the example. There is no update() in Adafruit Neopixel or FastLED. You have to loop over the data and put it, with the right color-conversion, into the LED buffer.

andreas-git commented 3 years ago

Thanks for reply @rstephan,

@ all Don't put WiFi credentials into the posts ;-)

Thanks for advise, thats already pseudo credentials. :-)

How to set the start-address? If you are using the Web-interface, than put in a textbox and a button and set a variable inside the code. How to do this? I don't know. Look for examples included in the Web-server framework. Also look for an option to store the setting (EEPROM?) so it will "remember" the setting after a reset.

Yes, that was my idea using variables for set the needed parameters thrue WebServer and store it on Filesystem. But im still not sure im my Copy Pasted code is ok, still looking optimization. Or is the Code ok ?

  if (universe == 0) { //SET UNIVERSE NUMBER
    //strip1.update(&data[0]); //SET [START CHANNEL]
    //strip2.update(&data[100]);
    //stepper.setPos(&data[201]);

Its looks what ive done already in my Code, but not so nice like you advise.

   leds[led] = CRGB(data[i * 3 + led_start], data[i * 3 + 1 + led_start], data[i * 3 + 2 + led_start]);

Im still not sure how do that at the moment, im still beginner.

rstephan commented 3 years ago

@andreas-git The code-snippets I write are not tested! So expect it that they don't compile properly (misspelled variable or forgotten bracket, ...). These snippets don't have to work. They should give you a direction where to go.

Don't copy-past "blind" and start crying if it is not work or it don't solve your exact problem!

But sure, use it as a start, maybe you are lucky.

The part:

//strip1.update(...)
//..

is just a comment to give you an orientation where to put the code. The for-loop is the important part and the fact to remove the universesReceived stuff.