Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ bench check that covers it instead. Do not leave it silently untested.

## Current state (2026-08)

The IO node is **deployed and working end to end** (#1, #2, #3). Both Goods Shed sensors
publish contract readings that the orchestrator trusts, and the block follows them.
The IO node is **deployed and working end to end** (#1, #2, #3). Installed: `cs---engine-shed-2`
(a `bazauto/block-detection` channel) and `ir---goods-shed`. Goods Shed has lost its detector,
so that block can go occupied but not clear (`docs/pin-allocation.md`).

Point position feedback on board 3 (`0x22`) is **built but publishes nothing yet** (#15): the
expander answers on the bus but `POINTS_INSTALLED` is empty, and **the feedback source is
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ means it is running. The codes are in `docs/startup-and-status-led.md`.

## Status

The IO node is deployed and working end to end. Both Goods Shed sensors, one current-sensing
and one IR, publish contract readings that the orchestrator trusts.
The IO node is deployed and working end to end. Two sensors publish contract readings: the
Engine Shed 2 current detector and the Goods Shed IR beam.

Still open:

Expand Down
12 changes: 9 additions & 3 deletions docs/pin-allocation.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,26 @@ wire it to its allocated pin, add its id to the installed list, deploy.

## Currently installed

Two, both on the **Goods Shed** block:
Two, on different blocks (2026-09-25):

| Board | Pin | Port | Chip pin | `sensorId` |
|---|---|---|---|---|
| 1 (`0x20`) | 8 | GPB0 | 1 | `cs---goods-shed` |
| 1 (`0x20`) | 7 | GPA7 | 28 | `cs---engine-shed-2` |
| 2 (`0x21`) | 8 | GPB0 | 1 | `ir---goods-shed` |

`cs---goods-shed` came out of `INSTALLED` when its LM-iD.1 was removed. Goods Shed now has
only its beam, and an `ir_position` `clear` is a no-op downstream, so **that block can go
occupied but can never clear** until a detector is back on board 1 pin 8. The orchestrator
also stops hearing `cs---goods-shed`, marks it untrusted, and treats the block as `unknown`
(occupied). Both are the fail-safe direction.

**No points.** Board 3 is fitted and answers on the bus, but no feedback source has been
chosen or wired, so `POINTS_INSTALLED` is empty and nothing is published on any `point/*/reading`. Bring points up
**one at a time**: every point fault kind Safe-Stops the whole layout, including a fault on a
point no route holds, so flipping all six to `positionFeedback: "required"` at once produces a
layout that halts on the first flaky contact with five other unproven points to rule out.

Two things follow from this being the pair brought up first, both of which paid off:
Goods Shed's two sensors were the pair brought up first, and two things followed from that, both of which paid off:

- **Goods Shed is the only block with both a CS and an IR sensor installed**, so the bring-up
exercised the occupancy derivation rather than one sensor in isolation. That proved its
Expand Down
9 changes: 5 additions & 4 deletions src/apps/io-node/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,14 @@
"active_low": WAVESHARE_IR_ACTIVE_LOW},
)

# Physically wired, and therefore the only sensors published. Both on Goods Shed, which
# is the one block with both a detector and a beam — so the bring-up exercises the
# occupancy derivation rather than one sensor in isolation. Every board 1 entry above is
# Physically wired, and therefore the only sensors published. The Goods Shed detector
# was taken out of service 2026-09-25 when its LM-iD.1 was removed; its beam stays. A
# block whose only live sensor is IR can go occupied but never clear, because an
# `ir_position` `clear` is a no-op downstream. Every board 1 entry above is
# already on the bazauto/block-detection polarity, so bringing another detector online is
# only a matter of adding its id here.
INSTALLED = (
"cs---goods-shed",
"cs---engine-shed-2",
"ir---goods-shed",
)

Expand Down
35 changes: 20 additions & 15 deletions tests/test_io_node_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,21 @@ def test_the_layout_id_is_real():
assert config.LAYOUT_ID == "c4e587aa-478d-46ab-a1df-9dd8359fc040"


def test_both_installed_sensors_are_the_goods_shed_pair():
"""The one block with both a detector and a beam, so the bring-up exercises the
occupancy derivation rather than one sensor in isolation."""
assert set(config.INSTALLED) == {"cs---goods-shed", "ir---goods-shed"}
def test_every_installed_sensor_is_on_the_board_its_prefix_names():
"""`cs---` is current sensing on board 1, `ir---` is IR on board 2. A sensor on the
wrong board would be read with the other device's polarity, upside down.

Deliberately not a snapshot of INSTALLED: bringing a sensor online is a config change
and should not also need a test edit."""
board_for = {"cs---": config.EXPANDER_CS, "ir---": config.EXPANDER_IR}
for entry in select_installed(config.SENSORS, config.INSTALLED):
prefix = entry["sensor_id"][:len("cs---")]
assert prefix in board_for, entry["sensor_id"]
assert entry["expander"] == board_for[prefix], entry["sensor_id"]

def test_installed_sensors_resolve_to_the_documented_pins():
"""docs/pin-allocation.md: both on pin 8, board 1 for CS and board 2 for IR."""
by_id = {e["sensor_id"]: e for e in select_installed(config.SENSORS, config.INSTALLED)}

assert (by_id["cs---goods-shed"]["expander"], by_id["cs---goods-shed"]["pin"]) == (0x20, 8)
assert (by_id["ir---goods-shed"]["expander"], by_id["ir---goods-shed"]["pin"]) == (0x21, 8)
def test_installed_sensors_are_not_duplicated():
assert len(set(config.INSTALLED)) == len(config.INSTALLED)


def test_pin_n_is_the_same_block_on_both_boards():
Expand All @@ -55,12 +58,14 @@ def test_pin_n_is_the_same_block_on_both_boards():


def test_the_installed_sensors_have_their_devices_polarity():
"""Goods Shed's detector is the bazauto/block-detection board, occupied reads 1; its
beam is a Waveshare IR switch to ground, triggered reads 0."""
by_id = {e["sensor_id"]: e for e in select_installed(config.SENSORS, config.INSTALLED)}

assert by_id["cs---goods-shed"]["active_low"] is config.BAZAUTO_BD_ACTIVE_LOW is False
assert by_id["ir---goods-shed"]["active_low"] is config.WAVESHARE_IR_ACTIVE_LOW is True
"""Detectors are the bazauto/block-detection board, occupied reads 1; beams are
Waveshare IR switches to ground, triggered reads 0."""
expected = {config.EXPANDER_CS: config.BAZAUTO_BD_ACTIVE_LOW,
config.EXPANDER_IR: config.WAVESHARE_IR_ACTIVE_LOW}
assert config.BAZAUTO_BD_ACTIVE_LOW is False
assert config.WAVESHARE_IR_ACTIVE_LOW is True
for entry in select_installed(config.SENSORS, config.INSTALLED):
assert entry["active_low"] is expected[entry["expander"]], entry["sensor_id"]


def test_every_current_sensor_uses_the_bazauto_detector_polarity():
Expand Down
Loading