If you're building an air quality monitoring app, integrating IoT sensors, or designing smart building dashboards - you've probably come across the term HEPA filter. But do you know what's actually happening at the physics level? And more importantly, how does it affect the data your sensors report?
This article breaks down HEPA filter mechanics from a developer's perspective, shows you how to read PM2.5 sensor data in Python, and explains why commercial-grade HEPA systems (like those inside modern automatic hand dryers) are becoming a data source worth integrating into your indoor air quality apps.
What Is a HEPA Filter, Really?
HEPA stands for High-Efficiency Particulate Air. It's not a brand - it's a performance standard.
To qualify as a true HEPA filter, a filtration medium must capture 99.97% of particles at 0.3 micrometers (µm) in diameter. Why 0.3µm specifically? Because that's the Most Penetrating Particle Size (MPPS) - the size that's hardest to trap.
Here's the counterintuitive part: particles larger than 0.3µm are actually easier to catch (they get stuck by inertia or interception), and particles smaller than 0.3µm are also easier to catch (they move erratically via Brownian motion and collide with filter fibers). The 0.3µm particle is the "Goldilocks problem" - it's the hardest adversary.
Particle capture mechanisms in HEPA:
Size > 1µm → Inertial Impaction (particle slams into fiber)
Size ~0.3µm → Interception (particle grazes fiber edge) ← MPPS
Size < 0.1µm → Diffusion (Brownian motion causes collision)
The Particle Size Spectrum - What Your Sensor Is Actually Measuring
Before writing a single line of sensor code, you need to understand what you're measuring:
Most low-cost sensors (PMS5003, SDS011, Plantower series) report PM1.0, PM2.5, and PM10. A HEPA-equipped device theoretically eliminates most of what these sensors measure.
Reading PM2.5 Data in Python - Getting Started
Here's a minimal working example to read from a PMS5003 sensor (one of the most popular sensors used in air quality projects) via UART on a Raspberry Pi:
import serial
import struct
import time
def read_pms5003(port='/dev/ttyS0', baudrate=9600):
"""
Read PM1.0, PM2.5, PM10 from PMS5003 sensor.
Returns dict with particle concentrations in µg/m³
"""
ser = serial.Serial(port, baudrate=baudrate, timeout=2)
# PMS5003 sends 32-byte frames starting with 0x42 0x4D
while True:
byte = ser.read(1)
if byte == b'\x42':
next_byte = ser.read(1)
if next_byte == b'\x4d':
frame = ser.read(30) # remaining 30 bytes
data = parse_pms5003_frame(frame)
return data
def parse_pms5003_frame(frame):
"""Parse 30-byte PMS5003 data frame"""
# Unpack: frame_length, pm1_std, pm25_std, pm10_std,
# pm1_env, pm25_env, pm10_env, ...
values = struct.unpack('>HHHHHHHHHHHHHH', frame[:28])
return {
'pm1_standard': values[1], # µg/m³ standard conditions
'pm25_standard': values[2],
'pm10_standard': values[3],
'pm1_env': values[4], # µg/m³ environmental conditions
'pm25_env': values[5], # ← this is what apps typically display
'pm10_env': values[6],
'particles_03um': values[7], # count per 0.1L air
'particles_05um': values[8],
'particles_10um': values[9],
'particles_25um': values[10],
}
if __name__ == "__main__":
while True:
reading = read_pms5003()
print(f"PM2.5: {reading['pm25_env']} µg/m³")
print(f"PM10: {reading['pm10_env']} µg/m³")
print(f"Particles >0.3µm: {reading['particles_03um']} per 0.1L")
print("---")
time.sleep(5)
Note: Install pyserial via pip install pyserial. Make sure your user is in the dialout group on Linux: sudo usermod -a -G dialout $USER
Converting Raw Readings to AQI
Raw µg/m³ values are hard for end users to interpret. You'll want to convert them to an Air Quality Index (AQI) score. Here's a Python implementation of the US EPA AQI breakpoints for PM2.5:
def pm25_to_aqi(pm25: float) -> dict:
"""
Convert PM2.5 concentration (µg/m³) to AQI score.
Based on US EPA 2024 breakpoints.
Returns: {'aqi': int, 'category': str, 'color': str}
"""
breakpoints = [
# (AQI_low, AQI_high, PM25_low, PM25_high, category, color)
(0, 50, 0.0, 9.0, "Good", "#00e400"),
(51, 100, 9.1, 35.4, "Moderate", "#ffff00"),
(101, 150, 35.5, 55.4, "Unhealthy for Some", "#ff7e00"),
(151, 200, 55.5, 125.4, "Unhealthy", "#ff0000"),
(201, 300, 125.5, 225.4,"Very Unhealthy", "#8f3f97"),
(301, 500, 225.5, 500.4,"Hazardous", "#7e0023"),
]
for aqi_lo, aqi_hi, pm_lo, pm_hi, category, color in breakpoints:
if pm_lo <= pm25 <= pm_hi:
# Linear interpolation
aqi = ((aqi_hi - aqi_lo) / (pm_hi - pm_lo)) * (pm25 - pm_lo) + aqi_lo
return {
'aqi': round(aqi),
'category': category,
'color': color,
'pm25': pm25
}
return {'aqi': 500, 'category': 'Hazardous', 'color': '#7e0023', 'pm25': pm25}
# Example usage
result = pm25_to_aqi(18.3)
print(result)
# → {'aqi': 63, 'category': 'Moderate', 'color': '#ffff00', 'pm25': 18.3}
Where HEPA-Equipped Devices Fit into Your Architecture
Here's where it gets interesting for app developers: modern commercial washroom equipment ships with built-in HEPA filtration. Automatic hand dryers like the Hyginest TurboDry Series include HEPA filters to prevent recirculating bathroom bacteria while drying hands.
In a smart building context, this means every hand dryer activation is a micro air-filtration event. If you're building an indoor air quality dashboard for offices, hospitals, or commercial spaces, these devices become passive air quality contributors.
A simple architecture for a smart washroom air quality monitor:
import paho.mqtt.client as mqtt
import json
import time
# Publish air quality readings to MQTT broker
def publish_air_quality(sensor_data: dict, location: str = "washroom-floor2"):
client = mqtt.Client()
client.connect("your-mqtt-broker.local", 1883, 60)
aqi_data = pm25_to_aqi(sensor_data['pm25_env'])
payload = {
"location": location,
"timestamp": time.time(),
"pm25": sensor_data['pm25_env'],
"pm10": sensor_data['pm10_env'],
"aqi": aqi_data['aqi'],
"category": aqi_data['category'],
"hepa_device_nearby": True # flag if HEPA hand dryer installed
}
client.publish(f"airquality/{location}", json.dumps(payload))
client.disconnect()
print(f"Published: AQI {aqi_data['aqi']} ({aqi_data['category']})")
Install MQTT client: pip install paho-mqtt
Using the World Air Quality Index (WAQI) API
If you don't have hardware sensors, you can still build air quality apps using public APIs. The WAQI API gives you real-time AQI data for 100+ countries:
import requests
def get_city_aqi(city: str, api_token: str) -> dict:
"""
Fetch real-time AQI data for a city using WAQI API.
Get free token at: https://aqicn.org/data-platform/token/
"""
url = f"https://api.waqi.info/feed/{city}/?token={api_token}"
response = requests.get(url)
data = response.json()
if data['status'] == 'ok':
iaqi = data['data']['iaqi']
return {
'city': city,
'aqi': data['data']['aqi'],
'pm25': iaqi.get('pm25', {}).get('v', 'N/A'),
'pm10': iaqi.get('pm10', {}).get('v', 'N/A'),
'temperature': iaqi.get('t', {}).get('v', 'N/A'),
'humidity': iaqi.get('h', {}).get('v', 'N/A'),
'dominant_pollutant': data['data'].get('dominentpol', 'N/A'),
'station': data['data']['city']['name'],
}
return {'error': data.get('data', 'API error')}
# Example
token = "your-api-token-here"
ahmedabad_air = get_city_aqi("ahmedabad", token)
print(ahmedabad_air)
# → {'city': 'ahmedabad', 'aqi': 142, 'pm25': 58.3, ...}
Key Takeaways for Developers
- Understand the physics: HEPA's 99.97% efficiency at 0.3µm means it's even better at capturing the PM2.5 particles your sensors measure. A room with active HEPA filtration will show measurably lower readings.
- Use the right sensor metric: Always use pm25_env (environmental) over pm25_standard for real-world indoor monitoring. Standard values assume fixed atmospheric conditions.
- Account for HEPA devices in your data model: In commercial buildings, HEPA-equipped devices (air purifiers, certain hand dryers) act as continuous passive filters. Flag their presence in your location metadata.
- AQI ≠ PM2.5: Always convert µg/m³ to AQI for user-facing displays. Raw concentrations confuse non-technical users.
- Sensor placement matters: Place sensors at breathing height (1.2–1.5m) and away from direct airflow from HEPA devices - else your readings will look artificially clean directly in front of the device.


Top comments (0)