[IoT] Aquarium monitor; controlling LED from C#

It’s been a few months now since I’ve posted the source code of Submerged on GitHub and started making some noise about it on hobbyist forums like ukaps. My main goal doing so was to gather feedback about which features people would want to see to convince them to use a solution like submerged. The number one requested feature by far: controlling LED lighting. Most aquascape tanks nowadays are lit using LED fixtures. Depending on your budget, you can buy cheap or expensive ones but basically they all do the same: control the output on a fixed number of channels.

I personally own a TC420 controller. This features 5 outputs which I use to control RGB + warm white + cold white LED strips. The controller is programmable by sticking in a USB cable and using some piece of shitty software to create timebound programs. There’s room for improvement.

I should begin with repeating that I am definitely not a hardware / electronics kind of guy. I like software, I find it somewhat fun to fiddle with simple electronics but when it gets too complicated I get lost. So creating my own LED controller was not an option, too much work and way too far outside of my comfort zone. It would be much easier if I could find a controller which would allow me to just send commands to it…

Luckily, todays world of domotica (which controls stuff in and around your house) offers some very affordable solutions for this problem. And so I found this controller:


It’s a simple RGBW (red, green, blue and white) type controller available for about $20 on AliExpress. Just search for “UFO LED controller” and you’ll find it there. So it’s cheap, easy to use and even better: it’s programmable over WiFi or Bluetooth! Just the thing I needed.

Note: the number of channels (4) and maximum output (96W = 8A) can be limiting. But since their price, you could just buy some more depending on what your needs are. I’d also advise to keep some headroom on the wattage which is limited by the type of adapter you connect to the controller. The controller doesn’t come with an adapter by the way, I reused the one I was using for the TC420.


Controlling the controller

So the controller comes in both WiFi as Bluetooth version (not both). I chose the WiFi type mainly because I found some code which seemed to be based on that one. There’s some stuff for Bluetooth out there as well for those interested. I can only assume the communication protocol is roughly the same. You can connect the controller to your existing WiFi network, the one to which you also connected your device (in my case my Raspberry Pi) which will be sending the commands. Use the WPS button on your router to connect, or read the manual to find out more about ways to connect the device.

Thankfully there was no need to reverse engineer the entire protocol because some other enthusiasts already did that. So I forked this repo and used the code to find out which commands to send over the wire. Just needed to convert it to C# code.

Once connected, first thing to do is to discover the device. This is done by sending out a UDP broadcast message on port 48899. That’s the following code:

async Task DiscoverDevice()
    IPEndPoint broadcastEndPoint = new IPEndPoint(IPAddress.Broadcast, 48899);

    // the broadcast message is a fixed one, do not change
    string msg = "HF-A11ASSISTHREAD";
    byte[] msgBytes = Encoding.ASCII.GetBytes(msg);

    UdpClient udp = new UdpClient();
    await udp.SendAsync(msgBytes, msgBytes.Length, broadcastEndPoint);

        // all active devices will reply, select the one we need 
        UdpReceiveResult receiveResult = await udp.ReceiveAsync();
        string returnData = Encoding.ASCII.GetString(receiveResult.Buffer);

        if (returnData.Contains(this._config.Device))
            _deviceAddress = returnData.Substring(0, returnData.IndexOf(','));
    } while (_deviceAddress == null);

I’ve added some additional code to filter out the replies (all devices will reply to this call) and find the one I need. The reply contains the IP address which we can now use to start sending TCP commands to the device.


Creating the TCP socket

Creating a new TCP socket is pretty easy. Make sure you’re connecting with a socket type of Stream and protocol Tcp. Also, the port for the TCP connection is 5577.

// connect the socket to the endpoint
IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(this._deviceAddress), 5577);
_socket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);


Sending packets

With the socket created, we can start sending messages to the controller. Each message will consist of a number of bytes followed by a checksum byte. The following two methods are used for sending those messages, I’ll get to the actual message contents a bit further on:

public static byte ComputeAdditionChecksum(byte[] data)
    byte sum = 0;
    unchecked // Let overflow occur without exceptions
        foreach (byte b in data)
            sum += b;
    return sum;

void SendPacket(List<byte> data)
        // add the checksum to the package as last byte
        var checksum = ComputeAdditionChecksum(data.ToArray());

        byte[] buffer = data.ToArray();
        int result = _socket.Send(buffer, buffer.Length, SocketFlags.None);

        Debug.WriteLine($"SendPacket returned: {result}");
    } catch (Exception ex)



Receiving packages

Similar to sending, we use the following code to receive messages. It reads the incoming bytes of which we know how many to expect, and outputs that into a byte array. Based on the forked code we can also work out which byte means what.

byte[] ReadRaw(int byte_count = 1024)
    byte[] buffer = new byte[byte_count];
    return buffer;

byte[] ReadResponse(int expected)
    var remaining = expected;
    var rx = new List<byte>();

    while (remaining > 0)
        var chunk = ReadRaw(remaining);
        remaining -= chunk.Length;

    return rx.ToArray();


On, off and setting RGBW color

The controller supports a couple of functions. Next to on, off and setting a static color you can also pick a preset, use timers and set speed for effects. I did not implement the additional functions because I do not need them, but the idea is the same so if you’d want to you can use those as well. Here are the methods I wrote for turning the controller on, off and setting a color based on RGBW values:

void TurnOn(bool on = true)
    List<byte> msg = new List<byte>();
    if (on)
        msg.AddRange(new byte[] { 0x71, 0x23, 0x0f });
        msg.AddRange(new byte[] { 0x71, 0x24, 0x0f });

    this.isOn = on;

void TurnOff()

void SetRgb(byte r, byte g, byte b, byte w, bool persist = true)
    List<byte> msg = new List<byte>();

    if (persist)

    msg.Add(r);         // Red
    msg.Add(g);         // Green
    msg.Add(b);         // Blue
    msg.Add(w);         // White



Note: the bytes you sent need to be very specific. If you add/remove bytes, the controller will stop understanding the commands and thus stop working. Use the forked code to find out the possible commands, or use a packet sniffer like Wireshark along with the out of the box app and see what is transmitted over the line.


Up next

That’s all! With the above methods implemented you should now be able to control the output from the controller. Of course that’s only the beginning for Submerged. I now need to come up with a good way of creating programs and ‘running’ those. I haven’t figured that part out yet, so keep an eye on my blog for a follow-up post. If you want to check out the completed code, click here.

I am planning to put this code in a separate library, opensource that on GitHub and publish to NuGet for those who want to reuse it. So if you’re interested in that, stay tuned and follow me on github.


, , ,

Related posts

Latest posts

Leave a Comment

Leave a Reply

Your email address will not be published. Required fields are marked *