Skip to content

Repository files navigation

AgroSensor

Field-node firmware for the AgroSmart precision-agriculture system. A NodeMCU ESP-12E (ESP8266) samples a water sensor board and a soil probe, applies calibration and temperature compensation, and publishes readings as versioned JSON.

tests firmware docs


⚠ Before you wire anything

The ESP8266 is not 5 V tolerant — 3.6 V absolute maximum on an input. Both sensor boards are 5 V parts, so every sensor-TX → ESP-RX line needs a resistor divider (1 kΩ series + 2 kΩ to ground) or a level shifter, fitted before first power-on. Details in docs/hardware/wiring.md.

Watch polarity on the soil probe. Its manual warns that reversed power or swapped A/B differential lines can destroy the device.


Hardware

Device Interface Provides
4-in-1 water board TTL UART, 9600 8N1, 5 V water pH, light, temperature, water level
7-in-1 soil probe RS485 Modbus RTU, 4800 8N1, 4.5–30 V moisture, soil temp, EC, soil pH, N, P, K

The two boards run at different baud rates. Getting them the wrong way round is the single most common cause of a sensor that appears dead.

Pin map

Function Pin GPIO Notes
4-in-1 RX (← board TX) D5 14 9600, via divider
4-in-1 TX (→ board RX) D6 12
Soil probe RX (← converter TXD) D7 13 4800, via divider
Soil probe TX (→ converter RXD) D1 5 3.3 V direct
Water-level power gate D2 4 duty-cycled to prevent trace corrosion
Debug + flashing 1/3 UART0 on USB, 115200

Both sensors run on SoftwareSerial, which is comfortable at these rates and leaves UART0 on USB for logging throughout — no Serial.swap(), no external USB-TTL adapter, no boot-strap-pin risk on D8. There is no DE/RE pin because the RS485 converter is an auto-flow-control module — an HW-097 (bare MAX485) was tried first and rejected, which docs/hardware/npk-7in1-probe.md explains. It is still a 5 V part, so its TXD keeps the divider.


Quick start

# Host-side unit tests — no board required
pio test -e native

# Build and flash the firmware
pio run -e nodemcuv2 -t upload
pio device monitor

# Phase B: decode the 4-in-1's undocumented serial format
pio run -e sniffer -t upload
pio device monitor -e sniffer

Before it will publish anything

cp include/secrets.example.h include/secrets.h   # then set AGRO_DEVICE_TOKEN

Without a device token every publish reports not-configured and readings only spool. The build still succeeds without secrets.h — it is gitignored, so CI and a fresh clone must not depend on it — and the node says so loudly at boot.

WiFi credentials do not belong in that file in normal use: an unprovisioned node raises a AgroSensor-Setup captive portal. The WIFI_SSID define exists only for bench work, where a portal on every reflash is a nuisance.

Developing the uplink without a backend

The real ingest endpoint does not exist yet, so develop against the mock:

python tools/mock-ingest.py                  # accept everything
python tools/mock-ingest.py --mode down      # simulate an outage
python tools/mock-ingest.py --fail-rate 0.5  # exercise backoff and the spool

Point AGRO_INGEST_URL in include/config.h at your machine's LAN address, not localhost — the request comes from the ESP8266. The failure modes are the interesting ones: --mode reject must not be spooled, --mode down must be.


Two things that will surprise you

1. The soil probe does not measure N, P or K. It measures bulk electrical conductivity and back-calculates all three with a linear fit. Out of the factory they are one EC reading wearing three scaling factors, and dry soil legitimately reports zero nitrogen. Every reading carries an npkEstimated flag that stays true until per-soil coefficients are written to the device, and there is a unit test asserting that a zero-NPK dry-soil frame decodes as valid rather than as a fault. Treat these values as relative trends, not lab results. See docs/hardware/npk-7in1-probe.md.

2. The 4-in-1's serial format is undocumented. Its framing is implemented and tested; its field layout is provisional, and the CSV column order is an explicit guess until you run the sniffer. Nothing trustworthy comes out of phWater, sunlight or waterLevel before that. See docs/hardware/ph-4in1-protocol.md.


Repository layout

src/main.cpp              composition root — wiring and scheduling only
src/sniffer/              Phase B protocol sniffer (separate env)
include/config.h          pins, timings, identity
lib/                      firmware modules (see lib/README)
test/                     host-side Unity tests (see test/README)
docs/                     hardware, module and integration documentation

Design rule: parsers take a byte buffer, never a serial port. Everything with logic in it is a pure function, and the hardware wrappers compile to nothing on the host. That is what makes pio test -e native possible, and it matters because protocol decoding is both the most likely thing to be wrong and the most painful thing to debug on-target. docs/modules/README.md covers the decomposition and how each module is broken down before coding.


Status

Phase State
A — Toolchain ✅ Arduino framework; both envs build clean, no warnings
B — 4-in-1 protocol Blocked on hardware. Sniffer ready
C — Modbus soil probe 🟡 Read + write paths implemented and tested; needs on-hardware cross-check
D — Calibration 🟡 pH and EC→nutrient maths tested, persisted to LittleFS; no on-node way to enter calibration points
E — Integration ✅ Both sensors sampled, payload built and logged
F — Network delivery 🟡 WiFi, NTP, HTTP uplink and store-and-forward working against a mock; TLS and OTA outstanding

✅ means the code exists and its decisions are covered by host tests — not that it has been confirmed against real hardware. docs/ROADMAP.md tracks that distinction and every open gap behind these rows.

Both vendor sample-code bugs are fixed

The soil probe's vendor code has two defects, each with a named regression test:

  • Negative temperatures decode as garbage. Their Arduino and Python samples read the temperature register unsigned, so 0xFF9B (−10.1 °C) becomes ~6549 °C.
  • The Arduino sample never verifies CRC. RS485 in a wet field picks up noise, and an unchecked frame occasionally hands you a plausible-looking wrong number.

Telemetry

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

Absent fields are omitted, not zeroed — an unplugged board must never report "phWater": 0.0, which is a plausible acid reading nobody downstream could distinguish from a real one.

There is currently nowhere to send this: the AgroSmart backend directory is empty and the web app has no API routes. The contract, the field mapping, and ten specific mismatches with the existing dashboard are documented in docs/integration/dashboard-contract.md. Note in particular: do not have the device write directly to Supabase with the app's publishable key — no RLS policies exist, so the table would be world-writable.


Documentation

Contributing

See CONTRIBUTING.md. Host tests must pass (pio test -e native) and both environments must build warning-free.

Licence

Dual-licensed by content type:

  • Firmware and source codeMIT
  • Documentation (docs/, READMEs) — CC BY 4.0

Third-party dependency licences, including one that needs attention before a release, are listed in THIRD-PARTY.md.

About

AI-level Agro-sensor Firmware for measuring: pH, NPK, sunlight, temperature, salinity, conductivity, etc

Topics

Resources

Code of conduct

Contributing

Stars

22 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages