JSON Schema
This schema is frozen. The visualiser depends on exact field names and types. Do not rename fields or change null behaviour. The firmware (
data_logger.cpp) and the visualiser (/viz) both implement exactly this shape.
Full Schema
{
"run_id": "run_001",
"total_duration_s": 480.0,
"sectors_completed": 4,
"path": [
{ "t_s": 0.0, "x_mm": 0.0, "y_mm": 0.0 }
],
"samples": [
{
"id": "W1",
"type": "WATER",
"sector": 1,
"terrain": "Sandpaper",
"value_ntu": 42.3,
"value_pct": null,
"t_s": 45.2
},
{
"id": "S1",
"type": "SOIL",
"sector": 1,
"terrain": "Sandpaper",
"value_ntu": null,
"value_pct": 68.5,
"t_s": 78.1
}
]
}
Field Reference
Top-Level
| Field | Type | Description |
|---|---|---|
run_id |
string | Unique per boot (run_<millis>) |
total_duration_s |
number | Run time in seconds (live until End Run, final after) |
sectors_completed |
integer | Count of distinct sectors with at least one sample |
path |
array | Position estimate, logged at 1 Hz |
samples |
array | Measurement objects, max 12 |
Sample Object
| Field | Type | Description |
|---|---|---|
id |
string | "W1"–"W4" for water, "S1"–"S8" for soil, numbered per type in collection order |
type |
"WATER" or "SOIL" |
Sensor used |
sector |
integer | 1–4; 0 = unknown (autonomous estimate unavailable) |
terrain |
string | From TERRAINS[] lookup by sector; "Unknown" for sector 0 |
value_ntu |
number or null | NTU for WATER; null for SOIL |
value_pct |
number or null | Moisture % for SOIL; null for WATER |
t_s |
number | Seconds from run start |
Path Point
| Field | Type | Description |
|---|---|---|
t_s |
number | Seconds from run start |
x_mm |
number | Estimated X (origin = start position) |
y_mm |
number | Estimated Y |
Path is a speed-model estimate (no encoders) — treat it as illustrative coverage, not ground truth. Samples carry no coordinates; sector labelling is the operator's job in RC mode.
Null Field Rules
| Type | value_ntu |
value_pct |
|---|---|---|
| WATER | number | null |
| SOIL | null |
number |
Both fields always present; the unused one is explicitly null, never omitted.
Terrain Names
Filled by firmware from the sector number via TERRAINS[] in data_logger.cpp:
| Sector | terrain |
|---|---|
| 0 | "Unknown" |
| 1 | "Sandpaper" |
| 2 | "Wet Soil" |
| 3 | "Grass" |
| 4 | "Sand" |
Rulebook lists terrains as examples only — update this table on Testing Day if the real arena differs.
Storage and Access
- Every sample is also printed to serial with units — the rulebook-approved output.
GET http://192.168.4.1/datareturns the live run JSON at any time, in both modes.- End Run (RC) or run completion (autonomous) persists
results.jsonto SPIFFS. - After a reboot,
/dataserves the savedresults.json— judges can still pull data if the robot power-cycled.
To archive: connect to RobotAP, open 192.168.4.1/data, save the JSON, back it up to two devices, then drag-drop into the visualiser.