Skip to content

Latest commit

 

History

History
134 lines (104 loc) · 6.88 KB

File metadata and controls

134 lines (104 loc) · 6.88 KB

Telemetry Contract & Dashboard Integration

What this node publishes, and where it does not line up with the AgroSmart dashboard.

Scope note: this repository is hardware/firmware only. The observations below about ../app/agroapp are recorded here for whoever builds the backend — they are deliberately not fixed by editing the web app from this repo.


Payload

Built by lib/Telemetry. Versioned, so the schema can move without bricking deployed nodes.

{
  "v": 1,
  "sensorId": "AGS-001",
  "ts": 1753900000,
  "readings": {
    "temperature": 24.8,
    "moisture": 42.5,
    "salinity": 0.380,
    "phSoil": 6.80,
    "npk": { "nitrogen": 95, "phosphorus": 52, "potassium": 210 },
    "phWater": 7.10,
    "waterTemperature": 23.4,
    "sunlight": 780,
    "waterLevel": 63
  },
  "quality": { "npkEstimated": true, "stabilising": false, "soilDry": false },
  "diag": { "rssi": -62, "uptime": 38211, "fw": "0.1.0" }
}

Field sources and units

Field Source Unit Confidence
temperature soil probe °C measured, ±0.5 °C
moisture soil probe % measured, ±2–3%
salinity soil probe EC ÷ 1000 dS/m measured, ±3% FS
phSoil soil probe pH rough — device range only 3–9, ±0.3
npk.* soil probe mg/kg estimated from EC, not measured
phWater 4-in-1 pH calibrated + temp-compensated
waterTemperature 4-in-1 °C mainly used to compensate phWater
sunlight 4-in-1 LDR relative, NOT lux uncalibrated
waterLevel 4-in-1 relative, non-linear coarse / presence

Two rules the consumer must respect

1. Absent fields are omitted, not zeroed. A node whose 4-in-1 board is unplugged emits no phWater key at all. It must never report "phWater": 0.0, because that is a perfectly plausible-looking acid reading and nothing downstream could tell it from a real one. Distinguish "no data" from "zero" by key absence.

2. The quality block is load-bearing, and always present.

Flag Meaning
npkEstimated N/P/K are back-calculated from conductivity, not measured. Stays true until per-soil coefficients are written to the probe. Do not build fertiliser recommendations on these values while it is true.
stabilising Within the probe's 5-minute post-insertion settling window. Readings are real but converging.
soilDry Conductivity was zero, which zeroes derived NPK. Expected in dry soil, not a fault. Do not raise a sensor-failure alert.

These are never omitted, so their absence can never be misread as false.

3. ts is omitted until NTP syncs. When absent, the server should stamp arrival time rather than trust a device clock known to be wrong. When present it is Unix epoch seconds. Buffered readings replayed after an outage carry their original ts, which is the whole point of sending it.


Mismatches with the existing dashboard

Observed in ../app/agroapp. Each needs a decision from whoever wires the backend.

# Observation Location
1 Sensor.ph is single-valued, but the hardware produces two pH readings — water (0–14, calibrated) and soil (3–9, ±0.3). They are different quantities and cannot be merged. The payload uses phWater and phSoil. lib/types.ts
2 No waterLevel field exists in the dashboard type at all, though the 4-in-1 measures it. lib/types.ts
3 sensorId length conflict: sensorSchema enforces z.string().length(7) while sampleSensors mocks use 14-char IDs like "SENSOR-LKO-001". The firmware bakes one in — currently "AGS-001" (7 chars, matching what the schema actually validates). Must also stay URL-safe, since it routes /dashboard/sensors/[sensorID]. lib/schema.ts vs lib/table-data.ts
4 No timestamp field anywhere in the app's types. The per-reading table type has no time column, and the sensor detail page fabricates its "last 5 data points" by duplicating one reading five times. That is exactly the slot real time-series data fills. lib/types.ts, app/dashboard/sensors/[sensorID]/page.tsx
5 npk is a nested object in TypeScript but naturally three Postgres columns. Decide where flattening happens — the API or the UI. The table reads row.original.npk.nitrogen directly. lib/types.ts, lib/table-columns.tsx
6 No ownership column (user_id / farm_id) exists, yet one is required for any workable row-level-security policy.
7 status is commented // these are just test labels. It should be server-derived from staleness and thresholds, not device-reported. The firmware does not send it. lib/types.ts
8 sunlight is labelled "lux" in the table header, but the LDR is uncalibrated and relative. Either relabel it or calibrate it — see ../hardware/calibration.md. lib/table-columns.tsx
9 temperature is formatted with temp.toFixed(1), which throws on null. Since fields can legitimately be absent, the UI needs a null guard. lib/table-columns.tsx
10 No battery or RSSI fields exist, although mockAlerts references a battery-level alert. The payload sends rssi and uptime under diag. lib/types.ts

Backend status: does not exist yet

At the time of writing:

  • ../app/agro-app-backend/ is an empty directory.
  • The Next.js app has no app/api/ routes and no Server Actions.
  • Supabase is wired for email/password auth only. A repo-wide search found no .from(), .insert() or .select() calls outside node_modules.
  • There are no migrations and no generated database types, so the sensor table schema does not exist in version control.
  • lib/supabase/proxy.ts exports updateSession but there is no middleware.ts, so route protection is not active.

⚠ Do not have the device write directly to Supabase

It would be technically possible to POST to PostgREST with the app's publishable/anon key. Don't. No RLS policies exist on that project, so an otherwise-open table would be world-writable by anyone holding a key that is already shipped to every browser.

The correct shape:

device --(HTTPS + per-device token)--> app/api/ingest --(service-role key)--> Postgres

The ingest route validates a per-device secret server-side and writes with a service-role key that never leaves the server. AGRO_DEVICE_TOKEN in include/secrets.h is the device half of that.

Keeping firmware unblocked

Phase F develops against a local mock endpoint over plain HTTP — any trivial listener that logs the body and returns 200. AGRO_INGEST_URL in include/config.h points at it. Plain HTTP first is also the right order for a second reason: BearSSL costs ~16–22 KB of heap per TLS handshake on top of WiFi, so prove the payload before adding that pressure.