{"id":49255913,"url":"https://github.com/mmbytes/multigeiger-v2","last_synced_at":"2026-06-13T08:02:10.056Z","repository":{"id":353043192,"uuid":"1217712300","full_name":"MMBytes/MultiGeiger-V2","owner":"MMBytes","description":"Rework of the https://github.com/ecocurious2/MultiGeiger/ codebase, directly on the latest ESP-IDF","archived":false,"fork":false,"pushed_at":"2026-05-24T03:21:23.000Z","size":2671,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T05:14:33.741Z","etag":null,"topics":["esp-idf","esp32","geiger-counter","radiation-sensor","sensor-community","si22g"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MMBytes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T06:29:22.000Z","updated_at":"2026-05-24T03:21:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/MMBytes/MultiGeiger-V2","commit_stats":null,"previous_names":["mmbytes/multigeiger-v2"],"tags_count":69,"template":false,"template_full_name":null,"purl":"pkg:github/MMBytes/MultiGeiger-V2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MMBytes%2FMultiGeiger-V2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MMBytes%2FMultiGeiger-V2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MMBytes%2FMultiGeiger-V2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MMBytes%2FMultiGeiger-V2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MMBytes","download_url":"https://codeload.github.com/MMBytes/MultiGeiger-V2/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MMBytes%2FMultiGeiger-V2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33686018,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["esp-idf","esp32","geiger-counter","radiation-sensor","sensor-community","si22g"],"created_at":"2026-04-25T03:52:06.233Z","updated_at":"2026-05-30T09:01:19.211Z","avatar_url":"https://github.com/MMBytes.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MultiGeiger V2 (ESP-IDF native)\n\nA ground-up C rewrite of the [MultiGeiger](https://github.com/ecocurious2/MultiGeiger) radiation sensor firmware, ported from Arduino / PlatformIO to **native ESP-IDF 6.0**. Runs on four ESP32 / ESP32-S3 board variants with a wide selection of optional environmental, particulate, noise, and ambient-light sensors. Uploads to five public back-ends and publishes to MQTT (with Home Assistant Discovery) and remote syslog.\n\nSee the [releases page](https://github.com/MMBytes/MultiGeiger-V2/releases) for the latest build and per-release notes.\n\n## What it does\n\n- Counts Geiger pulses from a Si22G tube (or any tube — calibration constant configurable), computes CPM and µSv/h.\n- Drives the HV flyback boost converter from on-chip GPIO timing (no Arduino dependency).\n- Optional **environmental sensing**: temperature, humidity, pressure, sea-level-adjusted pressure.\n- Optional **PM (particulate matter)**: PM1 / PM2.5 / PM4 / PM10 plus number concentrations and typical particle size.\n- Optional **noise**: LAeq / LAmin / LAmax dB(A).\n- Optional **ambient light**: lux (two sensor families supported).\n- Per-cycle upload (default 150 s, configurable 10 s – 1 h) to any subset of five public back-ends.\n- **MQTT publish** with 24 Home Assistant Discovery entities, three TLS modes including custom CA.\n- **Remote syslog** (UDP 514) for centralised log aggregation.\n- **Hourly FTP/FTPS** upload of the on-device log ring buffer.\n- **Web UI** at `http://\u003cdevice\u003e/` — `/config` (settings), `/status` (live metrics + per-target TX stats + per-sensor presence), `/update` (OTA), `/log` (in-memory log ring).\n- **Crash recovery**: ESP-IDF panic handler writes a coredump to a dedicated flash partition; downloadable post-reboot via `GET /coredump.elf` (no USB required).\n- **OLED status display** (SSD1306 128×64, also supports SparkFun SerLCD I²C and NeoPixel tick on supported boards).\n- **NTP** with up to three configurable servers and POSIX TZ string.\n\n## Supported hardware\n\n### Boards (five build targets)\n\n| Build target | MCU / module | Flash | Notes |\n|---|---|---|---|\n| `heltec_v2` | Heltec WiFi Kit 32 V2 (ESP32-D0WDQ6) | 8 MB | Onboard SSD1306 OLED. The original target — most production deployments. |\n| `heltec_v2_4mb` | Heltec WiFi Kit 32 V2 clone | 4 MB | Same module silicon, smaller flash. Tight on heap during OTA — see V2.4.13 teardown logic in `main.c`. |\n| `feathers3_d` | Unexpected Maker FeatherS3 with display (ESP32-S3) | 8 MB | Two STEMMA QT connectors (STEMMA1 on IO8/IO9, STEMMA2 LDO-gated on IO15/IO16). External I²C OLED via STEMMA. |\n| `adafruit_qtpy_esp32_pico` | Adafruit QT Py ESP32-PICO | 8 MB | Compact form factor. Optional NeoPixel tick on pulse. |\n| `seeed_xiao_esp32s3` | Seeed Studio XIAO ESP32-S3 | 8 MB | Tiny 21×17.5 mm — shares QT Py form factor + Geiger pin map (A0 / A1 / SCK), so one PCB design works for both. Also fine as an I²C-only sensor host with no tube wired. |\n\nBuild/flash invocation takes a board argument — see `_build.cmd` / `_flash.cmd` / `_merge.cmd` helpers. All boards share the same `main/` source tree; differences are isolated in per-board `sdkconfig.defaults.\u003cboard\u003e` and HAL pin map.\n\n### Geiger tube\n\n- **Si22G** pancake tube (default calibration: `µSv/h = cps / 12.2792`, empirical vs. odlinfo.bfs.de reference).\n- Other tubes are supported by changing the calibration constant — the firmware doesn't care which tube produces the pulses.\n\n### Environmental sensors (auto-detected on I²C)\n\n| Sensor | Address | Provides | Notes |\n|---|---|---|---|\n| **SHT45** | 0x44 | Temperature, humidity | Sensirion successor to SHT3x. PTFE filter variant supported. |\n| **BME280** | 0x76 / 0x77 | Temperature, humidity, pressure | Bosch — original upstream sensor. |\n| **BME688** | 0x76 / 0x77 | Temperature, humidity, pressure | Bosch — gas channel currently unused. |\n| **BMP390** | 0x76 / 0x77 | Temperature, pressure | Bosch — high-accuracy barometric. |\n| **BMP581** | 0x46 / 0x47 | Temperature, pressure | Bosch latest-gen — sub-Pa noise floor. |\n\nThe driver mix lets a node combine, e.g., SHT45 (best RH) + BMP581 (best pressure) on the same bus. The firmware picks the right reading per measurand from whichever sensors responded at boot.\n\n### Specialty sensors (auto-detected on I²C)\n\n| Sensor | Address | Provides | Notes |\n|---|---|---|---|\n| **Sensirion SPS30** | 0x69 | PM1 / PM2.5 / PM4 / PM10 mass, N05–N10 number concentrations, typical particle size | Optional. Adds the SPS30_* field group to all upload targets. |\n| **hbitter DNMS** | 0x55 | LAeq / LAmin / LAmax dB(A) | Nettigo NAM-style Teensy-based noise module. Adds DNMS_noise_* fields. |\n| **ALS-PT19** | (analog) | Ambient light (lux) | Onboard FeatherS3-D photodiode on ADC1_CH3. |\n| **VEML7700** | 0x10 | Ambient light (lux) | Vishay I²C, alternative to ALS-PT19. |\n\n### Displays\n\n- **SSD1306 OLED** 128×64, I²C 0x3C or 0x3D — auto-probe both addresses (V2.4.19).\n- **SparkFun SerLCD** (I²C variant) — alternative line-based display.\n- **NeoPixel** tick-per-pulse on supported boards (configurable colour / brightness via `display_mode`).\n- Built-in **speaker** click on pulse (configurable, off by default for sealed-tube installs).\n\n## Upload targets\n\nAll five targets are fully optional and independently configurable. Each has per-target circuit-breaker logic (V2.3.x) — 3 consecutive all-retry failures suspend that target for 20 cycles to prevent failed TLS handshakes from fragmenting heap.\n\n| Target | Endpoint | Auth | Fields posted |\n|---|---|---|---|\n| **Madavi** | `api-rrd.madavi.de/data.php` (HTTP / HTTPS) | none | T/H/P; PM and noise are POSTed but the legacy server only graphs T/H/P |\n| **sensor.community** | `api.sensor.community/v1/push-sensor-data/` (HTTP / HTTPS) | none, X-Sensor = chip ID | Radiation (X-PIN 19), T/H/P (11), PM (1), noise (15) — split bodies per pin |\n| **Radmon** | `radmon.org/radmon.php?function=submit` (HTTP / HTTPS) | basic auth user/pw | Radiation CPM only |\n| **openSenseMap** | `ingress.opensensemap.org/boxes/\u003cBOX_ID\u003e/data?luftdaten=1` (HTTPS) | Box ID in URL + optional raw access token in Authorization header (no `Bearer ` prefix) | Combined Luftdaten body |\n| **aqi.eco** | `api.aqi.eco/update/\u003cTOKEN\u003e` (HTTPS) | token in URL | Combined Luftdaten body wrapped with `esp8266id` field |\n\nEach target shows live per-target stats on `/status` — attempted / succeeded counters, last HTTP status, last attempt time, current breaker state.\n\nCert verification uses the Mozilla CA bundle baked into flash. Per-target \"insecure mode\" available for self-hosted instances. TLS 1.2 and TLS 1.3 both supported.\n\n## Observability\n\n### MQTT (V2.4.2–V2.4.6)\n\nPublish-only MQTT 3.1.1 client with three TLS modes:\n\n- **Mode 0** — plain TCP (broker on LAN)\n- **Mode 1** — TLS via Mozilla CA bundle (public broker)\n- **Mode 2** — TLS via user-supplied custom CA PEM (self-signed broker, e.g. a Pi running Mosquitto on the LAN)\n\nWhen `mqtt_ha_discovery` is enabled (default), the firmware publishes 24 Home Assistant Discovery entities at boot — radiation, all detected sensors, WiFi RSSI, per-target TX stats, etc. — auto-gated on driver presence. HA picks them up without manual configuration.\n\nTopic layout: `\u003cprefix\u003e/\u003cchip-id\u003e/state` (full state JSON every cycle) plus `\u003cprefix\u003e/\u003cchip-id\u003e/availability` for the LWT.\n\n### Syslog (V2.4.15)\n\nUDP syslog client (port 514, configurable). RFC 3164 BSD-format messages with `\u003c13\u003e` facility/severity. Every applog line mirrors to syslog when enabled. Useful for centralised log aggregation across a fleet — point all nodes at the same rsyslog server on your network.\n\n### FTP / FTPS log upload (V2.0.x +)\n\nHourly (configurable 1 min – 24 h) upload of the on-device log ring buffer to an FTP server. TLS supported (FTPS explicit-mode, AUTH TLS, TLS 1.2 and 1.3 both work). Cleared after successful upload; carried over across reboot via NVS.\n\n### Coredump on panic (V2.4.18)\n\n64 KB dedicated coredump partition. The ESP-IDF panic handler writes the panicking task's register state + stack snapshot before reset. Recoverable post-reboot via `GET /coredump.elf` (basic auth gated) — no USB needed once flashed. Decode with `espcoredump.py`.\n\n## Security\n\nV2.3.33 web security audit + V2.4.x follow-ups (see CHANGELOG):\n\n- **Basic auth** on `/config`, `/update`, `/reboot`, `/coredump.elf` (user `admin`, password = configured AP password).\n- **Constant-time password comparison** to defeat timing oracles.\n- **CSRF protection**: POSTs to `/config` / `/update` / `/reboot` require an `Origin` header matching the device's URL — protects against drive-by browser POSTs. **Note for curl users**: add `-H \"Origin: http://device:port\"` or you'll get a 403.\n- **OTA size clamp**: rejects images larger than the OTA partition (2 MB) before writing.\n- **X-Frame-Options: DENY** on all responses.\n- **TX is paused during OTA** (V2.4.24): scheduled cycles skip while an OTA upload is in progress, giving the OTA the full WiFi airtime.\n\nDeferred for future work: HTTPS for the device UI itself, signed OTA images, NVS encryption, rate limiting.\n\n## Installation\n\n### Flashing a prebuilt release (no build environment needed)\n\nEvery [release](https://github.com/MMBytes/MultiGeiger-V2/releases) attaches per-board artefacts. Pick the bundle that matches your board — see the release notes for the file naming.\n\nInstall [`esptool`](https://github.com/espressif/esptool) first (`pip install esptool`); replace `COM3` with your actual serial port.\n\n**First flash of a blank / fresh / bricked device — use `geiger_v2_merged_\u003cboard\u003e-\u003cversion\u003e.bin`:**\n\n```\nesptool --chip esp32 --port COM3 write-flash 0x0 geiger_v2_merged_heltec_v2-\u003cversion\u003e.bin\n```\n\n(For ESP32-S3 boards substitute `--chip esp32s3`. FeatherS3-D additionally needs `--before no-reset --after no-reset` and the BOOT+RST button dance.)\n\nThe merged image bundles the bootloader, partition table, OTA slot pointer, and the app at their correct flash offsets — a single-file factory flash.\n\n**Upgrading a device already running this firmware:**\n\n- **OTA (recommended)** — browse to `http://\u003cdevice-ip\u003e/update`, log in (`admin` / your configured AP password), pick the `geiger_v2.bin` artefact for your board. No cable, keeps your NVS (WiFi credentials, MQTT broker, etc.) intact.\n\n### First boot\n\nThe device comes up as an open WiFi AP named after its chip ID (derived from the MAC). Connect to it, browse to `http://192.168.4.1/config`, and set WiFi credentials, admin password, and back-end choices. After the 2-minute boot window or a manual reboot, it joins your network in STA mode.\n\n## Build from source\n\nNeeded only if you want to modify the firmware.\n\n### Requirements\n\n- [ESP-IDF v6.0](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/index.html) — pure IDF, not arduino-esp32, not PlatformIO\n- A Windows / Linux / macOS host with the IDF tools installed\n- USB cable with data lines\n\n### Commands\n\nWith the IDF environment sourced (`export.ps1` on Windows, `export.sh` on Linux/macOS):\n\n```\nidf.py -B build_heltec_v2 -D SDKCONFIG_DEFAULTS=sdkconfig.defaults.heltec_v2 build\nidf.py -B build_heltec_v2 -p \u003cPORT\u003e flash monitor\n```\n\nSubstitute `heltec_v2_4mb`, `feathers3_d`, or `adafruit_qtpy_esp32_pico` for other boards. Per-board build/cache directories prevent cross-board sdkconfig pollution.\n\nThe repo includes `_build.cmd \u003cboard\u003e`, `_flash.cmd \u003cboard\u003e`, `_merge.cmd \u003cboard\u003e` helpers that wrap the above.\n\n### Release workflow\n\n`git push --tags V2.X.Y` is the entire release ceremony — GitHub Actions `release.yml` builds all four boards in parallel and creates the GitHub Release with bundled artefacts + CHANGELOG body. Manual fallback documented in `_merge.cmd`.\n\n## Repository layout\n\n```\nmain/                       firmware C sources\n  main.c                    entry point, WiFi state machine, cycle loop\n  tube.c                    pulse ISR + HV driver timing\n  config.c / config_fields.def\n                            NVS-backed config, X-macro schema\n  http_server.c             web UI (/, /config, /status, /update, /log, /coredump.elf)\n  transmission.c            Madavi / sensor.community / Radmon / OSM / aqi.eco uploads\n  mqtt.c / mqtt_discovery.c MQTT 3.1.1 publish + HA Discovery\n  syslog.c                  RFC 3164 UDP syslog client\n  log_ftp.c                 hourly FTP/FTPS log upload\n  net_arp.c                 gratuitous ARP (mesh-AP blackhole prevention)\n  periodic.c                housekeeping (PSA refresh + ARP safety-net)\n  coredump.c                /coredump.elf streaming from partition\n  ntp.c                     SNTP with configurable POSIX TZ\n  env_sensor.c              umbrella for T/H/P drivers\n  sht45.c bmp581.c bmp390.c bme688.c bme280.c\n                            individual T/H/P drivers\n  pm_sensor.c sps30.c       particulate matter\n  noise_sensor.c dnms.c     noise (DNMS / NAM)\n  als.c veml7700.c          ambient light\n  display.c display_serlcd.c neopixel.c\n                            display drivers\n  speaker.c                 pulse tick + LED\n  i2c_bus.c                 shared I²C bus handle + mutex\n  applog.c                  in-memory log ring buffer\npartitions.csv              factory + dual-OTA (2 MB each) + coredump (64 KB) on 8 MB flash\npartitions_4mb.csv          tighter layout for heltec_v2_4mb\nsdkconfig.defaults.\u003cboard\u003e  per-board IDF configuration\nCHANGELOG.md                per-release WHAT/WHY notes\n\nHardware/                   PCB design files (KiCad)\n  Revision_B/               current production PCB\n    geiger.kicad_pro / .kicad_pcb / .kicad_sch\n                            KiCad 8 project sources\n    0_Custom_Library.pretty / geiger.pretty\n                            custom symbol + footprint libraries\n    3d-Files/               3D STEP models for non-trivial parts\n    Deliverables/\n      Gerber Files/         fab-ready Gerbers\n      Pick And Place Files/ assembly CSVs\n      BOM/                  bill of materials\n      Schematic/            schematic PDF\n      Renderings/           3D rendered previews\n      Supporting Files/     STEP export\n```\n\n## PCB design files\n\nThe current production PCB (Revision B) is included in the [`Hardware/Revision_B/`](Hardware/Revision_B/) directory as a complete KiCad 8 project, plus fabrication-ready Gerbers, pick-and-place CSVs, schematic PDF, BOM, and 3D STEP exports under `Deliverables/`.\n\n- **Browse the schematic**: [`Hardware/Revision_B/Deliverables/Schematic/geiger.pdf`](Hardware/Revision_B/Deliverables/Schematic/geiger.pdf)\n- **3D renderings**: [`Hardware/Revision_B/Deliverables/Renderings/`](Hardware/Revision_B/Deliverables/Renderings/)\n- **Re-fabricate at JLCPCB or similar**: upload the Gerbers + PnP CSV from `Deliverables/` directly, or open the `.kicad_pro` to regenerate\n- **License**: same GPL-3.0-or-later as the firmware\n\nThe PCB hosts a FeatherS3-D module. Si22G tube; full BOM uses metal-film resistors and polypropylene HV capacitors throughout — see the BOM CSV and schematic for the parts list.\n\n## Documentation\n\n- **Per-release notes**: [`CHANGELOG.md`](CHANGELOG.md) — full WHAT/WHY for every release since V2.3.23, headlines for older. GitHub release pages have the rest.\n- **In-firmware**: `/status` shows live state including which drivers responded at boot, per-target TX stats, FTP / MQTT / syslog status.\n\n## Attribution\n\nThis firmware is an **independent rewrite** of [MultiGeiger](https://github.com/ecocurious2/MultiGeiger) by the ecocurious2 project. Hardware design, sensor-community protocol compatibility, the Si22G calibration constant, and the upload-target conventions come from upstream; the C source in this repository shares no commits with it.\n\nThe MQTT / HA Discovery, MQTT TLS modes, openSenseMap support, aqi.eco support, syslog, coredump-to-flash, multi-board build (FeatherS3 / QT Py), and most of the additional sensor drivers (SHT45, BMP581, BMP390, BME688, SPS30, DNMS, ALS-PT19, VEML7700) are net-new in V2.\n\n## License\n\n**GPL-3.0-or-later** — same as upstream MultiGeiger. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmbytes%2Fmultigeiger-v2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmmbytes%2Fmultigeiger-v2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmbytes%2Fmultigeiger-v2/lists"}