Niall’s virtual diary archives – Sunday 21 February 2021

by . Last updated .

Sunday 21 February 2021: 00:48. We currently live in a former council house near Mallow, Cork which suffers rather from damp and mould, though certainly not as bad as in many Irish houses, especially ones in the humid south. This gives us all constant coughs, plus our sinuses are always congested, and we often don’t sleep as well as we might because we wake up early due to being unable to breathe due to being all bunged up. I have a dehumidifier which is excellent at drying out a room sufficient that one sleeps very well, but it is (a) noisy (b) expensive on electricity to run. So I’ve been looking for something which could clean out some of the crap which gets into the air, is quiet enough it can run 247, is cheap enough that I can deploy it throughout the house permanently, and at least reduces the severity of what ails us from this house.

After a bit of research, I eventually settled on the Xiaomi Air Purifier 3C for €90 delivered within the EU from Aliexpress, buying one for every room in the house. These are the cheapest edition of the third generation of Xiaomi’s very popular air purifier range widely used in the big cities of China, India and Poland to reduce the fine particulate air pollution inside your home to less toxic levels (for every 10 μg/m3 increase of PM2.5 in your air, there is a +36% increase in lung cancer, and 7.5% of all heart attacks are due to PM2.5). My main attraction to them was that they can be controlled over wifi without a cloud connection (or indeed any access to any other network) by python-miio, so I can script individual behaviours for each such as when to go fast (daytime), and when to go slow (night time). Here’s my kitchen 3C in action:

They’re a very simple design, particularly the low end 3C model which has almost no onboard intelligence, unlike the more expensive models (one doesn’t need onboard intelligence if one is scripting them). They consist of a plastic base, a replaceable filter which fits into it, a quiet and high efficiency variable RPM AC turbine fan, a LED display, and a very low end ARM CPU with 2.4Ghz 11n Wifi and Bluetooth (the CPU is so low end that ping times are in the 400-700ms range, and REST API calls take 1.5 seconds or so). The turbine fan will spin at any RPM you choose between 300 and 2200, in 10 RPM increments. Finally, there is a laser-based PM2.5 sensor, but on this cheapest model no temperature nor humidity sensors.

As you can see below, my 3C came with the grey filter, which is claimed to meet the EN 1822 H13 (HEPA) standard specification removing at least 99.95% of all particulates equal or exceeding 0.3 microns in size (i.e. PM0.3). Note that anything less than 0.3 microns will get filtered by almost any kind of filter, because smaller particles bounce around a lot and get trapped by just about any density of fibre – therefore, these purifiers readily scrub the air of covid-19 and most other viruses too. Inside the wood fibre HEPA filter there is an additional activated carbon filter, which might soak up some odours and Volatile Organic Compounds (VOCs).

Despite this model’s simplicity, no other air purifier comes remotely close to the feature set for €90 delivered. In fact, you’d probably need to multiply by six if you want a well known brand such as Blueair with a similar feature set. Obviously, nobody is expecting that a Blueair model for €550 isn’t going to beat one of these for €90, but I’m fairly sure that six of these would handily beat a single Blueair, and do it quieter with cheaper replaceables, even if the Xiaomi filter isn’t as good as it claims.

Of course, by far the most important part of any air purifier is the filter itself, partly because it determines whether the device will be of any use or not, but also because they tend to be the expensive consumable. As with all things Chinese, there are a lot of clones of Xiaomi filters, but I believe I screened those out. Incidentally, I discovered on a chinese forum actual numbers for the claims by Xiaomi for their different filters, I don’t believe these are easily findable in English, so I’m going to list them here for your (and my later) convenience:

TypeModel EfficiencyColourRFIDDevice supportPrice on Aliexpress delivered to EU
EPA Economical M2R-FLPEN 1822 E12 (99.5%) BlueYes2/2C/2H/2S/Pro/3C/3H€31
EPA Anti-bacterial MCR-FLGEN 1822 E12 (99.5%) Pink PurpleYes2/2C/2H/2S/Pro/3C/3H€33
EPA Anti-formaldehydeM1R-FLPEN 1822 E12 (99.5%) GreenYes2/2C/2H/2S/Pro/3C/3H€36
HEPA M8R-FLHEN 1822 H13 (99.95%)GreyYes2/2C/2H/2S/Pro/3C/3HUnavailable

Each filter has a RFID chip which tracks how many hours it has been used for, and you will be pestered to replace it after about six months of continuous usage. The EPA grade filters are, with some difficulty to find at that price, available for as little as €31 each delivered within the EU. I could not find the HEPA filters for sale, though because they are just new on the market, you can barely buy them in China either yet, so that situation may improve within six months. Assuming they are at least €45, that means they were half the cost of buying the whole purifier!

As the air in Ireland is extremely clean from a PM2.5 perspective, auto mode based on the sensor reading isn’t useful here. That afflicts a high end brand such as Blueair just as much as this Xiaomi unit. Therefore you need to manually override them to run faster all the time in order to clean the air of mould spores, and that’s where the scriptability over wifi comes in, because I don’t really want to have to manually go around the house adjusting these manually. Here is the script I wrote to control them, you can obtain the device token using the instructions from Home Assistant (I set up a separate WiFi SSID on a VLAN, used the Xiaomi Home app to register the devices, then used the Xiaomi Cloud Tokens Extractor to get the tokens, then closed off all access between the VLAN and any other network including the internet. This script connects into the VLAN using a source IP spoofing NAT).


from miio import airpurifier_miot
from miio.exceptions import DeviceException
import time

class Purifier:
  def __init__(self, ip, token, name):
    self.ip = ip
    self.__token = token = name
    self.__inst = None
    self.available = False
  def update(self):
      if self.__inst is None:
        self.__inst = airpurifier_miot.AirPurifierMB4(self.ip, self.__token)
      status = self.__inst.status()
    except (airpurifier_miot.AirPurifierMiotException, DeviceException) as e:
      print("Failed to connect to", self.ip, "(" + + ") due to", repr(e))
      self.available = False
    self.available = True
    self.mode = status.mode
    self.powered_on = 'on' in status.power
    self.air_ppm = 0 if status.aqi is None else status.aqi
    self.led_brightness = int(status.led_brightness_level)
    self.current_rpm = int(status.motor_speed)
    self.filter_hours_used = int(status.filter_hours_used)
    self.filter_life_remaining = int(status.filter_life_remaining)
  def __repr__(self):
    ret = 'Purifier(%s) available=%d' % (, self.available)
    if self.available:
      ret+= ' powered_on=%d air_ppm=%d led_brightness=%d current_rpm=%d filter_hours_used=%d filter_life_remaining=%d%%' % (self.powered_on, self.air_ppm, self.led_brightness, self.current_rpm, self.filter_hours_used, self.filter_life_remaining)
    return ret
  def enable_display(self):
    if self.available and self.led_brightness != 8:
      print('Purifier(%s) setting display to %d, led_brightness = %d' % (, 8, self.led_brightness))
  def disable_display(self):
    if self.available and self.led_brightness != 0:
      print('Purifier(%s) setting display to %d, led_brightness = %d' % (, 0, self.led_brightness))
  def set_rpm(self, newrpm):
    if self.available and abs(self.current_rpm - newrpm) > 20:
      if newrpm <= 400:
        if self.mode != airpurifier_miot.OperationMode.Silent:
        print('Purifier(%s) setting silent RPM to %d, current_rpm = %d' % (, newrpm, self.current_rpm))
        if self.mode != airpurifier_miot.OperationMode.Favorite:
        print('Purifier(%s) setting favourite RPM to %d, current_rpm = %d' % (, newrpm, self.current_rpm))

purifiers = {
  'Kitchen' : Purifier('', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Kitchen'),
  'Master Bedroom' : Purifier('', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Master Bedroom'),
  'Kids Bedroom' : Purifier('', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Kids Bedroom'),
  'Spare Bedroom' : Purifier('', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Spare Bedroom'),

while True:
  now = time.localtime()
  for name in purifiers:
    p = purifiers[name]
    print(time.asctime(now), p)
    if p.available:
      if p.powered_on:
          if 'Kitchen' in or (now.tm_hour >= 9 and now.tm_hour <= 21):
            # Daytime running
            if p.air_ppm >= 5:
              new_rpm = 1100 + p.air_ppm * 10
              if new_rpm > 2200:
                new_rpm = 2200
              new_rpm -= new_rpm % 10
          # Nighttime running
        except (airpurifier_miot.AirPurifierMiotException, DeviceException) as e:
          print("Failed to set", p.ip, "(" + + ") due to", repr(e))

  if now.tm_hour >= 22 and now.tm_min > 1:
    print("It is after 22.01pm, exiting!")

Sure, it’s not pretty, but it does the job until support for these purifiers lands into Home Assistant. I have it running inside a cronjob which launches at 08.59am, then the script exits itself at 22.02pm. This is to prevent constant WiFi traffic at night time as I have one of these units right next to my head in my bedroom. As the script runs, it outputs a constant sequence of logging which looks like:

Sun Feb 21 00:13:15 2021 Purifier(Kitchen) available=1 powered_on=1 air_ppm=0 led_brightness=8 current_rpm=1100 filter_hours_used=87 filter_life_remaining=97%
Sun Feb 21 00:13:15 2021 Purifier(Master Bedroom) available=1 powered_on=1 air_ppm=0 led_brightness=0 current_rpm=704 filter_hours_used=80 filter_life_remaining=98%
Sun Feb 21 00:13:15 2021 Purifier(Kids Bedroom) available=1 powered_on=1 air_ppm=0 led_brightness=0 current_rpm=704 filter_hours_used=81 filter_life_remaining=98%
Sun Feb 21 00:13:15 2021 Purifier(Spare Bedroom) available=1 powered_on=1 air_ppm=0 led_brightness=0 current_rpm=704 filter_hours_used=81 filter_life_remaining=98%
Sun Feb 21 00:13:24 2021 Purifier(Kitchen) available=1 powered_on=1 air_ppm=0 led_brightness=8 current_rpm=1104 filter_hours_used=87 filter_life_remaining=97%

Something a bit worrying is what happens whenever one cooks dinner, so the hob or oven is on. My script rapidly increases RPM on a per-purifier basis if its PM2.5 sensor exceeds five μg/m3, hitting the maximum 2200 RPM from 110 PM2.5 onwards. So far, we have not failed to hit at least 50 PM2.5 in the kitchen, sometimes over 300 PM2.5, though the purifier does clear it within fifteen minutes after you stop cooking. More worrying again is that this pollution gets all over the house, the other purifiers register 30 - 40 PM2.5 in our bedrooms.

Now, I knew from general reading that this is typical in UK-Irish homes which are unusually poorly ventilated by international norms, but until now it was all kinda abstract. I hadn’t really realised what it actually meant until I saw these sensors all jump throughout the house every dinner and lunch times where we cook hot food. We do have an extractor over the hob, but it’s a cheap noisy thing which doesn’t seem to extract much. So it looks like these air purifiers might do some good there too, which was not expected before I bought them.

#air-purifiers #xiaomi

Go back to the archive index Go back to the latest entries

Contact the webmaster: Niall Douglas @ webmaster2<at symbol> (Last updated: 2021-02-21 00:48:20 +0000 UTC)