Lab: First Workbook¶
This exercise turns the first lesson from Reading Machine Buses into a runnable inspection: read one checked-in artifact, decode its visible CAN fields, and state where the evidence stops.
The input is the first non-comment record in
samples/first-workbook/raw/can_frames.txt:
0.000000,0x123,false,false,01 02 03 04
It is synthetic, passive, and offline. The timestamp and frame-looking fields are teaching evidence, not a live capture or a procedure for interacting with a bus.
Run The Exercise¶
From the repository root, after uv sync --all-groups --frozen:
uv run python docs/labs/first_workbook.py
Expected output:
artifact: samples/first-workbook/raw/can_frames.txt
observed record: 0.000000,0x123,false,false,01 02 03 04
This is a Classical CAN data frame with a standard 11-bit identifier 0x123. Its DLC is 4, so it carries 01 02 03 04. Its arbitration priority depends on the captured identifier, frame format, and data-versus-remote arbitration bits.
Evidence supports: identifier 0x123, standard frame format, DLC 4, and payload bytes 01 02 03 04.
Evidence does not support: payload meaning, sender identity, freshness, or physical truth.
Read The Program¶
"""Inspect the first synthetic artifact in the schoolbus workbook."""
from pathlib import Path
from schoolbus.protocols.can import CanFrame
def main() -> None:
"""Read and explain the workbook's first passive CAN record."""
root = Path(__file__).resolve().parents[2]
artifact = root / "samples" / "first-workbook" / "raw" / "can_frames.txt"
observed_record = next(
line
for line in artifact.read_text(encoding="utf-8").splitlines()
if line and not line.startswith("#")
)
_timestamp, identifier, extended, remote, data = observed_record.split(",", maxsplit=4)
frame = CanFrame(
identifier=int(identifier, 16),
data=bytes.fromhex(data),
extended=extended == "true",
remote=remote == "true",
)
print(f"artifact: {artifact.relative_to(root)}")
print(f"observed record: {observed_record}")
print(frame.explain())
frame_format = "extended" if frame.extended else "standard"
payload = frame.data.hex(" ").upper()
print(
f"Evidence supports: identifier {frame.identifier:#x}, {frame_format} frame format, "
f"DLC {frame.dlc}, and payload bytes {payload}."
)
print(
"Evidence does not support: payload meaning, sender identity, freshness, or physical truth."
)
if __name__ == "__main__":
main()
The program deliberately reads the artifact from disk. The decode therefore remains connected to the evidence under inspection instead of silently replacing it with hard-coded values.
Evidence Boundary¶
The record supports a narrow observation: under the stated text-record convention,
the artifact describes a standard Classical CAN data frame with identifier 0x123,
DLC 4, and four payload bytes. It does not name the sender or signals, establish
when the record was captured, or prove anything about a physical system.
A DBC, J1939 profile, CANopen object dictionary, or other source might add semantic authority. The frame itself does not select or validate that authority.
Continue Through The Workbook¶
The sample pack also contains synthetic passive artifacts for J1939, ARINC 429, MIL-STD-1553, UART, NMEA 0183, Modbus RTU, and DBC:
samples/first-workbook/README.mdsamples/first-workbook/manifest.ymlsamples/first-workbook/raw/samples/first-workbook/expected/
Each expected record states what the artifact can show and what still requires semantic authority. None of the workbook files defines live capture setup, serial IO, SocketCAN use, Modbus polling, replay steps, or transmit helpers.
Continue with the Classical CAN teaching guide. It expands this first record into arbitration, cadence, and semantic-authority questions while keeping the evidence synthetic and passive.