Air Quality Sensor

Updated July 19, 2022 by luwol03 and Shaquu

UUID: 0000008D-0000-1000-8000-0026BB765291

Characteristics

Name Required
NitrogenDioxideDensity
OzoneDensity
PM10Density
PM2_5Density
SulphurDioxideDensity
VOCDensity
Name
StatusActive
StatusFault
StatusLowBattery
StatusTampered
AirQuality

To have a real Air Quality Sensor appear in the Home.app showing a Particulate Levels for 2.5 and 10 microns as well as an Air Quality Index based on the US EPA Scale, you can setup a Node-red flow to provide air characteristics value in HomeKit.

Example

These simple examples are meant to be copied into your Node-RED system and adapted to your setup.

Please note: Different from other HomeKit services (e.g. temperature sensor) the Home.app is not showing the Air Quality Measures on the device icon. To view the current values you have to open the preferences of the device in the Home.app. This works as designed by Apple and can’t be changed with characteristics properties.

Simple Air Quality Sensor

This example is a simple air quality sensor passing the characteristics value for testing purposes.

image

Note: A value for {"AirQuality" : 4} and {"AirQuality" : 5} will trigger a notification on the device running the Home.app.

[{"id":"ffa9ddb4.26a9e8","type":"homekit-service","z":"7138f7e3.add4a8","isParent":true,"bridge":"60729d35.03880c","accessoryCategory":"OTHER","parentService":"","name":"AirQuality","serviceName":"AirQualitySensor","topic":"","filter":false,"manufacturer":"Default Manufacturer","model":"Default Model","serialNo":"Default Serial Number","cameraConfigVideoProcessor":"ffmpeg","cameraConfigSource":"","cameraConfigStillImageSource":"","cameraConfigMaxStreams":2,"cameraConfigMaxWidth":1280,"cameraConfigMaxHeight":720,"cameraConfigMaxFPS":10,"cameraConfigMaxBitrate":300,"cameraConfigVideoCodec":"libx264","cameraConfigAudioCodec":"libfdk_aac","cameraConfigAudio":false,"cameraConfigPacketSize":1316,"cameraConfigVerticalFlip":false,"cameraConfigHorizontalFlip":false,"cameraConfigMapVideo":"0:0","cameraConfigMapAudio":"0:1","cameraConfigVideoFilter":"scale=1280:720","cameraConfigAdditionalCommandLine":"-tune zerolatency","cameraConfigDebug":false,"cameraConfigSnapshotOutput":"disabled","characteristicProperties":"{\n   \"AirQuality\" : 0,\n   \"PM2_5Density\" : 1.0,\n   \"PM10Density\" : 2.0,\n   \"StatusActive\" : 1\n}","x":1120,"y":1340,"wires":[[],[]]},{"id":"56a1e83b.c2441","type":"inject","z":"7138f7e3.add4a8","name":"","topic":"","payload":"{\"foo\":\"bar\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":1200,"wires":[["ffa9ddb4.26a9e8"]]},{"id":"f9907030.d93868","type":"inject","z":"7138f7e3.add4a8","name":"","topic":"","payload":"{\"AirQuality\":3}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":1360,"wires":[["ffa9ddb4.26a9e8"]]},{"id":"17c07925.fe45a7","type":"inject","z":"7138f7e3.add4a8","name":"","topic":"","payload":"{\"PM2_5Density\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":250,"y":1480,"wires":[["ffa9ddb4.26a9e8"]]},{"id":"e2706164.e33e5","type":"inject","z":"7138f7e3.add4a8","name":"","topic":"","payload":"{\"PM10Density\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":250,"y":1520,"wires":[["ffa9ddb4.26a9e8"]]},{"id":"35ca8872.7b877","type":"inject","z":"7138f7e3.add4a8","name":"","topic":"","payload":"{\"AirQuality\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":1280,"wires":[["ffa9ddb4.26a9e8"]]},{"id":"3e5ee1ad.6fd466","type":"inject","z":"7138f7e3.add4a8","name":"","topic":"","payload":"{\"AirQuality\":5}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":1440,"wires":[["ffa9ddb4.26a9e8"]]},{"id":"829b371b.08c6d","type":"inject","z":"7138f7e3.add4a8","name":"","topic":"","payload":"{\"AirQuality\":2}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":1320,"wires":[["ffa9ddb4.26a9e8"]]},{"id":"6b87d568.54d98c","type":"inject","z":"7138f7e3.add4a8","name":"","topic":"","payload":"{\"AirQuality\":0}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":1240,"wires":[["ffa9ddb4.26a9e8"]]},{"id":"e539751f.24eeb","type":"inject","z":"7138f7e3.add4a8","name":"","topic":"","payload":"{\"AirQuality\":4}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":1400,"wires":[["ffa9ddb4.26a9e8"]]},{"id":"60729d35.03880c","type":"homekit-bridge","z":"","bridgeName":"RedMatic-HAP-02","pinCode":"002-01-001","port":"","allowInsecureRequest":false,"manufacturer":"Default Manufacturer","model":"Default Model","serialNo":"Default Serial Number","customMdnsConfig":false,"mdnsMulticast":true,"mdnsInterface":"","mdnsPort":"","mdnsIp":"","mdnsTtl":"","mdnsLoopback":true,"mdnsReuseAddr":true,"allowMessagePassthrough":true}]

This is an example of an Air Quality Sensor captures real-time date from a simple web service using a http-request:

image

There are a couple of public available websites providing real-time data to monitor pollution in public available areas. The author of this Wiki has been chosen the service luftdaten.info for the setup above, a service provided by the OK Lab Stuttgart. The OK Lab Stuttgart is part of the program Code for Germany of the Open Knowledge Foundation Germany. The aim of the program is to promote developments in the areas of transparency, Open Data and Citizen Science.

As such the service is providing a web based accessible network of air quality sensors, distributed across Germany. A few sensors are also located outside of Germany across Europe.

This service is providing real-time values for PM 10 and PM 2.5 particulate density. A URL (e.g. http://api.luftdaten.info/v1/sensor/{{SENSOR_ID}}/) can be called by a http request node in Node-red to retrieve a parsed payload with sensor data, formed as an analyzed JSON-object.

The following Node-RED flow uses one particular sensor.:

[{"id":"cae95a6c.0010d8","type":"inject","z":"7138f7e3.add4a8","name":"Initiate Airquality Read","topic":"","payload":"true","payloadType":"bool","repeat":"1200","crontab":"","once":true,"onceDelay":"10","x":270,"y":1120,"wires":[["8dee64b7.b1cd"]]},{"id":"9f84bead.b2093","type":"debug","z":"7138f7e3.add4a8","name":"HK Output","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","x":1130,"y":1120,"wires":[]},{"id":"8dee64b7.b1cd","type":"http request","z":"7138f7e3.add4a8","name":"Air Quality Kiedrich","method":"GET","ret":"obj","paytoqs":false,"url":"http://api.luftdaten.info/v1/sensor/23490/","tls":"","persist":false,"proxy":"","authType":"","x":530,"y":1120,"wires":[["3d68d0ef.68703"]],"info":"Source: [Kiedrich](https://luftdaten.info)"},{"id":"3d68d0ef.68703","type":"function","z":"7138f7e3.add4a8","name":"Adjust payload for HomeKit","func":"var hkMsg = {};\n\nvar pm10 = Number(msg.payload[0].sensordatavalues[0].value);\nvar pm2 = Number(msg.payload[0].sensordatavalues[1].value);\nvar airQualityPM10, airQualityPM2, airQuality;\n\n// Banding AirQiality based on US AQI to provide HomeKit Levels\n//   Source: https://aqicn.org/calculator\n\nif (pm10 < 55) {\n    airQualityPM10 = 1;\n} else if (pm10 < 155) {\n    airQualityPM10 = 2\n} else if (pm10 < 255) {\n    airQualityPM10 = 3\n} else if (pm10 < 355) {\n    airQualityPM10 = 4\n} else if (pm10 >= 425) {\n    airQualityPM10 = 5;\n} else {\n  airQualityPM10 = 0;  \n}\n\nif (pm2 < 12) {\n    airQualityPM2 = 1;\n} else if (pm2 < 35.5) {\n    airQualityPM2 = 2\n} else if (pm2 < 55.5) {\n    airQualityPM2 = 3\n} else if (pm2 < 150.5) {\n    airQualityPM2 = 4\n} else if (pm2 >= 250.5) {\n    airQualityPM2 = 5;\n} else {\n  airQualityPM2 = 0;  \n}\n\n// Average airQuality derived from PM2 and PM10\n\nairQuality = (airQualityPM10 + airQualityPM2) / 2;\n\nhkMsg.payload = {\n    \"AirQuality\" : Number(airQuality.toFixed(0)),\n    \"PM10Density\" : pm10,\n    \"PM2_5Density\" : pm2,\n    \"StatusActive\" : 1\n    };\n\nreturn hkMsg;","outputs":1,"noerr":0,"x":800,"y":1120,"wires":[["9f84bead.b2093","ffa9ddb4.26a9e8"]]},{"id":"ffa9ddb4.26a9e8","type":"homekit-service","z":"7138f7e3.add4a8","isParent":true,"bridge":"60729d35.03880c","accessoryCategory":"OTHER","parentService":"","name":"AirQuality","serviceName":"AirQualitySensor","topic":"","filter":false,"manufacturer":"Default Manufacturer","model":"Default Model","serialNo":"Default Serial Number","cameraConfigVideoProcessor":"ffmpeg","cameraConfigSource":"","cameraConfigStillImageSource":"","cameraConfigMaxStreams":2,"cameraConfigMaxWidth":1280,"cameraConfigMaxHeight":720,"cameraConfigMaxFPS":10,"cameraConfigMaxBitrate":300,"cameraConfigVideoCodec":"libx264","cameraConfigAudioCodec":"libfdk_aac","cameraConfigAudio":false,"cameraConfigPacketSize":1316,"cameraConfigVerticalFlip":false,"cameraConfigHorizontalFlip":false,"cameraConfigMapVideo":"0:0","cameraConfigMapAudio":"0:1","cameraConfigVideoFilter":"scale=1280:720","cameraConfigAdditionalCommandLine":"-tune zerolatency","cameraConfigDebug":false,"cameraConfigSnapshotOutput":"disabled","characteristicProperties":"{\n   \"AirQuality\" : 0,\n   \"PM2_5Density\" : 1.0,\n   \"PM10Density\" : 2.0,\n   \"StatusActive\" : 1\n}","x":1120,"y":1200,"wires":[[],[]]},{"id":"60729d35.03880c","type":"homekit-bridge","z":"","bridgeName":"RedMatic-HAP-02","pinCode":"002-01-001","port":"","allowInsecureRequest":false,"manufacturer":"Default Manufacturer","model":"Default Model","serialNo":"Default Serial Number","customMdnsConfig":false,"mdnsMulticast":true,"mdnsInterface":"","mdnsPort":"","mdnsIp":"","mdnsTtl":"","mdnsLoopback":true,"mdnsReuseAddr":true,"allowMessagePassthrough":true}]
image

The PM 10 and PM 2.5 value is directly mapped to the appropriate HomeKit Characteristics value, using a function node.

const pm10 = Number(msg.payload[0].sensordatavalues[0].value);
const pm2 = Number(msg.payload[0].sensordatavalues[1].value);

//...

hkMsg.payload = {
  "AirQuality": Number(airQuality.toFixed(0)),
  "PM10Density": pm10,
  "PM2_5Density": pm2,
  "StatusActive": 1
};

A special calculation is applied for the AirQuality value itself. Since the HAP specification doesn’t necessarily dictate how the value has to be calculated it is up to the accessory to determine the subject assessment of the air quality. For the example above the author decide to calculate a mean value derived from the different scale of the PM 10 and PM 2.5 particulate density US AQI scale. For reference please see here.

However, with such a setup you can decide for yourself, whether your windows open/closes automatically based on air quality outside. In addition to that, the project encourages you to become part of the network by building your own air quality sensor.

Edit this page on GitHub