Canonical one-page contract

Publish one measurement.

Every device follows the same topic grammar and five-field JSON payload. We mandate what gets published. You own how.

Address

Topic grammar

garden/{zone}/{device-type}/{device-id}/{measurement}

Use lowercase ASCII, hyphens, and no spaces. A team prefix is two letters; a device ID combines that prefix with two digits. GardenSpine.h reads zone, device type, and device ID from config.h, then adds the measurement passed to publish().

garden/greenhouse-1/climate-node/gm-01/temperature
garden/entrance/counter/gk-01/count
garden/greenhouse-2/ladybug/lb-07/status

Body

Payload

Every accepted message becomes UTF-8 JSON with one measurement and exactly five fields. This is what you see on the dashboard. You never have to write it by hand.

The line you writespine.publish("temperature", reading, "celsius");
FieldTypeRule
device_idstringMust equal the device ID segment in the topic.
tsstring | nullISO 8601 UTC ending in Z, or null. The broker stamps arrival time when it is null.
valuenumber | stringUse a number for measurements and a string for status-like values.
unitstringMust exactly match the registered unit.
fwstringFree-form firmware version. It is informational.

Single source of truth

Registry

Zones

greenhouse-1greenhouse-2entrancepondcompostoutdoor-1visitor-centreworkshop

Device types

climate-nodelight-nodemoisture-nodefrost-nodedraft-noderain-nodecompost-nodemotion-nodecounterbench-nodepath-nodepollinator-nodeladybugchorusnight-nodekiosktowersonifierwhispererspine-doctorsleepy-node

Measurements

MeasurementUnitTypeAccepted range or values
temperaturecelsiusnumber-40–80
humiditypercent-rhnumber0–100
light-levelpercentnumber0–100
light-directionenumstringleft, balanced, right
soil-moisturepercentnumber0–100
water-levelpercentnumber0–100
counteventsnumber0–1000000
occupancyenumstringfree, occupied, uncertain
durationsecondsnumber0–86400
distancecentimetresnumber0–500
motionenumstringstill, moving
tiltenumstringlevel, tilted
day-lengthminutesnumber0–1440
batterypercentnumber0–100
nodes-alivedevicesnumber0–100
selectionenumstringplant-1, plant-2, plant-3, plant-4
moodenumstringcalm, alert, warm, cold, busy
beatcountnumber0–1000000000
statusenumstringok, hiding, low-battery, error, warning, active, sleeping

Example registration

prefix: gm
zone: greenhouse-1
device type: climate-node
device: gm-01
reads: garden/entrance/counter/gk-01/count

Creativity stays open

Free measurements

A team may publish an unregistered measurement under its own device prefix. The backbone stores it and marks it not in registry. It is not rejected. Declare the topic in the registry when it becomes part of the shared contract.

Never fail silently

Rejected messages

Every rejected message is stored with its topic, payload, reason, and a beginner-readable fix.

ReasonHint shown to the student
payload_not_jsonThe message body isn't valid JSON. Common cause: single quotes instead of double quotes, or a trailing comma.
missing_fieldYour JSON is missing unit. All five fields are required — see the spec.
device_id_mismatchThe topic says gm-02 but the payload says gm-01. They must match — you probably copied the config from another node.
unit_mismatchYou sent unit: "C" but temperature expects celsius. Units are spelled out.
value_type_wrongvalue should be a number for temperature, but arrived as text. Drop the quotes.
value_out_of_range1284 °C is outside the plausible range. Raw sensor value instead of a converted one?
timestamp_unparseablets must be ISO 8601 UTC like 2026-08-14T09:32:00Z, or null if your device has no clock. null is fine.
topic_malformedTopic needs five segments: garden/zone/device-type/device-id/measurement.
zone_not_registeredZone greenhous-1 isn't in the registry — check the spelling, or open a PR to add it.
device_type_not_registeredThis device type is not registered. Copy it from your charter configuration.
device_not_registeredThis device ID is not registered. Copy it from the credential slip.
device_registration_mismatchThe topic's zone or device type does not match this device's registry entry.
unexpected_fieldThe payload has a field outside the five-field contract. Remove the extra field.
rate_limitedThis device published more than 60 messages in a minute and is being throttled. Slow your loop down.