LoRa-connected farm tech that auto-waters based on soil and weather conditions.
- What: Low-power sensor nodes (C) that send soil/weather telemetry over LoRa to a Python gateway/server which decides when to water and issues valve commands.
- Who: Hobbyists, small farms, researchers building low-cost adaptive irrigation.
- Quick: Flash firmware → run gateway → configure thresholds/API token.
- Soil moisture, battery, optional temp/humidity telemetry.
- LoRa (SX127x family) radio comms: node → gateway → server.
- Server rule engine: soil threshold + weather/forecast + safety constraints.
- Power-efficient sleep mode for nodes.
- Simple JSON API and simulation helpers.
- C (firmware) — ~77%
- Python (gateway/server) — ~23%
- MCU: (e.g., ESP32 / Arduino Pro Mini / STM32) — set in firmware config
- LoRa radio: SX1276/SX1278 (adjust freq, SF in firmware)
- Soil moisture sensor (analog or digital)
- Valve: solenoid + relay or valve driver
- Power: battery ± solar charge controller
- Edit board & radio settings:
- Open firmware/config.h or firmware/boards/.h and set MCU, pins, frequency, spreading factor, and node ID.
- Build & flash (examples):
- PlatformIO:
cd firmware pio run -e <env> -t upload - Make / avr:
cd firmware make BOARD=<board> make flash
- PlatformIO:
- Behavior:
- Node wakes, reads sensors, sends compressed telemetry packet over LoRa, sleeps. It accepts simple control packets to open valve for N seconds.
-
Create venv and install:
python -m venv .venv # macOS / Linux source .venv/bin/activate # Windows (PowerShell) .\.venv\Scripts\Activate.ps1 pip install -r gateway/requirements.txt
-
Example configuration (gateway/config.example.yaml)
server: host: 0.0.0.0 port: 8000 api_token: "replace-with-secure-token" lora: port: "/dev/ttyUSB0" # serial port for sx127x gateway board (if using an attached concentrator) baudrate: 115200 frequency: 915000000 rules: soil_threshold: 30 # percent min_interval_sec: 3600 # minimum seconds between waterings per node max_valve_seconds: 300 # safety cap for commands
-
Run the server:
cd gateway python server.py --config config.yaml -
Docker (optional):
- Build:
docker build -t amc-irrigation:latest gateway/
- Run:
docker run -d \ --device=/dev/ttyUSB0:/dev/ttyUSB0 \ -p 8000:8000 \ -v $(pwd)/gateway/config.yaml:/app/config.yaml \ amc-irrigation:latest
- Build:
The gateway exposes a small JSON HTTP API for monitoring and manual commands.
-
GET /api/v1/nodes
- Returns list of known nodes and last-seen timestamps.
-
GET /api/v1/nodes/{node_id}/telemetry
- Returns latest telemetry for the node.
-
POST /api/v1/nodes/{node_id}/valve
- Body: { "duration": number } — seconds to open valve.
- Header: Authorization: Bearer <api_token>
- Response: { "status": "accepted", "scheduled_until": "" }
Security: always protect the API endpoint with a strong token and network-level restrictions.
Below are short TypeScript examples showing how to query telemetry and issue a valve command.
-
Install (Node):
npm init -y npm install node-fetch@2 npm install --save-dev @types/node-fetch
-
Get telemetry (TypeScript):
// filename: getTelemetry.ts import fetch from "node-fetch"; type Telemetry = { node_id: string; moisture: number; // percent battery_mv: number; temp_c?: number; humidity?: number; ts: string; // ISO }; async function getLatest(nodeId: string, token: string): Promise<Telemetry> { const res = await fetch(`http://localhost:8000/api/v1/nodes/${nodeId}/telemetry`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return (await res.json()) as Telemetry; } // usage (async () => { const telemetry = await getLatest("node-01", "replace-with-token"); console.log("Latest telemetry:", telemetry); })();
-
Open valve (TypeScript):
// filename: openValve.ts import fetch from "node-fetch"; async function openValve(nodeId: string, durationSec: number, token: string) { const res = await fetch(`http://localhost:8000/api/v1/nodes/${nodeId}/valve`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ duration: durationSec }), }); if (!res.ok) { const text = await res.text(); throw new Error(`Failed to open valve: ${res.status} ${text}`); } return await res.json(); } // usage (async () => { const resp = await openValve("node-01", 60, "replace-with-token"); console.log("Command response:", resp); })();
There are simple scripts to emulate node telemetry for testing (see gateway/simulate/).
- Example:
# send a fake telemetry packet for node-01 python gateway/simulate/send_telemetry.py --node node-01 --moisture 25 --battery 3700 - Use simulation to validate server rules, web UI, and API clients without hardware.
Nodes send compact binary packets over LoRa. The gateway decodes and stores telemetry as JSON:
- node_id (1 byte or ASCII ID)
- flags (sensors present)
- moisture (1 byte, 0-100)
- battery_mv (2 bytes)
- optional temp / humidity fields
- sequence / timestamp (optional)
See firmware/README or firmware/protocol.md for the exact encoding used by your build.
The server evaluates simple rules:
- If soil moisture < soil_threshold AND last_watering > min_interval_sec → schedule valve open.
- Safety checks:
- Never command more than max_valve_seconds in a single command.
- Respect blackout windows (e.g., don't water during freeze).
- Rate-limit automatic commands per node.
You can extend rules by modifying gateway/rules.py (or equivalent) to integrate weather APIs, ET0, or soil-water balance models.
- Use deep-sleep and long intervals to maximize battery life.
- Report battery voltage and implement low-battery behavior (e.g., less frequent telemetry).
- Use hardware-level watchdog on MCU for reliability.
- Add validation on server for malformed packets and replay protection (sequence numbers).
- Gateway can't see nodes:
- Verify LoRa frequency/spreading factor match between node and gateway.
- Check serial device permissions (udev rules on Linux).
- Valve commands appear accepted but valve doesn't open:
- Confirm relay wiring and power supply to solenoid.
- Check node logs for received control packet and any safety override.
- Telemetry values out of expected range:
- Calibrate sensors and add scaling in firmware/config.
Contributions welcome. Suggested workflow:
- Fork the repo.
- Create a feature branch.
- Add tests or simulation steps for new behavior.
- Open a PR describing the change.
Please include hardware tested against (board, radio, sensors) for firmware changes.
- firmware/ — device code (C)
- gateway/ — Python gateway + server, simulation tooling
- docs/ — protocol docs, hardware wiring diagrams (if present)
- examples/ — wiring and deployment examples
- Add UI for scheduling and visualizing moisture trends.
- Add OAuth / RBAC to API for teams.
- Add more precise irrigation models (weather + crop coefficients).
Add a LICENSE file to this repo with your preferred license (MIT or similar). If you haven't chosen one yet, consider MIT for permissive reuse.
Inspired by hobbyist LoRa projects and community irrigation controllers. Please attribute and share improvements.
If you want, I can:
- Add an example config that wires to a popular LoRa concentrator (e.g., RPi + LoRa HAT).
- Draft a minimal systemd service file and Docker Compose example for automated deployment.
- Generate TypeScript client library types & helper functions to interact with the API.