IKEA have these lovely remote control units for pairing with their various devices, and the wonderful thing is that they are quite easy to re-purpose to control anything. They send messages using the Zigbee protocol so all you have to do is get a Zigbee receiver and intercept the messages. Sounds simple, but it took a lot longer than I thought. I ordered the cheapest possible USB Zigbee controller - the CC2531. Yes, that’s the one which you are not supposed to get as it has a poor range and is problematic to configure, but it was so cheap I just had to try.
The plan was simple. Once I could get the messages from the remote I could use it as a volume control for my pi based Volumio music streamer.
Flashing the Zigbee card
First up was trying to flash the firmware for the controller. Fortunately I found this site with a set of really clear instructions. I just crimped the end of a few short hook up cables and managed to get a good connection to my trusty old pi to perform the firmware flash.
Next was the software stack. I generally have a preference for the smallest possible tool to do the job, but this time I had to choose a slightly heavier option in the form of Zibee2MQTT. Zigbee2MQTT is pretty impressive, but the installation steps take a while and you have to be wary of making sure you have up to date versions of node/npm otherwise you get all sorts of odd errors. I also ran into a weird issue in that zigbee2mqtt would hard crash my Pi Zero 2W which had to be fixed by updating the firmware on the pi itself to a new version.
Connecting the remote
Once I had the software installed there are instructions for pairing devices:
IKEA E1744. It is worth reading these pages well to understand the various configuration options as the debounce
setting was important to manage the rate of messages coming from the volume control.
Writing the code
With all that in place I could move on to writing a quick python script to collect the messages and control Volumio. Fortunately Volumio has a simple REST API so I cobbled together a hacky script to send messages across. I went with the low tech, but quick, solution of using regular expressions to parse the messages from the stream of MQTT data. It took a bit of tuning to get the feel right for the volume control by playing with the debounce
value and the volume step sizes in Volumio.
Here is a quick example of the stream of data you get:
{"action":"skip_forward","battery":0,"brightness":221,"linkquality":13,"rate":195,"step_size":1,"transition_time":0,"update":{"installed_version":553797169,"latest_version":604241925,"state":"available"},"update_available":true}
{"action":"brightness_step_up","action_step_size":1,"action_transition_time":0,"battery":0,"brightness":221,"linkquality":15,"rate":195,"step_size":1,"transition_time":0,"update":{"installed_version":553797169,"latest_version":604241925,"state":"available"},"update_available":true}
{"action":"skip_forward","battery":0,"brightness":221,"linkquality":13,"rate":195,"step_size":1,"transition_time":0,"update":{"installed_version":553797169,"latest_version":604241925,"state":"available"},"update_available":true}
{"action":"rotate_right","action_rate":195,"battery":0,"brightness":221,"linkquality":0,"rate":195,"update":{"installed_version":553797169,"latest_version":604241925,"state":"available"},"update_available":true}
{"action":"rotate_stop","battery":0,"brightness":237,"linkquality":0,"rate":195,"update":{"installed_version":553797169,"latest_version":604241925,"state":"available"},"update_available":true}
{"action":"rotate_left","action_rate":195,"battery":0,"brightness":237,"linkquality":0,"rate":195,"update":{"installed_version":553797169,"latest_version":604241925,"state":"available"},"update_available":true}
The last job was simply to set up the python script as a systemd service. I’m sure I can’t be the only one who struggles with systemd … I thought I was doing the correct thing by using a --user
service until, after wasting too much time trying to work out why it wasn’t working, I learned that --user
services only start when the user logs in and then stop when the user logs out. I thought they would just be a nice user mode equivalent of running a service as a user without polluting /etc
. Oh well, it works perfectly now :-)
The final result is a very smooth professional remote control for my music streamer.
Code:
#!/home/john/ikea_remote/venv/bin/python3
from paho.mqtt import client as mqtt
import http.client
import re
client_get = mqtt.Client(client_id='my_client', protocol=mqtt.MQTTv31)
client_get.connect('localhost', 1883)
def call_volumio(command):
conn = http.client.HTTPConnection("volumiostreamer",timeout=0.2)
conn.request("GET", "/api/v1/commands/?cmd=" + command)
conn.close()
def volume_up():
call_volumio("volume&volume=plus")
def pause():
call_volumio("toggle")
def volume_down():
call_volumio("volume&volume=minus")
def next():
call_volumio("next")
def prev():
call_volumio("previous")
def callback(client, userdata, message):
jsonMessage=str(message.payload.decode("utf-8"))
print(jsonMessage)
if re.search("toggle", jsonMessage):
pause()
if re.search("rotate_right", jsonMessage):
volume_up()
if re.search("rotate_left", jsonMessage):
volume_down()
if re.search("step_up", jsonMessage):
next()
if re.search("step_down", jsonMessage):
prev()
client_get.on_message = callback
client_get.subscribe('zigbee2mqtt/ikea_volume', qos=1)
client_get.loop_forever()