diff --git a/Project-Aurora/Project-Aurora/Devices/UDP/UDPDevice.cs b/Project-Aurora/Project-Aurora/Devices/UDP/UDPDevice.cs
new file mode 100644
index 000000000..fd73ca160
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Devices/UDP/UDPDevice.cs
@@ -0,0 +1,137 @@
+using Aurora.Settings;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Aurora.Devices.UDP
+{
+    public class UdpDevice : DefaultDevice
+    {
+        public override string DeviceName => "UDP";
+
+        private Stopwatch redrawWatch = new Stopwatch();
+        private UdpClient udpClient;
+
+        private string[] ips = new string[0];
+        private List<IPEndPoint> endpoints;
+        private DeviceKeys deviceKey;
+        private int ledCount;
+
+        private Color lastColor;
+
+        private const int defaultPort = 19446;
+
+        protected override string DeviceInfo => string.Join(", ", endpoints.Select(endpoint => endpoint.ToString()));
+
+        protected override void RegisterVariables(VariableRegistry variableRegistry)
+        {
+            var devKeysEnumAsEnumerable = Enum.GetValues(typeof(DeviceKeys)).Cast<DeviceKeys>();
+
+            variableRegistry.Register($"{DeviceName}_devicekey", DeviceKeys.Peripheral, "Key Color to Use",
+                devKeysEnumAsEnumerable.Max(), devKeysEnumAsEnumerable.Min());
+            variableRegistry.Register($"{DeviceName}_led_count", 300, "LED Count");
+            variableRegistry.Register($"{DeviceName}_ip", "", "Addresses (IP:port - Comma separated)");
+        }
+
+
+        public override bool Initialize()
+        {
+            if (IsInitialized) return IsInitialized = true;
+
+            deviceKey = Global.Configuration.VarRegistry.GetVariable<DeviceKeys>($"{DeviceName}_devicekey");
+            ledCount = Global.Configuration.VarRegistry.GetVariable<int>($"{DeviceName}_led_count");
+            ips = Global.Configuration.VarRegistry.GetVariable<string>($"{DeviceName}_ip").Split(',');
+
+            if (ips.Length < 2 && ips[0] == "")
+            {
+                LogError("UDP has no IP");
+                return IsInitialized = false;
+            }
+            else
+            {
+                LogInfo($"{ips.Length} |{string.Join("|", ips)}|");
+            }
+
+            endpoints = new List<IPEndPoint>();
+            foreach (var ip in ips)
+            {
+                try
+                {
+                    int udpPort = defaultPort;
+                    var parts = ip.Split(':');
+                    if (parts.Length > 1)
+                    {
+                        if (!int.TryParse(parts[1], out udpPort))
+                        {
+                            udpPort = defaultPort;
+                        }
+                    }
+                    endpoints.Add(new IPEndPoint(IPAddress.Parse(parts[0]), udpPort));
+                }
+                catch (FormatException)
+                {
+                } // Don't crash on malformed IPs
+            }
+
+            udpClient = new UdpClient();
+            lastColor = Color.Black;
+            redrawWatch.Start();
+
+            return IsInitialized = true;
+        }
+
+        public override void Shutdown()
+        {
+            endpoints = null;
+            udpClient.Dispose(); // udpClient is IDisposable
+            udpClient = null;
+            redrawWatch.Stop();
+
+            IsInitialized = false;
+        }
+
+        public override bool UpdateDevice(Dictionary<DeviceKeys, Color> keyColors, DoWorkEventArgs e,
+            bool forced = false)
+        {
+            if (!IsInitialized) return false;
+
+            if (keyColors.ContainsKey(deviceKey))
+            {
+                var c = keyColors[deviceKey];
+                if (redrawWatch.ElapsedMilliseconds < 1000
+                ) // Only send the color when it changes or a full second has passed
+                {
+                    if (c == lastColor && !forced) return true;
+                }
+                else
+                {
+                    redrawWatch.Restart();
+                }
+
+                lastColor = c;
+
+                // Build a payload by repeating the color ledCount times
+                var payload = new byte[3 * ledCount];
+                for (var i = 0; i < ledCount * 3; i += 3)
+                {
+                    payload[i + 0] = c.R;
+                    payload[i + 1] = c.G;
+                    payload[i + 2] = c.B;
+                }
+
+                // Actually send the payload to each endpoint
+                foreach (var endpoint in endpoints)
+                {
+                    udpClient.Send(payload, payload.Length, endpoint);
+                }
+            }
+
+            return true;
+        }
+    }
+}
\ No newline at end of file