diff --git a/MatrixRenderer.cs b/MatrixRenderer.cs
new file mode 100644
index 0000000..05a6652
--- /dev/null
+++ b/MatrixRenderer.cs
@@ -0,0 +1,60 @@
+using SkiaSharp;
+using System.Threading.Tasks;
+
+public class MatrixRenderer
+{
+ private readonly WledDdpClient _wled;
+ private readonly int _width = 64;
+ private readonly int _height = 64;
+
+ public MatrixRenderer(string wledIp)
+ {
+ _wled = new WledDdpClient(wledIp);
+ }
+
+ public async Task DrawAndSendFrameAsync(float currentTemp)
+ {
+ // 1. Create a 64x64 canvas
+ using SKBitmap bitmap = new SKBitmap(_width, _height, SKColorType.Rgba8888, SKAlphaType.Premul);
+ using SKCanvas canvas = new SKCanvas(bitmap);
+
+ // 2. Clear background to black
+ canvas.Clear(SKColors.Black);
+
+ // 3. Draw something (e.g., Temperature Text)
+
+ // Paint now only handles the color and rendering style
+ using SKPaint textPaint = new SKPaint
+ {
+ Color = SKColors.Orange,
+ IsAntialias = false // Keep crisp pixels for LED matrices
+ };
+
+ // Font handles the typography
+ using SKFont textFont = new SKFont
+ {
+ Size = 16
+ };
+
+ // DrawText now takes the string, X, Y, the font, and the paint
+ //canvas.DrawText($"{currentTemp:F1}c", 4, 32, textFont, textPaint);
+ // DrawText now takes the string, X, Y, alignment, the font, and the paint
+ canvas.DrawText($"{currentTemp:F1}c", 4, 32, SKTextAlign.Left, textFont, textPaint);
+
+ // 4. Convert 32-bit RGBA to 24-bit RGB for WLED
+ byte[] rgbaBytes = bitmap.Bytes;
+ byte[] rgbBytes = new byte[_width * _height * 3];
+
+ int rgbIndex = 0;
+ for (int i = 0; i < rgbaBytes.Length; i += 4)
+ {
+ rgbBytes[rgbIndex++] = rgbaBytes[i]; // R
+ rgbBytes[rgbIndex++] = rgbaBytes[i + 1]; // G
+ rgbBytes[rgbIndex++] = rgbaBytes[i + 2]; // B
+ // We ignore rgbaBytes[i + 3] (Alpha channel)
+ }
+
+ // 5. Fire it over UDP!
+ await _wled.SendFrameAsync(rgbBytes);
+ }
+}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..5b700b6
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,13 @@
+var renderer = new MatrixRenderer("192.168.0.150"); // Replace with WLED IP
+
+while (true)
+{
+ // TODO: Query InfluxDB for your BME680 data here
+ float bme680Temp = 22.5f;
+
+ await renderer.DrawAndSendFrameAsync(bme680Temp);
+
+ // Update screen at 30 FPS for smooth transitions,
+ // or every 5 seconds if just showing static data.
+ await Task.Delay(1000 / 30);
+}
\ No newline at end of file
diff --git a/WLED_DDP_Client.csproj b/WLED_DDP_Client.csproj
new file mode 100644
index 0000000..8c278a7
--- /dev/null
+++ b/WLED_DDP_Client.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/WLED_DDP_Client.slnx b/WLED_DDP_Client.slnx
new file mode 100644
index 0000000..c29e2ba
--- /dev/null
+++ b/WLED_DDP_Client.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/WledDdpClient.cs b/WledDdpClient.cs
new file mode 100644
index 0000000..03d8b25
--- /dev/null
+++ b/WledDdpClient.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+
+public class WledDdpClient
+{
+ private readonly UdpClient _udp;
+ private readonly string _ip;
+ private const int Port = 4048; // Standard DDP UDP port
+ private byte _sequence = 1;
+
+ public WledDdpClient(string ipAddress)
+ {
+ _ip = ipAddress;
+ _udp = new UdpClient();
+ }
+
+ public async Task SendFrameAsync(byte[] rgbData)
+ {
+ // Max payload per packet to stay under standard 1500 byte MTU
+ const int maxPixelsPerPacket = 480;
+ const int maxBytesPerPacket = maxPixelsPerPacket * 3;
+
+ int totalBytes = rgbData.Length;
+ int offset = 0;
+
+ while (offset < totalBytes)
+ {
+ int chunkLength = Math.Min(maxBytesPerPacket, totalBytes - offset);
+ bool isLast = (offset + chunkLength) >= totalBytes;
+
+ byte[] packet = new byte[10 + chunkLength];
+
+ // Byte 0: Flags. 0x40 = Version 1. 0x01 = Push bit.
+ // We only "Push" to the display on the final chunk of the frame.
+ packet[0] = (byte)(isLast ? 0x41 : 0x40);
+
+ // Byte 1: Sequence (0-15)
+ packet[1] = _sequence;
+
+ // Byte 2: Data type (1 = RGB)
+ packet[2] = 1;
+
+ // Byte 3: Destination ID (Default 1)
+ packet[3] = 1;
+
+ // Bytes 4-7: Data Offset (32-bit Big Endian)
+ packet[4] = (byte)(offset >> 24);
+ packet[5] = (byte)(offset >> 16);
+ packet[6] = (byte)(offset >> 8);
+ packet[7] = (byte)(offset);
+
+ // Bytes 8-9: Data Length (16-bit Big Endian)
+ packet[8] = (byte)(chunkLength >> 8);
+ packet[9] = (byte)(chunkLength);
+
+ // Copy the pixel payload into the packet
+ Buffer.BlockCopy(rgbData, offset, packet, 10, chunkLength);
+
+ await _udp.SendAsync(packet, packet.Length, _ip, Port);
+
+ offset += chunkLength;
+ }
+
+ // Increment sequence for the next frame (wrap at 15)
+ _sequence = (byte)((_sequence + 1) % 16);
+ }
+}
\ No newline at end of file