[{"element_type":"markdown","content":"\u003cp\u003eI wanted an air quality monitor I can read from across the room, and I wanted it to look like something from a Fallout vault.\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eAIR PRO\u003c/strong\u003e reads CO2, particles (PM1.0 to PM10), VOC, NOx, temperature and humidity every 2 seconds. It turns all of that into one simple status: OPTIMAL, NORMAL, FAIR, CAUTION, WARNING or CRITICAL. It also tells you the main problem, for example \"MAIN THREAT: CO2, VENTILATE ROOM\".\u003c/p\u003e\n\n\u003cp\u003eEverything runs on CircuitPython. No cloud, no app. WiFi is only used for a few seconds every hour to get the time.\u003c/p\u003e\n\n\u003cp\u003eFull code, parts list and photos: \u003ca href=\"https://github.com/ostgate/air-pro\"\u003egithub.com/ostgate/air-pro\u003c/a\u003e\u003c/p\u003e\n","metadata":{"markdown":"I wanted an air quality monitor I can read from across the room, and I wanted it to look like something from a Fallout vault.\n\n**AIR PRO** reads CO2, particles (PM1.0 to PM10), VOC, NOx, temperature and humidity every 2 seconds. It turns all of that into one simple status: OPTIMAL, NORMAL, FAIR, CAUTION, WARNING or CRITICAL. It also tells you the main problem, for example \"MAIN THREAT: CO2, VENTILATE ROOM\".\n\nEverything runs on CircuitPython. No cloud, no app. WiFi is only used for a few seconds every hour to get the time.\n\nFull code, parts list and photos: [github.com/ostgate/air-pro](https://github.com/ostgate/air-pro)"}},{"element_type":"image_url","content":"https://raw.githubusercontent.com/ostgate/air-pro/main/docs/img/screen-overview.jpg","metadata":{"alt":"Small wooden box with a square screen showing green Pip-Boy style text: air status NORMAL, CO2 952 ppm, temperature 24.0 C, humidity 48.2 percent.","caption":"Environment Overview: overall air status, CO2, temperature, humidity, dew point, PM2.5 and VOC."}},{"element_type":"markdown","content":"\u003ch2\u003eParts\u003c/h2\u003e\n\n\u003cul\u003e\n\u003cli\u003eAdafruit Qualia ESP32-S3 for TTL RGB-666 Displays\u003c/li\u003e\n\u003cli\u003e4\" 720x720 square TFT display (RGB666)\u003c/li\u003e\n\u003cli\u003eSensirion SEN66 (CO2, PM, VOC, NOx, temperature, humidity)\u003c/li\u003e\n\u003cli\u003eAdafruit SHT45 Temperature and Humidity Sensor (STEMMA QT)\u003c/li\u003e\n\u003cli\u003eAdafruit I2C QT Rotary Encoder with seesaw\u003c/li\u003e\n\u003cli\u003eSTEMMA QT / Qwiic cables\u003c/li\u003e\n\u003cli\u003eUSB-C cable and a USB charger\u003c/li\u003e\n\u003cli\u003eA small wooden box (for now)\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eAll sensors and the encoder share one I2C bus through STEMMA QT cables. No soldering needed.\u003c/p\u003e\n\n\u003ch2\u003eBuild\u003c/h2\u003e\n\n\u003cp\u003eThe case is a small wooden box I had at home. The display sits in the door, with the Qualia board on its back. The SHT45 and SEN66 are mounted on the outside wall of the box, so they breathe room air and do not get heated by the board and the backlight. The rotary encoder shaft comes out of the top.\u003c/p\u003e\n","metadata":{"markdown":"## Parts\n\n- Adafruit Qualia ESP32-S3 for TTL RGB-666 Displays\n- 4\" 720x720 square TFT display (RGB666)\n- Sensirion SEN66 (CO2, PM, VOC, NOx, temperature, humidity)\n- Adafruit SHT45 Temperature and Humidity Sensor (STEMMA QT)\n- Adafruit I2C QT Rotary Encoder with seesaw\n- STEMMA QT / Qwiic cables\n- USB-C cable and a USB charger\n- A small wooden box (for now)\n\nAll sensors and the encoder share one I2C bus through STEMMA QT cables. No soldering needed.\n\n## Build\n\nThe case is a small wooden box I had at home. The display sits in the door, with the Qualia board on its back. The SHT45 and SEN66 are mounted on the outside wall of the box, so they breathe room air and do not get heated by the board and the backlight. The rotary encoder shaft comes out of the top."}},{"element_type":"image_url","content":"https://raw.githubusercontent.com/ostgate/air-pro/main/docs/img/inside-open.jpg","metadata":{"alt":"Wooden box opened like a book. SHT45 and SEN66 on the left wall, Qualia S3 board taped to the back of the display in the door, colored STEMMA QT cables inside.","caption":"Inside: sensors on the left wall, Qualia S3 behind the display in the door."}},{"element_type":"image_url","content":"https://raw.githubusercontent.com/ostgate/air-pro/main/docs/img/side-sensors.jpg","metadata":{"alt":"Side of the closed wooden box with the SHT45 board and the black SEN66 sensor mounted outside, a brass latch, and the knob shaft on top.","caption":"SHT45 and SEN66 sit outside the box, away from the warm board."}},{"element_type":"markdown","content":"\u003ch2\u003eScreens\u003c/h2\u003e\n\n\u003cp\u003eTurn the knob to switch screens. Press it for night mode (dim colors and backlight off).\u003c/p\u003e\n","metadata":{"markdown":"## Screens\n\nTurn the knob to switch screens. Press it for night mode (dim colors and backlight off)."}},{"element_type":"image_url","content":"https://raw.githubusercontent.com/ostgate/air-pro/main/docs/img/screen-analysis.jpg","metadata":{"alt":"Air Quality Analysis screen with temperature, humidity, PM air quality, CO2, VOC, NOx, dew point, PM mass and particle counts.","caption":"Air Quality Analysis: every reading on one page, including PM mass for 4 sizes and particle counts for 5 sizes."}},{"element_type":"image_url","content":"https://raw.githubusercontent.com/ostgate/air-pro/main/docs/img/screen-co2.jpg","metadata":{"alt":"CO2 Monitoring Station screen with a huge 946 ppm number, temperature 23.9 C and humidity 48.0 percent.","caption":"CO2 Monitoring Station: a huge CO2 number you can read from far away."}},{"element_type":"image_url","content":"https://raw.githubusercontent.com/ostgate/air-pro/main/docs/img/screen-health.jpg","metadata":{"alt":"System Health screen listing WiFi/NTP SYNCED, SHT4X FRESH, SEN66 core FRESH, SEN66 particle count FRESH, uptime and free memory.","caption":"System Health: sensor status and data age, WiFi/NTP state, uptime and free memory."}},{"element_type":"markdown","content":"\u003ch2\u003eTwo gotchas I hit\u003c/h2\u003e\n\n\u003cp\u003e\u003cstrong\u003e1. The display needs its own I2C bus during init.\u003c/strong\u003e\nOn the Qualia S3 the display init sequence is sent through the I2C IO expander, and it wants its own bus. So the code releases the shared board I2C first, sends the init sequence on a temporary bus, and only then starts the sensors on the shared bus again.\u003c/p\u003e\n","metadata":{"markdown":"## Two gotchas I hit\n\n**1. The display needs its own I2C bus during init.**\nOn the Qualia S3 the display init sequence is sent through the I2C IO expander, and it wants its own bus. So the code releases the shared board I2C first, sends the init sequence on a temporary bus, and only then starts the sensors on the shared bus again."}},{"element_type":"code","content":"board.I2C().deinit()\ndisplayio.release_displays()\nwith busio.I2C(board.SCL, board.SDA) as i2c:\n    dotclockframebuffer.ioexpander_send_init_sequence(\n        i2c, bytes(), **dict(board.TFT_IO_EXPANDER)\n    )\nfb = dotclockframebuffer.DotClockFramebuffer(**board.TFT_PINS, **TFT_TIMING)\ndisplay = FramebufferDisplay(fb, auto_refresh=False)\n# now board.I2C() can be used for the sensors","metadata":{"language":"python","filename":""}},{"element_type":"markdown","content":"\u003cp\u003e\u003cstrong\u003e2. WiFi makes the screen flicker.\u003c/strong\u003e\nThe ESP32-S3 WiFi radio and the DotClock framebuffer compete for memory bandwidth (DMA), and the screen flickers. So the code turns WiFi on, syncs the clock with NTP, and turns it off again right away:\u003c/p\u003e\n","metadata":{"markdown":"**2. WiFi makes the screen flicker.**\nThe ESP32-S3 WiFi radio and the DotClock framebuffer compete for memory bandwidth (DMA), and the screen flickers. So the code turns WiFi on, syncs the clock with NTP, and turns it off again right away:"}},{"element_type":"code","content":"wifi.radio.enabled = True\nwifi.radio.connect(ssid, pwd)\n# ... NTP request ...\nwifi.radio.enabled = False","metadata":{"language":"python","filename":"wifi_sync.py","linenums":false}},{"element_type":"markdown","content":"\u003cp\u003eThe clock syncs once an hour. If a sync fails it tries again after 5 minutes.\u003c/p\u003e\n\n\u003ch2\u003eCode\u003c/h2\u003e\n\n\u003cp\u003eThe code is split into small files: \u003ccode class=\"inline\"\u003ehardware.py\u003c/code\u003e (display, I2C, backlight, encoder), \u003ccode class=\"inline\"\u003esensors.py\u003c/code\u003e, \u003ccode class=\"inline\"\u003eui.py\u003c/code\u003e, \u003ccode class=\"inline\"\u003ewifi_sync.py\u003c/code\u003e and \u003ccode class=\"inline\"\u003econfig.py\u003c/code\u003e. All colors, limits and timings live in \u003ccode class=\"inline\"\u003econfig.py\u003c/code\u003e, so it is easy to change the thresholds or the time zone.\u003c/p\u003e\n\n\u003cp\u003eFull code, parts list and photos: \u003ca href=\"https://github.com/ostgate/air-pro\"\u003egithub.com/ostgate/air-pro\u003c/a\u003e\u003c/p\u003e\n\n\u003ch2\u003eWhat is next\u003c/h2\u003e\n\n\u003cp\u003eThe wooden box works, but the display is held with foam tape. Next step is a 3D printed case with:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003ea proper frame for the display\u003c/li\u003e\n\u003cli\u003ea separate sensor bay with vents, away from the board heat\u003c/li\u003e\n\u003cli\u003ea clear air path for the SEN66 fan\u003c/li\u003e\n\u003cli\u003ea real knob cap and a USB-C cutout\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eThanks to Adafruit for the Qualia board and the CircuitPython libraries, including the new \u003ccode class=\"inline\"\u003eadafruit_sen6x\u003c/code\u003e driver.\u003c/p\u003e\n","metadata":{"markdown":"The clock syncs once an hour. If a sync fails it tries again after 5 minutes.\n\n## Code\n\nThe code is split into small files: `hardware.py` (display, I2C, backlight, encoder), `sensors.py`, `ui.py`, `wifi_sync.py` and `config.py`. All colors, limits and timings live in `config.py`, so it is easy to change the thresholds or the time zone.\n\nFull code, parts list and photos: [github.com/ostgate/air-pro](https://github.com/ostgate/air-pro)\n\n## What is next\n\nThe wooden box works, but the display is held with foam tape. Next step is a 3D printed case with:\n\n- a proper frame for the display\n- a separate sensor bay with vents, away from the board heat\n- a clear air path for the SEN66 fan\n- a real knob cap and a USB-C cutout\n\nThanks to Adafruit for the Qualia board and the CircuitPython libraries, including the new `adafruit_sen6x` driver."}}]