Skip to content

Understanding the ack parameter

Jan Scheiper edited this page Jan 3, 2022 · 4 revisions

The ack parameter of the Control constructor seems to be the cause of endless problems and confusion which this wiki entry tries to finally solve for good.

Quick and easy solution

If you don't want to read the full explanation and only want to know how to figure out what the ack parameter needs to be set to, this section is for you. Basically you need to perform the following steps:

  1. Get access to the magic-home CLI. Either install the library globally (npm i -g magic-home) or clone the repo and access it from there node ./cli.js).
  2. Run magic-home discover to find out the IP address of your controller or bulb if you don't know it already.
  3. Run a series of commands and note their output/return state:
magic-home --bytes -A 15 on $IP
magic-home --bytes -A 15 color $IP 255 0 0
magic-home --bytes -A 15 pattern $IP seven_color_cross_fade 50

The first command should turn your controller on. The second one will set it's color to red and the third one will display a color fade effect. If the command prints something like Received: 30 followed by success, you can be sure that the controller has acknowledged your command. If, on the other hand, the controller reacts to the request (e.g. it turns on), but you get an output like Error: Command timed out, you know that the controller will not send acknowledgements for this type of command.

With this knowledge in hand you can now build your ack parameter:

const ack = {
    power: true, // set to true if the first command was acknowledged, otherwise set it to false
    color: false, // set to true if the second command was acknowledged, otherwise set it to false 
    pattern: false, // set to true if the third command was acknowledged, otherwise set it to false 
    custom_pattern: false, // set to true if the third command was acknowledged, otherwise set it to false 
};

If you want to save yourself the typing, you can also use the Control.ackMask function, which takes a bitmask with the lowest 4 bits corresponding to the four ack parameters. Calling Control.ackMask(0b0011) would result in the object {power: true, color: true, pattern: false, custom_pattern: false} for example.

Explanation

What are acknowledgements

The controllers or bulbs use WiFi and the TCP protocol for communication. On top of that they use their own proprietary protocol, which varies from controller to controller. If we send a command to the controller there is no consistent way to know if the controller has successfully received and executed it. Some controllers make it easy for us however, since they always reply with one or multiple bytes to our commands, which gives us a mechanism to determine whether or not our command was received. These responses are called acknowledgements and they are pretty important for this library.

Why do we need them?

You might ask yourself this question: Why even wait for the controller at all? Why not just open a socket, send a command and then close it again. Who cares if we get a response back? For most methods like setPower() or setColor(), this is actually a somewhat reasonable approach. A problem arises however, when we want to get a response from the controller like with the query() method. If we were to just fire and forget a "turn on" command, followed immediately by a "query" command, the responses would get mixed up, since there is no way to correlate the responses and the controller always sends the response to each command to all connected sockets. The only sensible way around this issue is to wait for the acknowledgement of the first command and then send the second one afterwards. This is also a nice way to resolve promises or call callbacks, since we can now know when the action requested by a specific method is finished.

But what do we need the ack parameter for?

The previous two sections mirror my thoughts while writing the first version of this library. I only owned a single controller and it always replied with 0x30 or 0x31 to every command, so I designed the library around the way I understood the protocol. Only later did I discover that there are other controllers out there, which behave differently. Some don't reply to any command at all, and some only respond to some commands but not all of them. It was therefore required to give users of this library (including myself) an option to selectively disable the expectation of an acknowledgement from the controller.