Skip to content

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.

  1. Take the line WITHOUT its hash field.
  2. Canonical JSON: keys sorted, UTF-8 (not escaped), no spaces: separators , and :.
  3. 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.

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.

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.