It is possible to control WS281X-based LED strips via Artemis using a controller.
With sufficient knowledge of C# and the controller you're using, you can easily write your own implementation from scratch but if can't or you don't want to, we have a plugin that can talk to some common controllers.
Artemis ships with a plugin that supports the following options:
The Arduino and BitWizard sketches are COM-based and thus require you to keep your respective controller connected via an USB cable.
The ESP8266 sketch supports up to 168 LEDs, since that results in the maximum paket size that is safe to transmit. Everything above could potentially be dropped, there is a hard-limit of 255 LEDs.
Make sure you enable the WS281X Plugin and it's Device Provider feature.
After that, open the plugin settings and add devices as you need them.
TODO: Screenshots
It's hard to cover any issues with custom hardware in a document like this, if you need any help check out the #ws281x
channel on the Discord server.
Below you can find sketches that should work with RGB.NET (and therefore Artemis) out-of-the-box. These are all taken from the RGB.NET repository
Be sure to read through them first and change the variables configuration section as needed.
The Arduino sketch is provided by DarthAffe on the RGB.NET repository.
#include "FastLED.h"
// This sketch allows to use the arduino as ArduinoWS2812USBDevice.
// The amount of leds that can be handled in realtime mostly depends on the speef of the arduino,
// but an arduino UNO is doing quite well.
//#### CONFIGURATION ####
#define CHANNELS 4 // change this only if you add or remove channels in the implementation-part. To disable channels set them to 0 leds.
// no more than 255 leds per channel
#define LEDS_CHANNEL_1 32
#define LEDS_CHANNEL_2 0
#define LEDS_CHANNEL_3 0
#define LEDS_CHANNEL_4 0
#define PIN_CHANNEL_1 6
#define PIN_CHANNEL_2 7
#define PIN_CHANNEL_3 8
#define PIN_CHANNEL_4 9
#define BAUD_RATE 115200
#define SERIAL_PROMPT ">"
//#######################
CRGB leds_channel_1[LEDS_CHANNEL_1];
CRGB leds_channel_2[LEDS_CHANNEL_2];
CRGB leds_channel_3[LEDS_CHANNEL_3];
CRGB leds_channel_4[LEDS_CHANNEL_4];
byte command = 0;
//-----------------------
void setup()
{
if(LEDS_CHANNEL_1 > 0) { FastLED.addLeds<NEOPIXEL, PIN_CHANNEL_1>(leds_channel_1, LEDS_CHANNEL_1); }
if(LEDS_CHANNEL_2 > 0) { FastLED.addLeds<NEOPIXEL, PIN_CHANNEL_2>(leds_channel_2, LEDS_CHANNEL_2); }
if(LEDS_CHANNEL_3 > 0) { FastLED.addLeds<NEOPIXEL, PIN_CHANNEL_3>(leds_channel_3, LEDS_CHANNEL_3); }
if(LEDS_CHANNEL_4 > 0) { FastLED.addLeds<NEOPIXEL, PIN_CHANNEL_4>(leds_channel_4, LEDS_CHANNEL_4); }
Serial.begin(BAUD_RATE);
Serial.print(SERIAL_PROMPT);
}
void loop()
{
if(command > 0)
{
switch(command)
{
// ### default ###
case 0x01: // get channel-count
Serial.write(CHANNELS);
break;
case 0x02: // update
FastLED.show();
break;
case 0x0F: // ask for prompt
break;
// ### channel 1 ###
case 0x11: // get led-count of channel 1
Serial.write(LEDS_CHANNEL_1);
break;
case 0x12: // set leds of channel 1
Serial.readBytes(((uint8_t*)leds_channel_1), (LEDS_CHANNEL_1 * 3));
break;
// ### channel 2 ###
case 0x21: // get led-count of channel 2
Serial.write(LEDS_CHANNEL_2);
break;
case 0x22: // set leds of channel 2
Serial.readBytes(((uint8_t*)leds_channel_2), (LEDS_CHANNEL_2 * 3));
break;
// ### channel 3 ###
case 0x31: // get led-count of channel 3
Serial.write(LEDS_CHANNEL_3);
break;
case 0x32: // set leds of channel 3
Serial.readBytes(((uint8_t*)leds_channel_3), (LEDS_CHANNEL_3 * 3));
break;
// ### channel 4 ###
case 0x41: // get led-count of channel 4
Serial.write(LEDS_CHANNEL_4);
break;
case 0x42: // set leds of channel 4
Serial.readBytes(((uint8_t*)leds_channel_4), (LEDS_CHANNEL_4 * 3));
break;
// ### default ###
default:
command = 0;
return; // no prompt
}
Serial.print(SERIAL_PROMPT);
command = 0;
}
}
void serialEvent()
{
if (Serial.available())
{
command = Serial.read();
}
}
BitWizard does not require a sketch.
The ESP8266 sketch is provided by DarthAffe on the RGB.NET repository
#define FASTLED_ESP8266_RAW_PIN_ORDER
#include "FastLED.h"
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiUdp.h>
#include "base64.hpp"
//#### CONFIGURATION ####
// WLAN settings
const char* ssid = ""; // WLAN-network-name
const char* password = ""; // WLAN-password
#define CHANNELS 4 // change this only if you add or remove channels in the implementation-part. To disable channels set them to 0 leds.
// should not exceed 168 leds, since that results in the maximum paket size that is safe to transmit. Everything above could potentially be dropped.
// no more than 255 leds per channel (hard limit)
#define LEDS_CHANNEL_1 3
#define LEDS_CHANNEL_2 0
#define LEDS_CHANNEL_3 0
#define LEDS_CHANNEL_4 0
#define PIN_CHANNEL_1 15 // D8
#define PIN_CHANNEL_2 13 // D7
#define PIN_CHANNEL_3 12 // D6
#define PIN_CHANNEL_4 14 // D5
#define WEBSERVER_PORT 80
//#######################
CRGB leds_channel_1[LEDS_CHANNEL_1];
CRGB leds_channel_2[LEDS_CHANNEL_2];
CRGB leds_channel_3[LEDS_CHANNEL_3];
CRGB leds_channel_4[LEDS_CHANNEL_4];
ESP8266WebServer server(WEBSERVER_PORT);
WiFiUDP Udp;
bool isUDPEnabled;
int udpPort;
byte incomingPacket[767]; // 255 (max leds) * 3 + 2 (header)
byte lastSequenceNumbers[CHANNELS];
bool checkSequenceNumber(int channel, byte currentSequenceNumber)
{
bool isValid = (currentSequenceNumber > lastSequenceNumbers[channel]) || ((lastSequenceNumbers[channel] > 200) && (currentSequenceNumber < 50));
if(isValid)
{
lastSequenceNumbers[channel] = currentSequenceNumber;
}
return isValid;
}
void processUDP()
{
int packetSize = Udp.parsePacket();
if (packetSize)
{
// receive incoming UDP packets
byte sequenceNumber = Udp.read();
byte channel = Udp.read();
if(checkSequenceNumber(channel, sequenceNumber))
{
switch(channel)
{
case 1: // set leds of channel 1
Udp.read((uint8_t*)leds_channel_1, (LEDS_CHANNEL_1 * 3));
FastLED.show();
break;
// ### channel 2 ###
case 2: // set leds of channel 2
Udp.read((uint8_t*)leds_channel_2, (LEDS_CHANNEL_2 * 3));
FastLED.show();
break;
// ### channel 3 ###
case 3: // set leds of channel 3
Udp.read((uint8_t*)leds_channel_3, (LEDS_CHANNEL_3 * 3));
FastLED.show();
break;
// ### channel 4 ###
case 4: // set leds of channel 4
Udp.read((uint8_t*)leds_channel_4, (LEDS_CHANNEL_4 * 3));
FastLED.show();
break;
// ### default ###
default:
break;
}
}
}
}
void handleRoot()
{
String infoSite = (String)"<html>\
<head>\
<title>RGB.NET</title>\
</head>\
<body>\
<h1>RGB.NET</h1>\
This device is currently running the NodeMCU WS281X RGB.NET-Sketch.<br />\
<br />\
Check <a href=\"https://github.com/DarthAffe/RGB.NET\">https://github.com/DarthAffe/RGB.NET</a> for more info and the latest version of this sketch.<br />\
<br />\
<h3>Configuration:</h3>\
<b>UDP:</b>\ " + (isUDPEnabled ? ((String)"enabled (" + udpPort + ")") : "disabled") + "<br />\
<br />\
<b>Channel 1</b><br />\
Leds: " + LEDS_CHANNEL_1 + "<br />\
Pin: " + PIN_CHANNEL_1 + "<br />\
<br />\
<b>Channel 2</b><br />\
Leds: " + LEDS_CHANNEL_2 + "<br />\
Pin: " + PIN_CHANNEL_2 + "<br />\
<br />\
<b>Channel 4</b><br />\
Leds: " + LEDS_CHANNEL_3 + "<br />\
Pin: " + PIN_CHANNEL_3 + "<br />\
<br />\
<b>Channel 4</b><br />\
Leds: " + LEDS_CHANNEL_4 + "<br />\
Pin: " + PIN_CHANNEL_4 + "<br />\
<br />\
</body>\
</html>";
server.send(200, "text/html", infoSite);
}
void handleConfig()
{
String config = (String)"{\
\"channels\": [\
{\
\"channel\": 1,\
\"leds\": " + LEDS_CHANNEL_1 + "\
},\
{\
\"channel\": 2,\
\"leds\": " + LEDS_CHANNEL_2 + "\
},\
{\
\"channel\": 3,\
\"leds\": " + LEDS_CHANNEL_3 + "\
},\
{\
\"channel\": 4,\
\"leds\": " + LEDS_CHANNEL_4 + "\
}\
]\
}";
server.send(200, "application/json", config);
}
void handleEnableUDP()
{
if(isUDPEnabled)
{
Udp.stop();
}
udpPort = server.arg(0).toInt();
Udp.begin(udpPort);
isUDPEnabled = true;
server.send(200, "text/html", "");
}
void handleDisableUDP()
{
if(isUDPEnabled)
{
Udp.stop();
isUDPEnabled = false;
}
server.send(200, "text/html", "");
}
void handleReset()
{
for(int i = 0; i < CHANNELS; i++)
{
lastSequenceNumbers[i] = 0;
}
for(int i = 0; i < LEDS_CHANNEL_1; i++)
{
leds_channel_1[i] = CRGB::Black;
}
for(int i = 0; i < LEDS_CHANNEL_2; i++)
{
leds_channel_2[i] = CRGB::Black;
}
for(int i = 0; i < LEDS_CHANNEL_3; i++)
{
leds_channel_3[i] = CRGB::Black;
}
for(int i = 0; i < LEDS_CHANNEL_4; i++)
{
leds_channel_4[i] = CRGB::Black;
}
FastLED.show();
server.send(200, "text/html", "");
}
void handleUpdate()
{
unsigned int dataLength = decode_base64((unsigned char*)server.arg(0).c_str(), incomingPacket);
byte channel = (byte)incomingPacket[1];
switch(channel)
{
case 1: // set leds of channel 1
memcpy((uint8_t*)leds_channel_1, &incomingPacket[2], (LEDS_CHANNEL_1 * 3));
FastLED.show();
break;
// ### channel 2 ###
case 2: // set leds of channel 2
memcpy((uint8_t*)leds_channel_2, &incomingPacket[2], (LEDS_CHANNEL_2 * 3));
FastLED.show();
break;
// ### channel 3 ###
case 3: // set leds of channel 3
memcpy((uint8_t*)leds_channel_3, &incomingPacket[2], (LEDS_CHANNEL_3 * 3));
FastLED.show();
break;
// ### channel 4 ###
case 4: // set leds of channel 4
memcpy((uint8_t*)leds_channel_4, &incomingPacket[2], (LEDS_CHANNEL_4 * 3));
FastLED.show();
break;
// ### default ###
default:
break;
}
server.send(200, "text/html", "");
}
void setup()
{
if(LEDS_CHANNEL_1 > 0) { FastLED.addLeds<NEOPIXEL, PIN_CHANNEL_1>(leds_channel_1, LEDS_CHANNEL_1); }
if(LEDS_CHANNEL_2 > 0) { FastLED.addLeds<NEOPIXEL, PIN_CHANNEL_2>(leds_channel_2, LEDS_CHANNEL_2); }
if(LEDS_CHANNEL_3 > 0) { FastLED.addLeds<NEOPIXEL, PIN_CHANNEL_3>(leds_channel_3, LEDS_CHANNEL_3); }
if(LEDS_CHANNEL_4 > 0) { FastLED.addLeds<NEOPIXEL, PIN_CHANNEL_4>(leds_channel_4, LEDS_CHANNEL_4); }
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
}
delay(100);
server.on("/", handleRoot);
server.on("/config", handleConfig);
server.on("/enableUDP", handleEnableUDP);
server.on("/disableUDP", handleDisableUDP);
server.on("/reset", handleReset);
server.on("/update", handleUpdate);
server.onNotFound(handleRoot);
server.begin();
handleReset();
}
void loop()
{
server.handleClient();
if(isUDPEnabled)
{
processUDP();
}
}