12 KiB
System Architecture — EZO RTD Temperature Monitor
Purpose of this document: Provide a complete architectural snapshot of the project so that any contributor (or AI assistant) can understand the full system — hardware, firmware, data pipeline, and frontend — without reading every source file individually.
1. System Overview
The system is a single-node embedded IoT monitoring platform composed of four layers:
┌─────────────────────────────────────────────────────────────┐
│ Layer 4 — Presentation index.html / index-css.html │
│ Browser polls JSON via HTTP every 1 s │
├─────────────────────────────────────────────────────────────┤
│ Layer 3 — Web Server Nginx (static file server) │
│ Serves HTML, SVG assets, and JSON data files │
├─────────────────────────────────────────────────────────────┤
│ Layer 2 — Backend Daemon ezortd_daemon (C binary) │
│ Reads I²C → writes JSON + appends CSV │
├─────────────────────────────────────────────────────────────┤
│ Layer 1 — Hardware / HAL EZO-RTD via I²C on RPi │
│ Atlas Scientific ISCCB-2 @ 0x66, I²C bus 1 │
└─────────────────────────────────────────────────────────────┘
Host board: Raspberry Pi (any model with 40-pin GPIO; developed on Raspberry Pi Zero or similar)
OS: Raspberry Pi OS (Debian-based, Linux kernel)
Language: C (backend), HTML/CSS/JavaScript (frontend)
IPC mechanism: File system — JSON files in data/ act as the shared state between daemon and web server
2. Hardware Layer
2.1 Sensor — Atlas Scientific EZO-RTD™ (ISCCB-2)
The EZO-RTD is a signal-conditioning circuit for Pt-100 or Pt-1000 RTD probes. It digitizes the analog RTD signal internally and exposes a clean digital interface via I²C or UART.
| Property | Value |
|---|---|
| Module | Atlas Scientific ISCCB-2 |
| Protocol in use | I²C |
| I²C Address | 0x66 (fixed, factory default) |
| I²C Bus | Bus 1 (/dev/i2c-1) |
| Supply voltage | 3.3 V |
| Read command | ASCII string "R" (1 byte) |
| Response time | ~1000 ms after issuing "R" |
| Response format | Byte 0: status code (0x01 = success); Bytes 1–N: null-terminated ASCII float |
| Resolution | 0.001 °C |
2.2 Physical Wiring
EZO-RTD (ISCCB-2) Raspberry Pi 40-pin Header
───────────────── ──────────────────────────
VCC ────────────────────── Pin 1 (3.3 V)
GND ────────────────────── Pin 6 (GND)
SDA ────────────────────── Pin 3 (GPIO2, I²C1 SDA)
SCL ────────────────────── Pin 5 (GPIO3, I²C1 SCL)
OFF ──── not connected
The
OFFpin (active-low sleep input) is left floating/unconnected; the circuit is permanently powered. Future implementations may drive this pin from a GPIO to enable power management.
2.3 I²C Bus Configuration
- Bus:
/dev/i2c-1 - Speed: 100 kHz (standard mode; the EZO-RTD supports up to 400 kHz Fast Mode)
- Pull-ups: Provided internally by the Raspberry Pi (1.8 kΩ on SDA/SCL lines)
- Enable:
sudo raspi-config→ Interface Options → I2C → Enable
3. Firmware / HAL Layer (C)
3.1 Source Files
| File | Role |
|---|---|
sensors/EZORTD/ezortd.h |
Public API: I²C address constant, getTemperature() declaration |
sensors/EZORTD/ezortd.c |
getTemperature() implementation |
sensors/EZORTD/main.c |
One-shot binary: single read → JSON to stdout |
sensors/EZORTD/ezortd_daemon.c |
Daemon: infinite loop, writes JSON + CSV every second |
sensors/EZORTD/Makefile |
Builds EZORTD binary (one-shot target) |
3.2 getTemperature() — Protocol Detail
// Signature
int getTemperature(int fd, double *temperature);
// Returns 0 on success, -1 on error
Communication sequence:
1. ioctl(fd, I2C_SLAVE, 0x66) — select slave address
2. write(fd, "R", 1) — issue read command (ASCII 'R')
3. usleep(1000000) — wait 1000 ms for conversion
4. read(fd, response, 32) — read up to 32 bytes
5. Check response[0] == 0x01 — status byte: 0x01 = success
6. atof(&response[1]) — parse ASCII float from bytes 1..N
Error codes in response[0]:
| Code | Meaning |
|---|---|
0x01 |
Success |
0x02 |
Syntax error in command |
0xFF |
No data (not ready) |
0xFE |
Pending (not ready yet) |
3.3 One-Shot Executable (EZORTD)
Compiled from main.c + ezortd.c. Opens /dev/i2c-1, calls getTemperature() once, prints result as JSON to stdout, exits.
./EZORTD
{ "temperature": 24.414 }
Used for testing and for cron-based updates.
3.4 Daemon (ezortd_daemon)
Compiled from ezortd_daemon.c + ezortd.c. Runs an infinite loop with 1-second sleep between iterations. On each iteration:
- Reads temperature via
getTemperature() - Overwrites
data/EZORTD.jsonwith current value - Appends a Unix-timestamp + temperature row to
logs/temp.csv
Output paths (hardcoded in source — update before compiling):
// JSON
"/home/cristian/airquality/basic-ui-dashboard/data/EZORTD.json"
// CSV log
"/home/cristian/airquality/basic-ui-dashboard/logs/temp.csv"
JSON output format:
{ "temperature": 24.414 }
CSV output format:
timestamp,temperature ← header row (from EZORTD.csv template)
1717027200,24.414 ← Unix epoch (seconds), float to 3 decimal places
4. Data Pipeline
[EZO-RTD sensor]
│
│ I²C (0x66, /dev/i2c-1)
▼
[ezortd_daemon — C process]
│
├──► data/EZORTD.json (overwrite, every 1 s)
│ { "temperature": 24.414 }
│
└──► logs/temp.csv (append, every 1 s)
1717027200,24.414
│
▼
[Future: historical chart]
[Nginx — static file server]
│
│ HTTP GET ./data/EZORTD.json
▼
[Browser — index.html]
│
│ setInterval(fetchData, 1000)
▼
[DOM update — temp-value element]
5. Web Server Layer
Server: Nginx (static file serving only — no application logic)
Document root: Set to the repository root directory in /etc/nginx/sites-available/default:
root /home/pi/<repo-path>;
Served assets:
| Path | Description |
|---|---|
/ or /index.html |
Minimal temperature dashboard |
/index-css.html |
Extended multi-sensor dashboard |
/data/EZORTD.json |
Live temperature JSON (written by daemon) |
/images/*.svg |
Ionicons SVG assets (thermometer, water, cloud, sun) |
No server-side scripting is used. The daemon writes files to disk; Nginx serves them as-is.
6. Frontend Layer
6.1 index.html — Minimal Dashboard
Single-sensor display. Pure HTML + inline CSS + vanilla JS.
Data source: GET ./data/EZORTD.json
Polling interval: 1000 ms (setInterval)
DOM targets:
| Element ID | Content |
|---|---|
temp-value |
data.temperature.toFixed(3) + " ℃" |
last-update |
"Updated: " + new Date().toLocaleTimeString() |
6.2 index-css.html — Extended Dashboard
Multi-sensor card layout using Roboto (Google Fonts), flexbox, and CSS animations.
Data sources (partially commented out):
const res = await Promise.all([
// fetch("./data/BH1750.json"), // light — pending
// fetch("./data/BMP180.json"), // pressure — pending
fetch("./data/HTU21D.json"), // temp + humidity (legacy sensor, active)
// fetch("./data/MH_Z19.json") // CO₂ — pending
]);
Polling interval: 6000 ms
DOM targets: temp-value, humidity-value, co2-value, pressure-value
7. Key Constants and Configuration
| Constant | Value | Location |
|---|---|---|
| EZO-RTD I²C address | 0x66 |
sensors/EZORTD/ezortd.h |
| HTU21D I²C address | 0x40 |
sensors/HTU21D/htu21d.h |
| I²C bus device | /dev/i2c-1 |
sensors/EZORTD/main.c, ezortd_daemon.c |
| Sensor read delay | 1,000,000 µs (1 s) | sensors/EZORTD/ezortd.c |
| Daemon loop interval | sleep(1) — 1 s |
sensors/EZORTD/ezortd_daemon.c |
| JSON output path | /home/cristian/…/data/EZORTD.json |
ezortd_daemon.c (update per deployment) |
| CSV log path | /home/cristian/…/logs/temp.csv |
ezortd_daemon.c (update per deployment) |
| Frontend poll interval (primary) | 1000 ms | index.html |
| Frontend poll interval (extended) | 6000 ms | index-css.html |
| Nginx document root | /home/pi/<repo-path> |
/etc/nginx/sites-available/default |
8. Build System
EZO RTD
CC=gcc
CFLAGS=-I.
OBJ=main.o ezortd.o
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
EZORTD: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
No external libraries required. Uses only standard Linux I²C headers: <linux/i2c-dev.h>, <sys/ioctl.h>.
HTU21D (legacy)
EXTRA_LIBS=-li2c
Requires libi2c (sudo apt install libi2c-dev). Uses SMBus wrappers from <i2c/smbus.h>.
9. Legacy Sensor — HTU21D
Retained in sensors/HTU21D/ for reference and backward compatibility with index-css.html.
| Property | Value |
|---|---|
| I²C Address | 0x40 |
| Measurements | Temperature + Relative Humidity |
| Temperature formula | T = -46.85 + 175.72 × raw / 65536.0 |
| Humidity formula | RH = -6 + 125 × raw / 65536.0 |
| Library dependency | libi2c |
| Command | SMBus block read (0xE3 temp, 0xE5 humidity) |
10. Planned Sensors (Not Yet Integrated)
| Sensor | Measurement | Interface | Data file |
|---|---|---|---|
| BH1750 | Ambient light (lux) | I²C | data/BH1750.json |
| BMP581 | Atmospheric pressure (hPa) | I²C | data/BMP581.json |
| ZMOD4410 | TVOC / IAQ index | I²C | data/ZMOD4410.json |
11. File I/O Summary
| File | Writer | Reader | Format | Update Rate |
|---|---|---|---|---|
data/EZORTD.json |
ezortd_daemon |
Nginx → browser | JSON object | 1 s |
logs/temp.csv |
ezortd_daemon |
(manual / future viz) | CSV (epoch, float) | 1 s (append) |
data/HTU21D.json |
HTU21D binary (manual / cron) |
Nginx → browser | JSON object | On demand |
data/EZORTD.csv |
(template only — unused) | — | CSV | — |
12. Security and Deployment Notes
- The Nginx instance serves on port 80 with no authentication. Suitable for a local LAN network only.
- The daemon runs as root (required for I²C access unless the user is added to the
i2cgroup). Usesudo usermod -aG i2c $USERto avoid running as root. - File paths in
ezortd_daemon.care hardcoded and must be updated per deployment before compilation. - No input validation is performed on the JSON data in the browser; malformed JSON causes a silent catch/error in the console.