The line format (plant-line-v1)
PRIVATE. Patent-pending. plant_line.py in this repo is the exact file the cloud runs; when in doubt, it wins.
One line = one result for one unit (or one sensor reading)
Section titled “One line = one result for one unit (or one sensor reading)”{"format":"plant-line-v1","plant":"demo-w3","line":"W3","station":"CAM6","source":"camera","box":"CAM6", "unit":"DEMO-0040","result":"NG","missing":"L1;L2;L7","ts":"2026-11-16T10:05:12+07:00","seq":41, "line_id":"CAM6-...","prev":"<hash of line 40>","hash":"<this line's hash>"}Needs result (OK or NG), value (a number as text) or state (a discrete reading). Unknown fields are refused.
Never allowed out of the plant: any person name or id, operator, images, frames, video, faces (dropped at the box).
Every text field is at most 512 characters. The connector cuts long free text (missing, unit_of_measure, state,
check) and marks it [truncated] rather than dropping the unit.
The hash (any language can recompute it)
Section titled “The hash (any language can recompute it)”- Take the line WITHOUT its
hashfield. - Canonical JSON: keys sorted, UTF-8 (not escaped), no spaces: separators
,and:. hash= lowercase hex SHA-256 of those bytes.
json.dumps(line_without_hash, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")prev = the hash of the previous line from the same box; the first line uses 64 zeros.
line_id (answers cite it)
Section titled “line_id (answers cite it)”station + "-" + first 16 hex of sha256("plant|line|station|source|check|unit|X"), with X = ts exactly as the
source wrote it, or offset:<byte offset> when the source had no time. So a re-read never makes a second id.
ts is ISO 8601 with an offset: +07:00, +0700 or Z, optional fraction. No offset: the box stamps the plant
zone and sets ts_flag: "assumed". No time at all: the box uses the read time and sets ts_flag: "read".
A camera-PC clock more than 5 minutes fast is kept and flagged on the cloud side, and shows as a plant warning.
Order and the chain
Section titled “Order and the chain”seq rises on each box and is never reused. Send in seq order. The cloud accepts a line only if prev equals the
head it holds for that box; otherwise 409 (see openapi.yaml). Refused content with an intact hash still moves the
chain, so one bad vendor row never stops the stream.