Sometimes you see someone do something that looks interesting, and figure that you want to have a go at doing that, but with an entirely different set of tools. This is how you end up hacking IKEA on an ordinary Tuesday. Or, at least, IKEA’s Vindriktning air quality sensor. Because I just used a Raspberry Pi Pico W to put one of those on the internet.
The Vindriktning air quality sensor
The Vindriktning air quality sensor is sort of fascinating. Opening it up is pretty easy; all you’ll need is a PH0 screwdriver. Once open, you’ll see a Cubic PM1006 particulate sensor connected via flying leads to a PCB which hosts an Eastsoft ES7P001FGSA microcontroller, which talks to the PM1006 sensor and drives the LED on the front of the Vindriktning based on the readings.
Crucially, you’ll also see some convenient test points (TP) on the PCB, which we can use to connect our own microcontroller board to extend the functionality of the Vindriktning, and to connect it to the internet. We’re interested in the set of TPs furthest away from the USB-C power jack, as these break out the traffic between the PM1006 sensor and the Eastsoft microcontroller. The other set of TPs are connected to the RGB LED and the blower housed beneath the particulate sensor.
Vindriktning works by pulling in air using a blower through the PM1006 PM2.5 sensor, so when adding additional electronics, we’re going to have to be careful not to obstruct the airflow with our modifications.
The PM1006 sensor itself is a pretty complicated piece of engineering. It has an infrared light-emitting diode and an optoelectronic sensor. Light from the diode is reflected by particulates in the air, and the intensity of the reflected light is measured to derive the air quality. Calculation of the PM2.5 value is all handled by a microcontroller inside the PM1006 sensor itself, which, helpfully for us, talks to the outside world using UART, and we can intercept that stream of data by soldering jumper leads to those exposed TPs.
Let’s do some soldering
You should solder wires to the TPs labelled as +5V, GND, and REST, then connect them as shown in the table below to your Raspberry Pi Pico W to VSYS, GND, and the RX pin of the default UART0. As you can see, I’ve used some Kapton tape over the top of my connections. It’s not strictly necessary, but it keeps them from shorting as they move around, and provides a bit of de-tensioning.
Pico | RP2040 | Vindriktning |
VSYS | VSYS1 | +5V |
GND | GND | GND |
Pin 2 | GP1 (UART0 RX) | REST (PM1006 TX) |
1Do not power the Raspberry Pi Pico via micro-USB and VSYS at the same time without using a Schottky diode, see Section 4.5 of the Raspberry Pi Pico Datasheet.
You can also see from the image that I’ve also gone ahead and soldered a male header to pins 6, 7, and 8 of my Pico. That’s going to allow me to easily send data back out using the Pico’s other default UART (UART1) using the Raspberry Pi Debug Probe. This is also something that’s not totally necessary, so you can skip doing that if you like, but it is helpful during debugging to have some feedback from your code.
As well as putting the Vindriktning sensor on to the internet I decided to extend its capabilities a bit using a Bosch BMP280 barometric pressure sensor.
I’ve used an Adafruit breakout board for the BMP280, but the same sensor can easily be found on breakout boards by other vendors like Pimoroni. While Adafruit’s BMP280 breakout offers both SPI and I2C interfaces, we’re going to use the I2C interface, and I chose to connect it to Pico’s I2C1 bus which is broken out on pins 19 and 20 of your Pico. Go ahead and connect wires to those pins as well as the 3V3 (OUT) pin and a nearby GND pin, and connect them to the breakout.
Pico | RP2040 | Signal | BMP280 Breakout1 |
3V3 (OUT) | — | +3V3 | VIN |
GND | GND | GND | GND |
Pin 19 | GP14 (I2C1 SDA) | SDA | SDI |
Pin 20 | GP15 (I2C1 SCL) | SCL | SCK |
1 Depending on your sensor, the BMP280 can be found at I2C address 0x76 or 0x77.
Be careful when you’re soldering things together; remember this is all going to have to fit back into the case, so keep your wires as short as you can. The more wire there is, the more wire you’re going to have to stuff back inside the Vindriktning case.
Writing the software
While the Adafruit tutorial, written by Liz Clark, made use of CircuitPython and an ESP32 talking to the Adafruit IO service, I’m going to be doing something a little bit different.
Instead of sending our data into the cloud, I’m going to run a local webserver on our Raspberry Pi Pico that you can ping via an HTTP GET request and get a JSON file back.
I’m going to make our Vindriktning RESTful.
To do that I’m going to be using David Stenwell’s BMP280 MicroPython library, along with bits torn out of Liz Clark’s CircuitPython example code and my own tutorial on how to run a webserver on a Pico W.
Because if you can’t steal from yourself, who can you steal from? (Although in any case, all of the code I’ve written or made use of here is open source, and released under the MIT licence.)
import network
import socket
import time
from machine import Pin
from machine import UART
from machine import I2C
from bmp280 import *
I2C1_SDA = 14
I2C1_SCL = 15
measurements = [0, 0, 0, 0, 0]
measurement_idx = 0
def valid_header(d):
headerValid = (d[0] == 0x16 and d[1] == 0x11 and d[2] == 0x0B)
return headerValid
led = Pin("LED", Pin.OUT)
led.on()
uart0 = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1))
i2c1 = I2C(1, scl=Pin(I2C1_SCL), sda=Pin(I2C1_SDA), freq=100000)
bmp = BMP280(i2c1)
bmp.use_case(BMP280_CASE_WEATHER)
bmp.oversample(BMP280_OS_HIGH)
bmp.temp_os = BMP280_TEMP_OS_8
bmp.press_os = BMP280_PRES_OS_4
bmp.standby = BMP280_STANDBY_250
bmp.iir = BMP280_IIR_FILTER_2
bmp.spi3w = BMP280_SPI3W_ON
bmp.power_mode = BMP280_POWER_NORMAL
ssid = 'SSID'
password = 'PASSWORD'
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
json = """{
"sensor":"vindriktning",
"pm25":%s,
"temperature":%s,
"pressure":%s
}
"""
max_wait = 10
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
time.sleep(1)
if wlan.status() != 3:
raise RuntimeError('network connection failedn')
else:
status = wlan.ifconfig()
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.bind(addr)
s.listen(1)
led.off()
# Listen for connections
while True:
try:
cl, addr = s.accept()
request = cl.recv(1024)
request = str(request)
reading = request.find('/reading')
stateis = "ERROR"
if reading == 6:
led.on()
v = False
while v is not True:
data = uart0.read(32)
if data is not None:
v = valid_header(data)
measurement_idx = 0
start_read = True
while True:
if start_read is True:
pm25 = (data[5] << 8) | data[6]
measurements[measurement_idx] = pm25
if measurement_idx == 4:
start_read = False
measurement_idx = (measurement_idx + 1) % 5
else:
break
stateis = str(measurements)
response = json % (stateis, str(bmp.temperature), str(bmp.pressure))
cl.send('HTTP/1.0 200 OKrnContent-type: application/jsonrnrn')
cl.send(response)
cl.close()
led.off()
except OSError as e:
cl.close()
I’ve posted the full source code, including a copy of the BMP280 library, as a Gist on Github.
Unlike the slightly cut-down version above (which has been abbreviated for blog post purposes), that version includes all the debug code, which prints helpful messages on UART1 which you can monitor to get feedback on how the code is running – at least until you seal everything back up into the Vindriktning’s case.
You’ll need to copy both the vindriktning.py
and bmp280.py
files to your Pico, and you can do that by loading both files into Thonny and then saving them to your Raspberry Pi Pico. If you haven’t done something like this before, you can find more information on working with MicroPython and Pico in the Raspberry Pi Pico Python SDK book.
Remember to substitue the name and password for your own wireless network into the code.
Running the code
To test the code, you can leave your Raspberry Pi Pico connected the micro USB cable attached to your computer, or unplug it and plug the Vindriktning into its USB-C power supply.
It’s important that you don’t have your Pico plugged into your computer via the micro USB cable and have the Vindriktning powered via the USB C cable at the same time, unless you’ve used a Schottky diode (see Section 4.5 of the Raspberry Pi Pico Datasheet) when soldering between the TP on the Vindriktning’s PCB and the VSYS pin on your Pico. Otherwise, bad things will happen if you back-power the Pico via VSYS while it’s also being powered from USB.
On the other hand, it’s absolutely fine to have your Debug Probe plugged into your Pico while it’s being powered by the Vindriktning via VSYS, which is why adding that header to allow you to do that via UART1 is so handy.
Once the Pico powers up, it’ll grab an IP address from your network. If you have your Pico connected to your computer using the Debug Probe, and you’re using the “full fat” version of the code, you’ll see that IP address scroll past in your console window. Otherwise, you’ll need to check your router to figure out what address it’s assigned your Pico using DHCP.
Then head to your browser and go to http://XXX.XXX.XXX.XXX/reading
, and you should see something like the above, with the last five measurements of PM2.5 from the PM1006 sensor, along with a measurement of the temperature in centigrade, and the barometric pressure in hectopascals (hPa).
Be aware that the time it’ll take to get a response from our server is going to vary. Remember I mentioned earlier that the PM1006 is chattering away over its UART back to that 8-bit Eastsoft microcontroller? All we’re doing is listening in on that stream of serial data, so we have to wait, and look out for the start of a data frame.
The start of a frame is signalled when we see the 0x16
, 0x11
, and 0x0B
bytes. Our code looks out for those and then gets ready to read the data from the PM1006, but depending on when we poll our webserver it could be a little while — a second or so — before we see the start of a frame.
Depending on the age of your BMP280 sensor, it’ll be found either at address 0x76
or 0x77
on the I2C bus, and there’s some debate online which is most common. If the code doesn’t work for you, flip the address in line 91 of the bmp280.py
file and see if you have better luck.
If all goes well you should see a JSON response in your browser.
{
"sensor":"vindriktning",
"pm25":[12, 12, 12, 12, 12],
"temperature":27.07,
"pressure":101365.9
}
Congratulations, your Vindriktning is now RESTful. You can now unattach your Debug Probe if you were using it and close up the Vindriktning case.
It might take a little bit of moving things around, but everything will (eventually) fit and the case should screw back together cleanly. You’ll need to make sure your boards and wires aren’t blocking the screw holes, and that they aren’t dropping below the PM1006 sensor and getting trapped between the side of the sensor and the PCB. Everything should fit comfortably.
Wrapping up
While the Adafruit tutorial by Liz Clark was the thing that inspired me to go off and do this myself, there seems to be a long history of hacking the IKEA Vindriktning.
Elsewhere there’s an excellent walkthrough of reversing the Vindriktning by Daniel Cuthbert as part of his home assistant project which makes use of various ESP8266 and ESP32 boards. Sören Beye also has an Arduino project that adds MQTT functionality to the Vindriktning using an ESP8266, and Lup Yuen Lee has a walkthrough for getting the Vindriktning up and running under the Apache NuttX RTOS.
What’s fascinating for me is that about the Vindriktning is how easy to hack it. There is plenty of space inside the case — and yes, we’re probably compromising the airflow somewhat, but nothing comes for free — and there are those convenient test points, including ones for the blower and the front-facing LED that I haven’t really explored here.
I’m bought three or four of them and I’m probably going to end up converting them all, but each one will end up somewhat different. Because potentially there’s a lot more to be done here, more and different sensors you could add. I definitely want to squeeze a temperature and humidity sensor in there, and you could conceivably even replace the IKEA microcontroller board entirely if you want to take over responsibility for polling the PM1006 yourself. If anyone has ideas, I’d love to hear them!