# Environmental sensors for VAHA: wiring, how readings reach the app, and two TVOC data-integrity gaps

- Version: v1.0, published 22 October 2026
- Written by: Ritesh Bonthalakoti
- Reviewed by: Bharat Meda
- Domains: Sensing
- Readiness: TRL 3, experimental proof of concept
- Tested on: VAHA's sensor path as of 2026-08-17: an Arduino UNO Q with a DHT22, an AGS02MA and a hall-effect water-flow sensor, read from the code and the AGS02MA datasheet; the device server's code run on a development computer with TVOC failure values
- Standards: ISO 16290 for the readiness level only; no sensor compared with a reference instrument yet; no accredited testing
- Full paper: [PDF, 17 pages, 2.7 MB](https://dharanova.com/whitepapers/vaha-environmental-sensors-tvoc-data-integrity.pdf)

## Summary

Describes how VAHA's three sensors are wired to the Arduino UNO Q and how a reading reaches every capture and the app's live view, and checks whether a bad reading can pass as a good one. DHT22 failures are handled (sent as nan, stored as no value). TVOC has two gaps: a failed I2C read returns 7777 or 8888 + error code, which both Linux-side parsers store as a reading (confirmed against the device server's API); and replies are decoded without the AGS02MA's CRC and with its status byte in the value, so a set status flag adds 16,777,216 ppb and a damaged reply can give a plausible wrong value (calculated from the datasheet's reply format). No sensor accuracy or delivery latency has been measured. TRL 3; the path to TRL 4 is limits, the TVOC fix tested with a fault induced on the device, reference comparisons for each sensor, and measured latency.

## Key findings

- **TVOC failures stored as readings.** A failed read returns 7777 or 8888 + error code, stored and served as a real ppb value. The DHT22 path in the same function reports failure correctly.
- **TVOC decoded without its checksum.** The status byte is kept in the value and the CRC is never checked, so a set status flag or a corrupted reply gives a wrong reading with no sign of it.
- **Two parsers.** A capture's sensor snapshot is parsed in main.py; the live readings in sensor_service.py. Neither checks the TVOC value.
- **Not measured yet.** Sensor accuracy, delivery latency, and the TVOC failure on real hardware.

## Figures shown on the page

- Figure 2: Sensor wiring, from the README's pin table and the pin definitions in sketch.ino. Wiring diagram. Three sensors connect to the microcontroller side of the Arduino UNO Q (ABX00087). DHT22, temperature and humidity over a single-wire digital line: VCC 5V, DATA D2, GND, with D2 as a digital input. Water-flow sensor, hall-effect, 7.5 pulses per L/min, rising-edge pulses: VCC 5V, SIG D3, GND, with D3 as an interrupt. AGS02MA, TVOC, I2C address 0x1A: VCC 3.3V, SDA A4, SCL A5, GND, with the I2C clock at 20 kHz.
- Figure 3: From sensor to app, as the code runs it. The two Linux-side paths parse the same string in the same way. Flow diagram. The DHT22, the AGS02MA and the flow sensor feed sensors_get() in sketch.ino on the microcontroller, which reads all three and returns one string, T:..:H:..:F:..:L:..:V:... A DHT22 failure is sent as nan. A TVOC failure is sent as 7777 or 8888 plus an error code, a plain number (Gap 1), and the TVOC status byte is kept in the value (Gap 2). The string crosses the router bridge, Bridge.call('sensors_get'), to two Linux-side paths. At each capture, get_sensors() in main.py parses it and save_capture() stores it with the note, which is synced to the app. Every 30 s, sensor_service.py parses the same string and keeps the last 100 readings, about 50 minutes, for the live readings API and WebSocket. Neither parser checks the TVOC value: each takes int() of whatever the microcontroller sent.
- Figure 4: Where this work sits on the EU Horizon TRL scale. Technology readiness ladder from TRL 1 to TRL 9. TRL 1 to 3 are filled in; TRL 3, experimental proof of concept, is marked as this work. TRL 4, technology validated in lab, is marked as next.
- Figure 5: Path from this work to TRL 4, one row per requirement in priority order: its status today, the change to the build, the test that shows it is met, and the data the test leaves. Step 1: set the limits first, accuracy for each sensor and delivery latency, written down before any test. Then one row per requirement. R3, failures and corrupt reads flagged, not met: readTVOC() reports a failure as a flag, as the DHT22 does with nan, checks the CRC and keeps only the 24-bit value; tested by disconnecting SDA during reads on the device and corrupting the CRC byte, with no number reaching a capture; evidence ags02ma_fault_test.csv. R2, readings represent the room, not measured: no change beyond the R3 fix; each sensor tested against a reference, a thermometer and hygrometer, a VOC source and a measured volume of water; evidence dht22_accuracy.csv, ags02ma_accuracy.csv and flow_calibration.csv. R1, readings delivered quickly, not measured: timestamp each reading on the microcontroller and when it reaches the app; delivery latency over several sessions, p50, p95 and p99 with n; evidence bridge_latency_log.csv. TRL 4 when every row passes against the limits set in step 1. Then a test in a bathroom, with shower humidity, fan noise and cleaning products (TRL 5).

## Related reading

- [The common sensor families](https://dharanova.com/learn/sensor-families.md): background on Learn
- [UART, I²C and SPI](https://dharanova.com/learn/uart-i2c-spi.md): background on Learn
- [Accuracy, precision and resolution](https://dharanova.com/learn/accuracy-precision-resolution.md): background on Learn
- [The one room a phone does not follow you into](https://dharanova.com/arduino-physical-ai-challenge-2026.md): an engineering note from our team
- [Uncalibrated bench characterisation of low-cost turbidity, TDS, pH and level sensors on Arduino Uno R4 WiFi](https://dharanova.com/whitepapers/uncalibrated-water-sensors-arduino-uno-r4-wifi.md): another whitepaper

This paper documents component-level work in the lab. It is not a product specification.
