mirror of
https://github.com/DarthAffe/RGB.NET.git
synced 2025-12-12 17:48:31 +00:00
Added device-provider for NodeMCU based WS281X-leds
This commit is contained in:
parent
93eae1d859
commit
7c6db8738c
87
RGB.NET.Devices.WS281X/NodeMCU/NodeMCUWS2812USBDevice.cs
Normal file
87
RGB.NET.Devices.WS281X/NodeMCU/NodeMCUWS2812USBDevice.cs
Normal file
@ -0,0 +1,87 @@
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace RGB.NET.Devices.WS281X.NodeMCU
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
/// <inheritdoc cref="AbstractRGBDevice{TDeviceInfo}" />
|
||||
/// <summary>
|
||||
/// Represents an NodeMCU WS2812 device.
|
||||
/// </summary>
|
||||
public class NodeMCUWS2812USBDevice : AbstractRGBDevice<NodeMCUWS2812USBDeviceInfo>, ILedStripe
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
/// <summary>
|
||||
/// Gets the update queue performing updates for this device.
|
||||
/// </summary>
|
||||
public NodeMCUWS2812USBUpdateQueue UpdateQueue { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override NodeMCUWS2812USBDeviceInfo DeviceInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel (as defined in the NodeMCU-sketch) this device is attached to.
|
||||
/// </summary>
|
||||
public int Channel { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NodeMCUWS2812USBDevice"/> class.
|
||||
/// </summary>
|
||||
/// <param name="deviceInfo">The update trigger used by this queue.</param>
|
||||
/// <param name="updateQueue">The update queue performing updates for this device.</param>
|
||||
/// <param name="channel">The channel (as defined in the NodeMCU-sketch) this device is attached to.</param>
|
||||
public NodeMCUWS2812USBDevice(NodeMCUWS2812USBDeviceInfo deviceInfo, NodeMCUWS2812USBUpdateQueue updateQueue, int channel)
|
||||
{
|
||||
this.DeviceInfo = deviceInfo;
|
||||
this.UpdateQueue = updateQueue;
|
||||
this.Channel = channel;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
internal void Initialize(int ledCount)
|
||||
{
|
||||
for (int i = 0; i < ledCount; i++)
|
||||
InitializeLed(LedId.LedStripe1 + i, new Point(i * 10, 0), new Size(10, 10));
|
||||
|
||||
//TODO DarthAffe 23.12.2018: Allow to load a layout.
|
||||
|
||||
if (Size == Size.Invalid)
|
||||
{
|
||||
Rectangle ledRectangle = new Rectangle(this.Select(x => x.LedRectangle));
|
||||
Size = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override object CreateLedCustomData(LedId ledId) => (Channel, (int)ledId - (int)LedId.LedStripe1);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<Led> GetLedsToUpdate(bool flushLeds) => (flushLeds || LedMapping.Values.Any(x => x.IsDirty)) ? LedMapping.Values : null;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateLeds(IEnumerable<Led> ledsToUpdate) => UpdateQueue.SetData(ledsToUpdate.Where(x => x.Color.A > 0));
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dispose()
|
||||
{
|
||||
try { UpdateQueue?.Dispose(); }
|
||||
catch { /* at least we tried */ }
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
51
RGB.NET.Devices.WS281X/NodeMCU/NodeMCUWS2812USBDeviceInfo.cs
Normal file
51
RGB.NET.Devices.WS281X/NodeMCU/NodeMCUWS2812USBDeviceInfo.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace RGB.NET.Devices.WS281X.NodeMCU
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Represents a generic information for a <see cref="T:RGB.NET.Devices.WS281X.NodeMCU.NodeMCUWS2812USBDevice" />.
|
||||
/// </summary>
|
||||
public class NodeMCUWS2812USBDeviceInfo : IRGBDeviceInfo
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DeviceName { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public RGBDeviceType DeviceType => RGBDeviceType.LedStripe;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Manufacturer => "NodeMCU";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Model => "WS2812 WLAN";
|
||||
|
||||
/// <inheritdoc />
|
||||
public RGBDeviceLighting Lighting => RGBDeviceLighting.Key;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsSyncBack => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri Image { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NodeMCUWS2812USBDeviceInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of this device.</param>
|
||||
public NodeMCUWS2812USBDeviceInfo(string name)
|
||||
{
|
||||
this.DeviceName = name;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
106
RGB.NET.Devices.WS281X/NodeMCU/NodeMCUWS2812USBUpdateQueue.cs
Normal file
106
RGB.NET.Devices.WS281X/NodeMCU/NodeMCUWS2812USBUpdateQueue.cs
Normal file
@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace RGB.NET.Devices.WS281X.NodeMCU
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Represents the update-queue performing updates for NodeMCU WS2812 devices.
|
||||
/// </summary>
|
||||
public class NodeMCUWS2812USBUpdateQueue : UpdateQueue
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private static readonly byte UPDATE_COMMAND = 0x02;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly string _hostname;
|
||||
|
||||
private readonly UdpClient _udpClient;
|
||||
private readonly Dictionary<int, byte[]> _dataBuffer = new Dictionary<int, byte[]>();
|
||||
private readonly Dictionary<int, byte> _sequenceNumbers = new Dictionary<int, byte>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NodeMCUWS2812USBUpdateQueue"/> class.
|
||||
/// </summary>
|
||||
/// <param name="updateTrigger">The update trigger used by this queue.</param>
|
||||
/// <param name="hostname">The hostname to connect to.</param>
|
||||
/// <param name="port">The port used by the web-connection.</param>
|
||||
public NodeMCUWS2812USBUpdateQueue(IDeviceUpdateTrigger updateTrigger, string hostname, int port)
|
||||
: base(updateTrigger)
|
||||
{
|
||||
this._hostname = hostname;
|
||||
|
||||
_udpClient = new UdpClient(_hostname, port);
|
||||
_udpClient.Connect(_hostname, port);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Update(Dictionary<object, Color> dataSet)
|
||||
{
|
||||
foreach (IGrouping<int, ((int channel, int key), Color Value)> channelData in dataSet.Select(x => (((int channel, int key))x.Key, x.Value))
|
||||
.GroupBy(x => x.Item1.channel))
|
||||
{
|
||||
int channel = channelData.Key;
|
||||
byte[] buffer = _dataBuffer[channel];
|
||||
|
||||
buffer[0] = GetSequenceNumber(channel);
|
||||
buffer[1] = (byte)((channel << 4) | UPDATE_COMMAND);
|
||||
int i = 2;
|
||||
foreach ((byte _, byte r, byte g, byte b) in channelData.OrderBy(x => x.Item1.key)
|
||||
.Select(x => x.Value.GetRGBBytes()))
|
||||
{
|
||||
buffer[i++] = r;
|
||||
buffer[i++] = g;
|
||||
buffer[i++] = b;
|
||||
}
|
||||
|
||||
Send(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<(int channel, int ledCount)> GetChannels()
|
||||
{
|
||||
WebClient webClient = new WebClient();
|
||||
webClient.DownloadString($"http://{_hostname}/reset");
|
||||
|
||||
int channelCount = int.Parse(webClient.DownloadString($"http://{_hostname}/channels"));
|
||||
for (int channel = 1; channel <= channelCount; channel++)
|
||||
{
|
||||
int ledCount = int.Parse(webClient.DownloadString($"http://{_hostname}/channel/{channel}"));
|
||||
if (ledCount > 0)
|
||||
{
|
||||
_dataBuffer[channel] = new byte[(ledCount * 3) + 2];
|
||||
_sequenceNumbers[channel] = 0;
|
||||
yield return (channel, ledCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Send(byte[] data) => _udpClient.Send(data, data.Length);
|
||||
|
||||
private byte GetSequenceNumber(int channel)
|
||||
{
|
||||
byte sequenceNumber = (byte)((_sequenceNumbers[channel] + 1) % byte.MaxValue);
|
||||
_sequenceNumbers[channel] = sequenceNumber;
|
||||
return sequenceNumber;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
|
||||
|
||||
using System.Collections.Generic;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace RGB.NET.Devices.WS281X.NodeMCU
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Represents a definition of an NodeMCU WS2812 devices.
|
||||
/// </summary>
|
||||
public class NodeMCUWS281XDeviceDefinition : IWS281XDeviceDefinition
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hostname to connect to.
|
||||
/// </summary>
|
||||
public string Hostname { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the port of the UDP connection.
|
||||
/// </summary>
|
||||
public int Port { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name used by this device.
|
||||
/// This allows to use {0} as a placeholder for a incrementing number if multiple devices are created.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NodeMCUWS281XDeviceDefinition"/> class.
|
||||
/// </summary>
|
||||
/// <param name="hostname">The hostname to connect to.</param>
|
||||
/// <param name="port">The port of the UDP connection.</param>
|
||||
public NodeMCUWS281XDeviceDefinition(string hostname, int port = 1872)
|
||||
{
|
||||
this.Hostname = hostname;
|
||||
this.Port = port;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IRGBDevice> CreateDevices(IDeviceUpdateTrigger updateTrigger)
|
||||
{
|
||||
NodeMCUWS2812USBUpdateQueue queue = new NodeMCUWS2812USBUpdateQueue(updateTrigger, Hostname, Port);
|
||||
IEnumerable<(int channel, int ledCount)> channels = queue.GetChannels();
|
||||
int counter = 0;
|
||||
foreach ((int channel, int ledCount) in channels)
|
||||
{
|
||||
string name = string.Format(Name ?? $"NodeMCU WS2812 WIFI ({Hostname}) [{{0}}]", ++counter);
|
||||
NodeMCUWS2812USBDevice device = new NodeMCUWS2812USBDevice(new NodeMCUWS2812USBDeviceInfo(name), queue, channel);
|
||||
device.Initialize(ledCount);
|
||||
yield return device;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
210
RGB.NET.Devices.WS281X/Sketches/RGB.NET_udp.ino
Normal file
210
RGB.NET.Devices.WS281X/Sketches/RGB.NET_udp.ino
Normal file
@ -0,0 +1,210 @@
|
||||
#define FASTLED_ESP8266_RAW_PIN_ORDER
|
||||
|
||||
#include "FastLED.h"
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
//#### 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 PORT 1872 // if changed needs to be configured in RGB.NET (default: 1872)
|
||||
|
||||
#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];
|
||||
|
||||
WiFiServer server(WEBSERVER_PORT);
|
||||
WiFiUDP Udp;
|
||||
|
||||
byte incomingPacket[767]; // 255 (max leds) * 3 + 2 (header)
|
||||
byte lastSequenceNumbers[CHANNELS];
|
||||
|
||||
String header;
|
||||
|
||||
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); }
|
||||
|
||||
for(int i = 0; i < CHANNELS; i++)
|
||||
{
|
||||
lastSequenceNumbers[i] = 255;
|
||||
}
|
||||
|
||||
WiFi.begin(ssid, password);
|
||||
while (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
delay(500);
|
||||
}
|
||||
delay(100);
|
||||
|
||||
Udp.begin(PORT);
|
||||
server.begin();
|
||||
}
|
||||
|
||||
bool checkSequenceNumber(int channel, byte currentSequenceNumber)
|
||||
{
|
||||
bool isValid = (currentSequenceNumber > lastSequenceNumbers[channel]) || ((lastSequenceNumbers[channel] > 200) && (currentSequenceNumber < 50));
|
||||
if(isValid)
|
||||
{
|
||||
lastSequenceNumbers[channel] = currentSequenceNumber;
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Web client
|
||||
WiFiClient client = server.available();
|
||||
if (client)
|
||||
{
|
||||
String currentLine = "";
|
||||
while (client.connected())
|
||||
{
|
||||
if (client.available())
|
||||
{
|
||||
char c = client.read();
|
||||
header += c;
|
||||
if (c == '\n') {
|
||||
if (currentLine.length() == 0) {
|
||||
client.println("HTTP/1.1 200 OK");
|
||||
client.println("Content-type:text/html");
|
||||
client.println("Connection: close");
|
||||
client.println();
|
||||
|
||||
if (header.indexOf("GET /reset") >= 0)
|
||||
{
|
||||
for(int i = 0; i < CHANNELS; i++)
|
||||
{
|
||||
lastSequenceNumbers[i] = 255;
|
||||
}
|
||||
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();
|
||||
}
|
||||
else if (header.indexOf("GET /channels") >= 0)
|
||||
{
|
||||
client.println(CHANNELS);
|
||||
}
|
||||
else if (header.indexOf("GET /channel/1") >= 0)
|
||||
{
|
||||
client.println(LEDS_CHANNEL_1);
|
||||
}
|
||||
else if (header.indexOf("GET /channel/2") >= 0)
|
||||
{
|
||||
client.println(LEDS_CHANNEL_2);
|
||||
}
|
||||
else if (header.indexOf("GET /channel/3") >= 0)
|
||||
{
|
||||
client.println(LEDS_CHANNEL_3);
|
||||
}
|
||||
else if (header.indexOf("GET /channel/4") >= 0)
|
||||
{
|
||||
client.println(LEDS_CHANNEL_4);
|
||||
}
|
||||
client.println();
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentLine = "";
|
||||
}
|
||||
}
|
||||
else if (c != '\r')
|
||||
{
|
||||
currentLine += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
header = "";
|
||||
client.stop();
|
||||
}
|
||||
|
||||
// Color update
|
||||
int packetSize = Udp.parsePacket();
|
||||
if (packetSize)
|
||||
{
|
||||
// receive incoming UDP packets
|
||||
byte sequenceNumber = Udp.read();
|
||||
byte command = Udp.read();
|
||||
switch(command)
|
||||
{
|
||||
// ### channel 1 ###
|
||||
case 0x12: // set leds of channel 1
|
||||
if(checkSequenceNumber(0, sequenceNumber))
|
||||
{
|
||||
Udp.read(((uint8_t*)leds_channel_1), (LEDS_CHANNEL_1 * 3));
|
||||
FastLED.show();
|
||||
}
|
||||
break;
|
||||
|
||||
// ### channel 2 ###
|
||||
case 0x22: // set leds of channel 2
|
||||
if(checkSequenceNumber(1, sequenceNumber))
|
||||
{
|
||||
Udp.read(((uint8_t*)leds_channel_2), (LEDS_CHANNEL_2 * 3));
|
||||
FastLED.show();
|
||||
}
|
||||
break;
|
||||
|
||||
// ### channel 3 ###
|
||||
case 0x32: // set leds of channel 3
|
||||
if(checkSequenceNumber(2, sequenceNumber))
|
||||
{
|
||||
Udp.read(((uint8_t*)leds_channel_3), (LEDS_CHANNEL_3 * 3));
|
||||
FastLED.show();
|
||||
}
|
||||
break;
|
||||
|
||||
// ### channel 4 ###
|
||||
case 0x42: // set leds of channel 4
|
||||
if(checkSequenceNumber(3, sequenceNumber))
|
||||
{
|
||||
Udp.read(((uint8_t*)leds_channel_4), (LEDS_CHANNEL_4 * 3));
|
||||
FastLED.show();
|
||||
}
|
||||
break;
|
||||
|
||||
// ### default ###
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user