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/data returns the live run JSON at any time, in both modes.
  • End Run (RC) or run completion (autonomous) persists results.json to SPIFFS.
  • After a reboot, /data serves the saved results.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.