Loading files…
DIY multi-sensor environmental monitor for indoor spaces.
Measures air quality and presence; mounts on a standard wall-recessed electrical box; runs ESPHome and integrates natively with Home Assistant.
https://github.com/user-attachments/assets/d60150c3-3ed5-4fff-b437-b9b54c48e413
v0.54 — manufactured, bench-validated and in use. Boards of this revision were fabricated and SMT-assembled at JLCPCB, brought up on the bench with a repeatable per-unit check, and run in a multi-unit deployment as the sensor input for Home Assistant HVAC automations. Working on the delivered hardware: the 24 V power chain, the Sensirion SEN66 (CO₂ / PM / VOC / NOx / T / RH, with Sensirion's STAR temperature compensation running inside the module), the HLK-LD2410C presence radar including stillness detection, the LED ring, Wi-Fi / OTA / native Home Assistant API, the Bluetooth proxy and the on-device web dashboard. The firmware reports , matching the version printed on the silkscreen. The design is fully script-generated and verified: 35/35 stages PASS, DRC 0 violations / 0 unconnected, JLCPCB DFM 0 Danger on both the bare-board and assembly checks. The production files for this revision are attached to the . See for the design rationale, hard constraints and the lessons learned along the way.
project_version: v0.54build.pyYes — this revision has been built and works. What to know before ordering:
hardware/output/jlcpcb/ holds the gerber + drill bundle, the BOM and the CPL files for JLCPCB SMT assembly (also attached to the GitHub release); they are regenerated and verified by the 35-stage pipeline on every build. Follow the ordering workflow in CLAUDE.md — in particular the pre-payment Assembly Order cross-check, which has caught wrong-part substitutions more than once.firmware/README.md.Known limitations (findings on the built units):
firmware/esphome/packages/presence.yaml.firmware/esphome/HARDWARE-COMPAT-v0.51.md; v0.54 boards run the firmware defaults.A consolidated step-by-step build guide is not written yet; open work is tracked in the TODO list in CLAUDE.md.
The open-source DIY space offers many indoor-air-quality projects. Most optimise aggressively for cost. OAS holds two priorities non-negotiable, even when the trade-off is a higher per-unit BOM than the cheapest alternatives:
Both compromises (cheap-but-inaccurate, accurate-but-ugly) are rejected. See CLAUDE.md for the full rationale and the hard constraints any change must clear.
| Function | Component | Interface |
|---|---|---|
| MCU | ESP32-C6-DevKitM-1-N4 (EAN 5904422385651) | 2× USB-C on module |
| Air quality combo | Sensirion SEN66-SIN-T | I²C (JST GH 6-pin cable) |
| Presence | HiLink HLK-LD2410C | UART @ 256000 baud |
| Visual indicator | 7× SK6812-SIDE side-emit ring (Ø26 mm base pitch, 8 slots with D13 vacated for J1) | 1-wire WS281x on GPIO 8 |
| Power input | 24 V DC terminal block + TVS + PTC + reverse-polarity P-FET | — |
Enclosure: SZOMK AK-N-94 (Ø128 mm perforated white ABS). PCB is a Ø120 mm D-shape with a flat chord along the bottom edge. Mounts on a standard wall-recessed electrical box (60 mm screw pitch).
Authoritative module metadata (EAN, MPN, datasheet URLs, derived dimensions) lives in hardware/kicad/boardgen/_project.py::EXTERNAL_MODULES. Pinout is in boardgen/_project.py::GPIO_ASSIGNMENTS. Full design rationale and hard constraints: CLAUDE.md.
open-ambient-sensor/
├── README.md # this file
├── CLAUDE.md # design rationale, hard constraints, working conventions
├── GPLv3-LICENSE.md
├── .gitignore
├── media/ # promo video (oas-promo.mp4) + README photos (oas-*.webp)
├── firmware/
│ ├── README.md # flashing + Home Assistant integration
│ ├── esphome/
│ │ ├── oas.yaml # top-level ESPHome config
│ │ ├── packages/ # core / leds / air-quality / presence / bt-proxy
│ │ └── examples/ # anonymized per-device override examples
│ ├── tools/ # bringup_check.py — repeatable per-unit bench check
│ └── secrets.yaml.example
└── hardware/
├── kicad/
│ ├── build.py # THE single entrypoint — emits sources + runs the 35-stage pipeline
│ ├── boardgen/ # source of truth — generators for every .kicad_* file
│ ├── pipeline/ # 35 verification/export stages (generic / oas / jlcpcb)
│ ├── tests/ # unit-test suite (pytest; run by stage 16 of build.py)
│ ├── tools/ # manual-trigger scripts (route extract, DFM upload, jlcparts cache)
│ ├── oas_routes.py # routing snapshot (replayed onto the generated PCB)
│ ├── lcsc_mapping.py # SMD BOM — LCSC SKU source of truth
│ └── oas.kicad_* # generated KiCad project files (derived artefacts — never hand-edit)
├── renders/ # generated previews (PNG + SVG, visual changelog)
├── photos/ # photos of built units (EXIF stripped)
└── output/ # production deliverables (ibom + jlcpcb/ ZIP + BOM + 2× CPL)
The KiCad project is fully script-generated. The .kicad_pcb / .kicad_sch / .kicad_pro / library files are derived artefacts — never hand-edit them; the next build overwrites every edit. The source of truth lives in hardware/kicad/boardgen/ (Python generators), lcsc_mapping.py (SMD BOM), and oas_routes.py (routing snapshot).
cd hardware/kicad
python build.py
build.py is the only top-level entrypoint. It regenerates every KiCad source file bit-identically (deterministic v5 UUIDs — an unchanged tree produces an empty git diff) and then runs the full 35-stage verification pipeline: DRC, ERC, determinism self-check, analytical DC / ampacity / boot-strap / thermal / I²C-rise-time checks, ngspice SPICE simulations (buck soft-start, IEC 61000-4-5 surge, reverse polarity), unit tests, renders, and the vendor export stages that produce the JLCPCB deliverables. Any stage failure aborts the build.
Requirements:
kicad-cli on PATHpip install mypy pytest cairosvg pygerbergit submodule update --init (kicad-skip, InteractiveHtmlBom, jlcparts, and other tools under hardware/kicad/third_party/)python hardware/kicad/tools/setup_jlcparts_cache.py (~2 GiB download, expands to a local SQLite cache)ngspice and the vendor SPICE models are downloaded automatically into a gitignored .tmp/ directory on first run. Individual pipeline stages are independently runnable for debugging (e.g. python pipeline/generic/03_drc.py), but a committable state always comes from a full build.py pass.
Contributions are welcome once the design stabilises. Any change must clear both design pillars (measurement quality, aesthetic acceptability) and the hard constraints listed in CLAUDE.md.
This is a public repository — see the public-repository rules for the anonymisation and third-party-IP requirements that apply to every committed file.
GNU General Public License v3.0 — see GPLv3-LICENSE.md.
No comments yet. Be the first to ask about this board.