<!-- SPDX-License-Identifier: CC-BY-4.0 -->
# Farm stack foundation layer: spec v0

Oct 7, 2026 · @anun333

## Purpose and scope

Layer zero is the data and device contract every farm app builds on: identity, time, records, observations, devices, messaging and commands. It ships no end-user features. Its job is to make sensor data trustworthy, farmer-owned and portable.

It is for small and mid-size diversified farms, DIY ag builders and researchers, and for developers building apps on their behalf.

In scope:

- A canonical data model for records and sensor observations
- Device, calibration and health tracking
- A message format, link drivers and store-and-forward delivery
- A command contract with acknowledgements and node-side limits
- Local-first storage, export and revocable sharing
- A driver SDK, conformance tests and import/export adapters

Out of scope (built on top): rules and alerts, irrigation logic, dashboards and UI, robots and guidance, ML.

Done means three different apps (an irrigation controller, a livestock water monitor and a research data logger) can each be built without changing layer zero.

## Design principles

Every rule below traces to a pain point farmers or field deployments report.

| Principle | What it means in layer zero | Pain point it answers |
| --- | --- | --- |
| Auditable | Every reading, command and decision lands in an append-only log | Silent failures, distrust of black boxes |
| Farmer-owned | Local-first, full export in open formats, revocable sharing grants | Fear of losing data ownership, vendor lock-in |
| Honest data | Raw value, calibrated value and a quality flag on every reading; nothing silently cleaned | Drift, stuck sensors, misleading recommendations |
| Works offline | Store-and-forward delivery; nodes keep local schedules and limits | Poor rural connectivity |
| Modular | Every driver and adapter is optional; removing one breaks nothing | Too many disconnected tools |
| Standards first | farmOS conventions, OGC SensorThings, ISOBUS, Modbus, MQTT, LoRaWAN | Interoperability, the top barrier after cost |
| Safety below software | Hardwired interlocks and fail-safe power-off for anything that moves or pumps | Liability, machinery and water risk |
| Reimplement, don't copy | Build from published specs and documented models | GPL entanglement |

## Identity

Every entity gets a UUIDv7, generated offline with no coordination. UUIDv7 is time-sortable, so logs and records order naturally.

- **Labels are not keys.** Human names ("north bed") are separate, editable labels. Renaming breaks nothing.
- **Radio nodes send short IDs.** A 16-byte UUID in every LoRa packet wastes airtime. Nodes send their native short ID (LoRaWAN DevEUI or Meshtastic node number) plus a channel index. The gateway maps that pair to the full point ID through the registry.
- **Per-device sequence numbers.** Every message carries a counter that increments per node. It drives deduplication and gap detection.
- **Multi-site ready.** Every entity belongs to a site. IDs never collide across farms, so data can be merged or shared without rewriting keys.

Open: whether a registry-assigned 16-bit point index should replace the channel index on very constrained links.

## Time

Every observation carries four time fields, so ordering survives outages and bad clocks.

| Field | Meaning |
| --- | --- |
| measured_at | When the sensor took the reading (UTC) |
| received_at | When the gateway received it (UTC) |
| seq | The node's sequence number |
| clock_quality | gps, network, rtc_unsynced, or none |

- Nodes with no clock send "measured N seconds ago." The gateway computes measured_at and sets clock_quality to none, so the time is marked as estimated.
- Everything is stored in UTC. The farm's timezone is a property of the site and is used only for display.
- measured_at maps to SensorThings phenomenonTime. received_at has no exact equivalent there, so it travels in the observation's parameters.

## Core data model

Layer zero has two models joined by shared IDs: records follow farmOS conventions, sensing follows OGC SensorThings, and both write to one log.

A sensor asset and its Thing share one ID, as do a field or bed asset and the feature of interest it represents. Each observation belongs to a datastream, which ties one sensor, one observed property and one unit to its Thing.

Externally, records are served through a farmOS-compatible API and sensor data through a SensorThings-compatible API, so existing tools in both ecosystems work on day one.

### Observation record

An observation keeps the raw value, the calibrated value and a quality flag together, so nothing is ever silently cleaned.

| Field | Type | Notes |
| --- | --- | --- |
| id | UUIDv7 | |
| datastream | UUIDv7 | Points to one sensor, one observed property, one unit |
| feature | UUIDv7 | The field, bed, tank or animal observed |
| measured_at, received_at, seq, clock_quality | see Time | |
| raw | number or string | Exactly as the node sent it |
| result | number or string | After calibration |
| calibration | UUIDv7 | The calibration record applied; null if none |
| quality | enum | good, suspect, stuck, stale, out_of_range |

- Units use one formal unit-code system (UCUM is the leading candidate), declared once on the datastream.
- Observed properties reference a shared vocabulary by URI, not free text.
- Recalibration never rewrites history. A new calibration produces new derived results tagged with the new calibration ID; the original stays.
- Quality is set by health checks in the foundation (see Devices) and can be overridden by a person, which is itself logged.

## Devices, calibration and health

Sensors are maintained assets with their own history, because drift and hardware failure are the most common reasons field data goes wrong.

Each device record holds:

- Install location, depth and orientation, with dates
- Firmware version (also stamped into each reading's provenance)
- Calibration history: method, reference used, coefficients, date, who did it
- Battery and signal trend, last seen
- A maintenance log: cleaned, moved, replaced, recalibrated

Health checks run in the foundation and set each reading's quality flag:

| Check | Flags | Example |
| --- | --- | --- |
| Staleness | stale | No reading for 3× the expected interval |
| Flatline | stuck | Identical values for longer than physically plausible |
| Range | out_of_range | Soil moisture above saturation |
| Rate of change | suspect | A jump no real process could produce |
| Battery and signal trend | device warning | Voltage falling toward cutoff |

Thresholds live on the datastream with sensible defaults, so apps can tune them without code. Every check result is logged, so a false alarm can be traced and the threshold fixed.

Pumps, wells and power sources are resource assets with declared capacities (flow, pressure, watts) that modules book time against, so two modules can never overdraw one well. Tasks for people are pending logs, following farmOS, rather than a separate entity.

## Messaging and link layer

One compact message format rides over any link; the gateway expands it into full observations.

A reading's path:

1. The node samples, applies nothing, and packs a compact binary message: short node ID, channel index, seq, age or timestamp, raw value.
2. The node buffers it locally until the link confirms delivery (store-and-forward).
3. A link driver carries it: Meshtastic, LoRaWAN via ChirpStack, wifi, cellular or wired.
4. The gateway resolves IDs through the registry, computes times, applies calibration and health checks, and drops duplicates by node ID plus seq.
5. The full observation is written to the append-only log and the time-series store, then published on the bus (MQTT for edge devices; bridged to ROS 2 for robots and machinery).

Rules:

- Link drivers are interchangeable and share one interface: send, receive, link health.
- Messages must fit the smallest link's payload. LoRa airtime is the binding limit; in EU 868 MHz bands, duty cycle caps transmit time at about 36 seconds per hour per channel.
- Gaps in seq are logged as gaps, never filled with guesses.
- Encoding: a schema-defined binary format (Protocol Buffers or CBOR are candidates), versioned with the spec.

Modules watch for new observations and events through a stable subscription API; nothing above layer zero polls.

## Command contract

Commands state a desired outcome, carry their own safety bounds, and are safe to send twice, so a lost or repeated message can never leave a pump or valve in a bad state.

Every command carries:

- A UUIDv7 command ID (repeats with the same ID are ignored)
- The target point and the desired state, not a toggle ("valve 3 open", never "flip valve 3")
- A maximum duration or expiry, after which the node reverts to its safe state on its own
- Who issued it and why (a user, a regimen, a rule), for the log

A command can also carry a dry_run flag. The node validates it, checks its limits and acknowledges, but does not act, so simulation and backtesting run through the real code path.

The node answers with an acknowledgement: accepted, rejected (with reason), or completed. No acknowledgement within the link's timeout means the command is treated as not delivered.

Node-side limits are configured on the node, not sent with each command: maximum runtime, minimum off-time, interlocks with other outputs, and the safe state on power loss or link loss. The server cannot override them.

Concepts follow SensorThings Part 2 (Tasking) where they fit, rather than inventing new ones. Hardwired interlocks for machinery and high-energy loads sit below all of this and are outside software's reach.

## Storage, ownership and sharing

The farm's own machine holds the authoritative copy; the cloud is optional and never required to operate.

- **Append-only log.** Every change to records, every observation and every command is an event. Current state is derived from the log, so history can always be replayed and audited.
- **Two stores.** High-frequency sensor data goes to a time-series store with downsampling and retention rules. Records (assets, logs, plans) go to the main store. Years of readings never slow everyday records.
- **Full export.** Everything exports in open formats (CSV, JSON, GeoJSON for geometry) at any time, with no fee and no support ticket.
- **Revocable sharing.** Sharing is a grant: what data, to whom, for what purpose, until when. Grants are logged and can be revoked; revocation stops future access.
- **Offline sync.** Devices that edit while disconnected sync later. Conflict rules are written into the spec: last writer wins per field for simple values; both versions kept and flagged for a person when they conflict in a way that matters.
- **Required records.** The log model must be able to produce certification and compliance records (organic, produce-safety water testing, pesticide application) without custom code. Exact requirements vary by country and program.

Automated actions are logged as decision events: the observations used as inputs, the rule or model and its version, the output, and the trust level it ran under. Any action can be traced back to why it happened.

## Security

Secure by default, because no farm will harden its network later.

- **Device onboarding.** Each device gets its own key when it is registered. Unregistered devices are heard but not trusted, and their data is quarantined until someone approves them.
- **Users and roles.** Owner, manager, worker, viewer and service account to start. Commands are authorized per role and per point.
- **Signed firmware.** Updates are signed and the version is recorded. LoRa links are too slow for over-the-air updates, so the spec plans for physical update visits on those nodes.
- **Encrypted links where the link allows it.** LoRaWAN and Meshtastic both offer link encryption; wired and wifi links use TLS to the gateway.
- **No default passwords**, no open admin ports, and the log records every login and permission change.

Modules are permissioned like users. Each declares what it may read, write and command, scoped per point; the farmer grants it at install and the foundation enforces it, which is what makes third-party modules safe to run.

## Driver SDK and conformance tests

Anyone can add hardware without touching the core, and a passing conformance suite is what makes a driver or implementation official.

Two driver kinds, one plugin interface each:

- **Device drivers** turn a protocol into raw readings and accept commands: Modbus RTU/TCP, SDI-12, I2C, 1-Wire, 4–20 mA and 0–10 V analog, BLE, local HTTP APIs, CAN/J1939/ISOBUS, NMEA and NTRIP.
- **Link drivers** move messages: Meshtastic, LoRaWAN via ChirpStack, MQTT, wifi, cellular, wired.

A driver declares its points, units, sample interval and safe states; the foundation handles everything else. Python is the first SDK language, with Rust for the edge daemon.

The conformance suite includes:

- Simulated devices for every driver kind
- Replay of real recorded field data
- Fault injection: drift, stuck values, dead batteries, dropped and repeated messages, clock jumps
- Command safety tests: lost acks, duplicates, link loss mid-command

Reliability claims require at least one full growing season of field data, not a few weeks in the lab.

## Interoperability adapters

Adapters let farmers bring in what they already have and let existing tools read layer zero on day one.

| Adapter | Direction | When | Purpose |
| --- | --- | --- | --- |
| CSV and spreadsheets | In and out | v0 | Bring in existing records; nothing re-entered by hand |
| farmOS API | In and out | v0 | Use farmOS v4 as the records backend at first; existing farmOS clients keep working |
| SensorThings API | Out | v0 | Standard sensor-data endpoint for GIS and research tools |
| Local weather stations | In | v0 | Ecowitt local API, WeatherFlow broadcast, Davis AirLink |
| ISOBUS task data (ISOXML) | In and out | Later | Prescriptions and as-applied data from machinery |

Open: target SensorThings 2.0 once published in 2026, or ship on 1.1 and migrate.

## Layers above

Everything above layer zero is optional and depends only on the layers below it.

| Layer | What lives there | Must get right |
| --- | --- | --- |
| 1. Core services | Rules and alerts, scheduler for sequences and regimens, notifications, query and analytics | Rules backtested on past seasons before going live; resource conflicts resolved; alerts escalate and need acknowledgement |
| 2. Domain modules | Irrigation, greenhouse climate, livestock, storage and cold chain, field operations, robotics, inputs and inventory, harvest and traceability, compliance reports | Built only on layers 0 and 1; each one removable |
| 3. Decision support | Agronomic models, forecasting and ML, cost per field or crop, simulation | Models versioned and calibrated per site; confidence shown; suspect and stuck readings excluded |
| 4. Apps and interfaces | Phone field app, desktop planning, SMS and voice, a plain-language assistant | Offline, glove- and sunlight-friendly, bilingual; the assistant cites readings and never commands without approval |
| 5. Ecosystem | Module registry, multi-farm and co-op use, research sharing, buyer and certification exports | Signed modules with declared permissions; sharing only by consent |

Rules for building upward:

- **Dependencies point down only.** A module never reaches into another module, only into the layers below, so any module can be removed.
- **Trust ramp per module.** Every automation runs as advisory (it suggests), approval (a person confirms each action) or autonomous (it acts within limits). Farms move up one step at a time, and the level is recorded on every decision event.
- **Nothing above layer zero is safety-critical.** If layers 1 to 4 go down, nodes keep running their local schedules and limits.
- **Shared limits are enforced, not hoped for.** The scheduler books pumps, wells and power against the resource registry, so modules cannot fight over one well.
- **Cost visibility early.** Showing cost and return per field answers the barrier farmers name most often: unclear value.

## Versioning and governance

The spec is published separately from the code, so others can build on it or write their own implementation.

- **Semantic versions.** Breaking changes only in major versions, each with a written migration. The message format carries its version in every packet.
- **Spec, reference implementation, conformance suite** are three versioned artifacts released together.
- **Change process.** Public proposals with a comment period before a breaking change lands.
- **License.** To decide: GPL to match much of the ecosystem, or permissive (Apache 2.0 or MIT) for the spec and SDK so device makers can adopt them.
- **Liability.** Control software moves water and machinery. The project needs clear disclaimers, documented safety boundaries, and possibly a legal entity to hold the project.
- **Sustainability.** Free software with paid setup, hosting or support is a common model; decide before the first pilot.
- **Partners.** University extension services already have farmer trust and run research plots, which makes them natural pilot partners.

## Open questions

- [ ] Who is the first pilot farm, and what crop or livestock?
- [ ] License for the spec, SDK and reference implementation
- [ ] SensorThings 1.1 now, or wait for 2.0
- [ ] Unit system (UCUM?) and observed-property vocabulary (AGROVOC or another)
- [ ] Binary encoding: Protocol Buffers or CBOR
- [ ] Short point index on constrained links: channel index or registry-assigned ID
- [ ] Conflict rules for offline edits beyond last-writer-wins
- [ ] When to replace farmOS as the records backend, if ever
- [ ] Legal entity, liability coverage and sustainability model
- [ ] Rule language for layer 1: a small declarative format, or plain Python with a restricted runtime
- [ ] How modules are packaged and isolated: separate processes, containers, or WebAssembly
- [ ] Which layer 2 module ships first alongside layer zero (irrigation is the current candidate)
