Skip to content

Repository files navigation

SAMBA IEQ Monitoring System

This repository contains the firmware for SAMBA v2, a low-cost indoor environmental quality (IEQ) monitor developed by the IEQ Lab at The University of Sydney. SAMBA runs on ESPHome, an open-source firmware framework for ESP32 microcontrollers. The minimum supported version of ESPHome is 2026.8.1.

Configuration

ESPHome devices are configured using YAML. This repository is structured so that each sensor or function has its own config file in config/, which are imported as packages by the main samba.yaml.

├── samba.yaml              # main config
├── config/
|   ├── adc.yaml            # analog-to-digital converter
|   ├── airspeed.yaml       # anemometers
|   ├── calibration.yaml    # calibration coefficients
|   ├── co2.yaml            # CO2 sensor
|   ├── diagnostics.yaml    # device diagnostics
|   ├── esp32.yaml          # ESP32 board and framework
|   ├── fileserver.yaml     # SD file server for SAMBA Home
|   ├── globals.yaml        # global variables
|   ├── homeassistant.yaml  # Home Assistant API
|   ├── illuminance.yaml    # illuminance sensor
|   ├── influx.yaml         # InfluxDB connection
|   ├── led.yaml            # status LED
|   ├── ota.yaml            # over-the-air updates
|   ├── pm25.yaml           # PM2.5 sensor
|   ├── rtc.yaml            # real-time clock
|   ├── sample.yaml         # sampling loop
|   ├── sd.yaml             # SD card logging
|   ├── spl.yaml            # sound pressure level
|   ├── substitutions.yaml  # secrets and substitutions
|   ├── tags.yaml           # InfluxDB location tags
|   ├── tair.yaml           # air temperature and RH
|   ├── tglobe.yaml         # globe temperature
|   ├── tvoc.yaml           # TVOC and NOx sensor
|   └── wifi.yaml           # wireless networking
├── components/             # custom ESPHome components
|   ├── influxdb/           # InfluxDB v2 HTTP upload
|   ├── sd_file_server/     # read-only HTTP access to the SD log
|   ├── sd_spi_card/        # SPI SD card read/write
|   ├── senseair_i2c/       # K30/K33 CO2 over I2C
|   └── sound_level_meter/  # I2S audio DSP for SPL
├── firmware/               # compiled binaries for OTA
└── pcb/                    # hardware design files (Altium)

The components/ directory contains five external components that extend ESPHome:

  1. sound_level_meter — audio DSP for sound pressure level (LAeq, LA90, LA10)
  2. senseair_i2c — I2C driver for the K30 CO2 sensor
  3. influxdb — HTTP upload to an InfluxDB v2 bucket
  4. sd_spi_card — FAT32 SD card logging via SPI
  5. sd_file_server — read-only HTTP service over the SD card log for the SAMBA Home app

Sensors

Parameter Sensor Config Component
Temperature / RH Sensirion SHT40 tair.yaml sht4x
Globe Temperature NTC Thermistor tglobe.yaml ntc
Air Speed Thermal Anemometer airspeed.yaml ads1115
CO2 CO2Meter K30 co2.yaml senseair_i2c
PM2.5 Plantower PMS5003T pm25.yaml pmsx003
VOC / NOx Index Sensirion SGP41 tvoc.yaml sgp4x
Illuminance TI OPT3001 illuminance.yaml opt3001
Sound Pressure Level ICS-43434 Microphone spl.yaml sound_level_meter

Most sensors are natively supported by ESPHome. The CO2 sensor and sound pressure level measurement use custom external components in components/.

Anemometer Tips

The remote board has two thermal anemometer tips, one on each face. The firmware numbers them the other way round from the schematic, so identify a tip by the face it is on:

Face Tip Schematic sheet Firmware
RJ45 connector R9 Airspeed 1 as2 (ADS1115 A1)
Opposite face R27 Airspeed 2 as1 (ADS1115 A0)

The published Air Speed is the larger of the two, or the one still reporting if the other has dropped out.

Sampling

SAMBA continuously measures environmental parameters, applies quality filters and a moving median, then publishes a summary every 5 minutes. The routine is:

  1. Each sensor measures at its own frequency (see table below).
  2. Raw readings pass through filters — NaN rejection, clamping, moving median — and calibration functions.
  3. Every 5 minutes, a cron task on the RTC triggers the sensor_sample script.
  4. The script updates all template sensors, then publishes to Home Assistant, InfluxDB, and/or the SD card. The LED flashes white with each upload.
  5. Publishing is skipped during the first 2 minutes after boot (sensor warm-up).
Measure Frequency Filters
Air Temperature 30s clamp; moving median; linear calibration
Relative Humidity 30s clamp; moving median; linear calibration
Globe Temperature 30s clamp; moving median; linear calibration
Air Speed 2s clamp; moving median; exponential calibration with air temperature
CO2 30s filter; clamp; moving median; linear calibration; clamp
PM2.5 ~1s clamp; moving median
VOC Index 30s moving median
NOx Index 30s moving median
Illuminance 20s clamp; moving median; linear calibration; clamp
Sound Pressure Level 125ms sos (mic EQ, A-weighting); sliding 5min window of 125ms levels, sent each minute: energy average (LAeq), 0.1dB histogram quantiles (LA90, LA10)

Data

Published measurements are sent every 5 minutes to one or more of the following backends:

Home Assistant — An open-source home automation platform. Easy to use but requires additional hardware (e.g. Raspberry Pi) and some configuration to retain raw data beyond 10 days. Best suited for projects that also collect other measurements (e.g. energy, window/door state). Communication uses the native ESPHome API component.

InfluxDB — An open-source time series database optimised for IoT. Can be self-hosted or used via InfluxData's cloud service. Requires an active internet connection. Best suited for field deployments of multiple SAMBAs. Communication uses the custom influxdb component with building, level, and zone IDs as tags (set over the native API, see config/tags.yaml). See the InfluxDB key concepts for background.

SD Card — Local CSV logging via the sd_spi_card component. Files are named using the device MAC address and UTC timestamp. No network connection required.

Status LED

A single RGB LED on the board reports device state. One rule covers the whole scheme: colour identifies the subsystem, and pulsing versus solid identifies severity. A slow pulse is a warning the device expects to recover from on its own; a steady light means the fault is sustained. Only the ADS1115 and K30 escalate to a restart, and only after an hour of uptime. Brightness is deliberately low throughout so a rack of units is not distracting in an occupied office.

LED Meaning What to do
Green, flashing Booting Nothing — clears after startup
White, brief flash 5-minute sample taken and uploaded Nothing — this is the healthy heartbeat
Off Running normally Nothing
Amber, pulsing Globe temperature / air speed unresponsive ~3 min Watch — often transient
Amber, solid Same, ~5 min. Restarts once the fault has covered 35% of the last hour (about 25 min continuous) Check the RJ45 cable to the remote board, then reseat both ends
Blue, pulsing CO2 (K30) failed 3 consecutive reads Watch — usually self-recovers
Blue, solid CO2 failed 4 or more; restarts once it has failed 67% of the last hour (about 65 min of near-total failure) Check the K30; a persistent fault is often board-specific rather than the sensor
Magenta, pulsing VOC / NOx (SGP4x) failed 4 consecutive reads Watch — usually self-recovers
Magenta, solid VOC / NOx failed 6 or more. Never restarts: the sensor keeps retrying itself, and a reboot would discard its month-long baseline Check the sensor

The LED is applied from a single 10-second poll of the error counters rather than from each sensor's error handler, so it survives the sample heartbeat and returns to off within about ten seconds of the sensor recovering. If two subsystems are in trouble at once, the more severe one shows, and the ADS1115 wins a tie.

A lit LED does not mean the device has stopped working. A failed sensor is reported as nan and left out of the upload, while every other measurement continues to be sampled and sent. Restarts are rate-limited to at most one per hour precisely so that one dead sensor cannot take the whole unit off the air.

Every sensor drops out the same way: each stamps the time of its last successful read, and a measurand goes nan once that stamp is more than 5.5 minutes old — one sample interval plus margin. A single failed read never costs a reading, because the sensors poll far faster than they are sampled; it takes eleven consecutive failures for temperature, humidity, globe or illuminance, and 165 for air speed. What this does not catch is a sensor that keeps answering with a stuck value.

Amber is the one worth attention in the field: the remote board connects over an RJ45 lead, and a marginal cable is the most common cause of it.

Modifying Firmware

Users are free to modify the SAMBA firmware to suit their needs. We recommend familiarity with ESPHome and microcontroller programming before doing so. To get started:

  1. Copy secrets.yaml.example to secrets.yaml and set your InfluxDB server.
  2. Modify the relevant .yaml files in config/.
  3. Compile and upload via USB-C with esphome run samba.yaml, or wirelessly with esphome run samba.yaml --device <IP_ADDRESS>.

Calibration coefficients are stored as persistent global variables and exposed as number entities over the native API (config/calibration.yaml), so they are set on the device rather than compiled in.

The user is responsible for managing any device running modified firmware.

OTA Updates

SAMBA devices check for firmware updates every Monday at 04:00 UTC by comparing against firmware/manifest_v2.json. If a new version is available, the update is applied automatically after a random delay of up to 10 minutes to avoid fleet-wide simultaneous downloads. Units still on 1.x read firmware/manifest.json and never update to 2.x on their own; they move to 2.0 only when the IEQ Lab reflashes them. Automatic updates can be disabled with the Automatic Updates switch (see Deployment).

Deployment

SAMBA devices are shipped pre-calibrated, with their location tags (building, level, zone) and calibration coefficients already set by the IEQ Lab. Follow these steps to connect a new SAMBA to your network and start sampling:

  1. Power on the SAMBA. The status LED blinks green while it boots. This firmware has no separate setup colour, so a unit still waiting for a network looks the same as one that is sampling.
  2. Connect to the hotspot. Using a phone or laptop, join the SAMBA's own open WiFi network — it is named after the device, samba-xxxxxx, where xxxxxx is the last three bytes of its MAC address — and open the captive portal at http://192.168.4.1. There is no password on the hotspot.
  3. Enter WiFi credentials. Select the target 2.4 GHz network from the list and enter the password. The SAMBA will connect and begin sampling automatically. The LED will blink green during the warm-up period and then turn off once it enters the normal sampling routine.

Switch it on in the room it will monitor. The VOC sensor's index is relative: it learns a baseline from the air it sees in its first 45 minutes and then holds that baseline for about a month, so the first hour sets the scale every later TVOC reading is reported against. Boot the unit in its final position under normal conditions — not on a bench in another room, and not somewhere unusually clean or unusually solvent-heavy. If a unit's first hour was unrepresentative, power-cycling it within three hours discards the baseline and it learns again from scratch; after three hours only a firmware update clears it. This also means TVOC may step to a new baseline after an automatic update. Nothing else in the unit depends on where it is first switched on.

The location tags, the InfluxDB Upload / SD Card Write / Automatic Updates switches and the calibration coefficients are exposed over the ESPHome native API, so they can be viewed and changed from Home Assistant or with the IEQ Lab's samba laptop client from samba_calibration (its Identify SAMBA button blinks the LED to pick one unit out of a batch). If you need a SAMBA recalibrated or re-tagged, please reach out — see Project Maintenance below.

The API is encrypted with a key that is set on the device by the IEQ Lab during deployment rather than compiled into the firmware, so the published binaries carry no key; until one is set, the device accepts unencrypted connections so it can be provisioned. The same is true of the InfluxDB token and the OTA password (see config/influx.yaml and config/ota.yaml): each is stored on the device, survives updates, and the device only ever reports a fingerprint of it (InfluxDB Token, OTA Password) together with the result of its last upload (InfluxDB Status). The published firmware has no compiled-in fallbacks: a unit that has not been provisioned does not upload to InfluxDB and takes OTA updates without a password, so provision it before it leaves a network you trust. To upload to your own InfluxDB instead, set token: in config/influx.yaml in your build; a provisioned token still takes precedence. A unit going into a home gets a fourth credential, a pairing password that turns on a read-only HTTP service over the SD card log for the SAMBA Home app (see config/fileserver.yaml and docs/home-sync.md); without one the service is off and the unit behaves exactly as a building unit.

Project Maintenance

This is an active project to build an open research platform for healthy, high-performance buildings. If you're interested in using SAMBA in your project or contributing to its development, start a Github discussion or email Tom.

Releases

Packages

Contributors

Languages