Trace machine-to-machine device exchange
Verify registry admission, request and response, public MQTT fan-out, delayed replay and command refusal.

Blueprint Bina: I want you to inspect the failed case and the record that explains the next decision.
Predict the reading, then compare it with the measurement.
Python 3 in your browser (JupyterLite)
Python · no installOpen the notebook in your browser and run each Python cell; no install or account is needed.
Three ways to run: use JupyterLite here with no install; run main.py locally from the downloadable lab folder; or open the same notebook in Google Colab.
Steps
Step 1
- Do
- In the JupyterLite notebook, run the Step 1 cell to register devices before routing.
- You will see
- Input: Run python main.py and inspect step 1: register devices before routing. Observed heading: "Register devices before routing" Observed intermediate: "pump-controller-1 site-operations plant-1 v1 0" Observed rule or check: "Also required: sequence message_type quality payload; identity lists are lab policy." Observed result: "STEP 1 registry admitted=2 quarantined=1" Expected from the existing capture: pump-controller-1 ACCEPT; temperature-1 ACCEPT. STEP 1 registry admitted=2 quarantined=1
- Why it matters
- Admission needs a trusted identity, allowed capabilities and an explicit schema. Unknown devices remain quarantined before normal routing. Intermediate evidence: "pump-controller-1 site-operations plant-1 v1 0" gives a value or state to inspect. Comparison cue: "Also required: sequence message_type quality payload; identity lists are lab policy." supplies the rule or adjacent evidence for that check. Result: "STEP 1 registry admitted=2 quarantined=1" records how this input was classified.

Step 1 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 2
- Do
- In the JupyterLite notebook, run the Step 2 cell to trace the legacy rr-001 request and response.
- You will see
- Input: Run python main.py and inspect step 2: trace the legacy rr-001 request and response. Observed heading: "Trace the legacy rr-001 request and response" Observed intermediate: "result=ACK running/local_auto sequence=42" Observed rule or check: "Response age=1 s <= freshness rule 2 s; request correlation=rr-001" Observed result: "STEP 2 request response matched" Expected from the existing capture: trace=rr-001 gateway=site-gateway-1 device=pump-controller-1; request read_state sent=2026-01-01T00:00:03Z deadline=00:00:05Z. STEP 2 request response matched
- Why it matters
- The host separates device state from platform acceptance. Correlation, age and target state explain why this specific response may be used. Intermediate evidence: "result=ACK running/local_auto sequence=42" gives a value or state to inspect. Comparison cue: "Response age=1 s <= freshness rule 2 s; request correlation=rr-001" supplies the rule or adjacent evidence for that check. Result: "STEP 2 request response matched" records how this input was classified.

Step 2 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 3
- Do
- In the JupyterLite notebook, run the Step 3 cell to separate timeout, bounded retry and authorization refusal.
- You will see
- Input: Run python main.py and inspect step 3: separate timeout, bounded retry and authorization refusal. Observed heading: "Separate timeout, bounded retry and authorization refusal" Observed intermediate: "t=10 viewer requests stop -> REFUSE authorization: viewer cannot control" Observed rule or check: "The public broker transports telemetry; authorization is local lab policy." Observed result: "STEP 3 retry succeeded; unauthorized control refused" Expected from the existing capture: Logical clock; no sleeping or real actuator commands.; t=6 attempt=1 deadline=8 -> TIMEOUT no response within 2 s. STEP 3 retry succeeded; unauthorized control refused
- Why it matters
- A timeout says no timely answer arrived; a refusal records a policy decision. A bounded retry can recover the first without bypassing the second. Intermediate evidence: "t=10 viewer requests stop -> REFUSE authorization: viewer cannot control" gives a value or state to inspect. Comparison cue: "The public broker transports telemetry; authorization is local lab policy." supplies the rule or adjacent evidence for that check. Result: "STEP 3 retry succeeded; unauthorized control refused" records how this input was classified.

Step 3 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 4
- Do
- In the JupyterLite notebook, run the Step 4 cell to publish through the real public mqtt broker.
- You will see
- broker=test.mosquitto.org:1883; MQTT 3.1.1 QoS=0 clean-session; topic=iotclass/labs/m2m/<unique-run>/telemetry/temperature. STEP 4 public-broker fanout and late join PASS
- Why it matters
- Decoupled consumers receive the same real broker event. A late subscriber gets last-known state, whose age must still be checked before use.

Step 4 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 5
- Do
- In the JupyterLite notebook, run the Step 5 cell to disconnect upstream and persist three events.
- You will see
- Input: Run python main.py and inspect step 5: disconnect upstream and persist three events. Observed heading: "Disconnect upstream and persist three events" Observed intermediate: "45 11 2" Observed rule or check: "Durable queue uses SQLite commit; close/reopen proves records survive connection loss." Observed result: "STEP 5 outage queue persisted=3" Expected from the existing capture: Local outage simulation: gateway cannot send during t=10..19.; sequence event_time_s persisted_depth. STEP 5 outage queue persisted=3
- Why it matters
- The legacy outage exercise requires three accepted events in durable storage. Closing and reopening SQLite proves the queue is not just a Python list. Intermediate evidence: "45 11 2" gives a value or state to inspect. Comparison cue: "Durable queue uses SQLite commit; close/reopen proves records survive connection loss." supplies the rule or adjacent evidence for that check. Result: "STEP 5 outage queue persisted=3" records how this input was classified.

Step 5 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 6
- Do
- In the JupyterLite notebook, run the Step 6 cell to replay in sequence and suppress the duplicate.
- You will see
- Input: Run python main.py and inspect step 6: replay in sequence and suppress the duplicate. Observed heading: "Replay in sequence and suppress the duplicate" Observed intermediate: "44 10 20 SUPPRESS duplicate" Observed rule or check: "Advance the queue only in the same transaction as platform acceptance." Observed result: "STEP 6 replay delayed=3 duplicate=1 remaining=0" Expected from the existing capture: sequence original_time received_time decision; 44 10 20 ACCEPT delayed. STEP 6 replay delayed=3 duplicate=1 remaining=0
- Why it matters
- Original event times distinguish delayed telemetry from current readings. A durable unique key prevents repeat delivery from creating duplicate platform records. Intermediate evidence: "44 10 20 SUPPRESS duplicate" gives a value or state to inspect. Comparison cue: "Advance the queue only in the same transaction as platform acceptance." supplies the rule or adjacent evidence for that check. Result: "STEP 6 replay delayed=3 duplicate=1 remaining=0" records how this input was classified.

Step 6 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 7
- Do
- In the JupyterLite notebook, run the Step 7 cell to reject stale commands and invalid translations.
- You will see
- Input: Run python main.py and inspect step 7: reject stale commands and invalid translations. Observed heading: "Reject stale commands and invalid translations" Observed intermediate: "735 /10 73.5 percent ACCEPT" Observed rule or check: "Rule: retain provenance and quarantine out-of-range values; do not clamp." Observed result: "STEP 7 expiry and translation gates PASS" Expected from the existing capture: cmd-004 expires=15 reconnect=20 -> REFUSE expired command; source=fieldbus-adapter address=register_40011 canonical=tank_level. STEP 7 expiry and translation gates PASS
- Why it matters
- Commands can expire while telemetry remains worth replaying. Translation needs explicit units, scaling and quarantine reasons so bad source values are not hidden. Intermediate evidence: "735 /10 73.5 percent ACCEPT" gives a value or state to inspect. Comparison cue: "Rule: retain provenance and quarantine out-of-range values; do not clamp." supplies the rule or adjacent evidence for that check. Result: "STEP 7 expiry and translation gates PASS" records how this input was classified.

Step 7 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 8
- Do
- In the JupyterLite notebook, run the Step 8 cell to keep a diagnostic snapshot and decision record.
- You will see
- gateway=site-gateway-1 signal=simulated-good power=simulated-on; firmware=lab-v1 reboot_count=0 last_error=upstream-outage last_contact=20. STEP 8 validation PASS
- Why it matters
- The diagnostic record binds device identity, gateway health and platform results. It separates what was locally simulated from the public MQTT delivery that actually ran.

Step 8 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
Chapter checks
These questions refer to the chapter’s examples. Use the return links to review their answers.
A campus platform will onboard ventilation controllers, lighting gateways, energy meters, and maintenance apps. Which identity decision belongs first in its platform boundary record?
Return to the chapter’s knowledge checkA gateway translates field-bus readings into platform events. Which records best show the platform boundary is well designed?
Return to the chapter’s knowledge check