Hacking IKEA

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.

IKEA air quality sensors
Vindriktning air quality sensors from IKEA

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.

A cracked open IKEA air quality sensor with all the electronics inside
Opening up the Vindriktning air quality 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

Raspberry Pi Pico W hooked up to an IKEA air quality sensor
The IKEA Vindriktning with a Raspberry Pi Pico

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.

PicoRP2040Vindriktning
VSYSVSYS1+5V
GNDGNDGND
Pin 2GP1 (UART0 RX)REST (PM1006 TX)
Mapping the physical pins, RP2040 pins, and the Vindriktning

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.

Adding an Adafruit BMP280 breakout to our Pico and Vindriktning air quality 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.

PicoRP2040SignalBMP280 Breakout1
3V3 (OUT)+3V3VIN
GNDGNDGNDGND
Pin 19GP14 (I2C1 SDA)SDASDI
Pin 20GP15 (I2C1 SCL)SCLSCK
Mapping the physical pins, RP2040 pins, and the Adafruit BMP280 breakout

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 failed\n')
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 OK\r\nContent-type: application/json\r\n\r\n')
        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.

Grabbing the air quality, temperature, and barometric pressure, via REST

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 pascals (Pa).

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 0x160x11, 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.

Closing up the Vindriktning case with the Raspberry Pi Pico inside

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.

The newly internet-enabled Vindriktning air quality sensor

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!

22 comments

JumpZero avatar

Thanks Allan,
very nice step by step tutorial. I must try this hack. And the Vindriktning is only 9.99€ !

DidierCH avatar

Thank you very much for this hack. My only complaint with this Ikea sensor is the constant fan noise. In a quiet environment, it is very audible unfortunately.

Alasdair Allan avatar

So you can do something to reduce the noise from the blower. Right now it’s being powered by a +5V supply from the original PCB. If you power it from the Pico’s 3V3 (OUT) pin then it’ll still run, but the noise will be much reduced. Of course, you’re now responsible for turning it off and on, and the air flow will also be reduced. But having tried it out — I too thought it was a bit loud — it seems not to make much of a difference to the measurements. Perhaps it would elsewhere in the measurement range but in normal “green light” territory it seems okay.

Szaja avatar

I’m afraid that you will always be in the green territory, as Pico is blocking the air inlet. I would say that a better test would be to start in the orange territory and stay there after the hack.

Alasdair Allan avatar

So, yes. The Pico does cause reduced airflow to the sensor. But it doesn’t seem to be enough to affect the measurements. I’ve got one on my lab bench and it’ll reliably go into the yellow — or in one case red — while I’m soldering if I leave the bench extractor fan off.

Adrian avatar

Hi guys, or…simply replace the fan with a Noctua? (Well known for silence)

John avatar

Very. Very cool!

Michael avatar

I have serval of these running…. BUT: the fan inside sucks! every 30 sec (don’t know the exact time right now) the fan is running for the measurement -> in normal usage environment, this is no prob, but I don’t want to use this in sleeping room!

Alasdair Allan avatar

You can reduce the noise from the blower by powering it from the Pico’s 3V3 (OUT) pin. It’ll still run, but the noise will be much reduced. Of course, you’re then responsible for turning it off and on, and the air flow will be somewhat reduced. But it does reduce the fan noise considerably.

Cuba avatar

take a look at https://www.laskakit.cz/laskakit-esp-vindriktning-esp-32-i2c/
ESP32 board to fit into Vindriktning with own LEDs

Alasdair Allan avatar

Saw that one, it’s pretty cool that someone has gone to the trouble to spin a custom PCB for the Vindriktning.

Connor avatar

When these came out I was rather excited but then I went to the product page and found out they ONLY did air quality and was disappointed. I don’t understand why they simply didn’t add cheap pressure, and combination temp/humidity sensors it would have been a really nice device to throw around the house linking with a smart hub to do virtual environmental controls even if the price was slightly more. Exciting to see people hacking to add the features it could have had in the first place.

lond avatar

The low cost solution is to use a ESP8266 to connect the sensor to internet:
https://hackaday.com/2021/07/24/esp8266-adds-wifi-logging-to-ikeas-air-quality-sensor/
And my re-spinn to make it cleaner:
https://hackaday.com/2021/08/13/hacked-ikea-air-quality-sensor-gets-custom-pcb/

Alasdair Allan avatar

Oh, like that you’ve used the JST footprint on the edge of the original PCB, very neat.

Mikey-Mike avatar

One small quibble … I believe the pressure shown in your output (“pressure”:101365.9) has units of Pascals (Pa) not hectopascals (hPa). Divide Pa by 100 to get hPa.

Alasdair Allan avatar

Yup. Fixed!

TMW avatar

In the loop:

# while True:
# if start_read is True:
# pm25 = (data[5] << 8) | data[6]

the content of variable "data" doesn't change.
So "measurements" will always contain five the same values?

On Adafruit there is an example in wich "data" is read first
from the uart for all five values.

https://learn.adafruit.com/ikea-vindriktning-hack-with-qt-py-esp32-s3-and-adafruit-io/code-the-vindriktning

Jade avatar

Did exactly this with an esp8266! These pm2.5 sensors are highly capable and a fantastic bang for your buck. The natural next step is to hook it up to MQTT so home-assistant can monitor the data and use it for automation! I know the esp8266 can do this via esphome/tasmota so I’m sure it’s doable on the rp2040

Alasdair Allan avatar

Yup, ESPHome even runs on RP2040!

Alex avatar

I’m trying your code with just the pico and the 2.5 sensor. How would you ammmend it to work?

Josh Gallagher avatar

Nice build and write up, definitely almost makes me want to go to IKEA (almost… It’ll take me a while to build up the energy to go to the yellow and blue hell haha!) – reading everything and looking at the Vindriktning insides, it strikes me the PCB isn’t doing a huge amount at this point that couldn’t be done by the Pico. At £15 for the Vindriktning, I wonder if just getting a fan, an air quality sensor, and then 3D printing/sourcing an enclosure would be cheaper? Out of interest, does the barometric pressure sensor give accurate results, I’d have thought the fan would increase the pressure inside the enclosure and affect the readings?

Marcelo avatar

You could swap the BMP280 for a BME680, which does temperature, humidity, pressure, IAQ, VOC equivalent and CO2 equivalent.
I currently use one, paired with an ESP32 (no Vindriktning, since it’s not sold in IKEA Mexico), running esphome.

Comments are closed