Skip to content
Merged
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
81 changes: 50 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,49 @@
MicroPython firmware for the Raspberry Pi Pico nodes that connect layout hardware — block
detectors, IO expanders, RFID readers — to MQTT, for the Westgate Hollow model railway.

Part of a four-repo control stack:
Part of the Westgate Hollow control stack:
[layout-orchestration](https://github.com/bazauto/layout-orchestration) (backend and operator
UI), [PicoDCC](https://github.com/bazauto/PicoDCC) (DCC command station),
[esp-layout-controller](https://github.com/bazauto/esp-layout-controller) (touchscreen
throttle), and this.
throttle), and this. [block-detection](https://github.com/bazauto/block-detection) is the
detector PCB this firmware reads.

The orchestrator's `docs/mqtt-contract.md` is **binding** on what these nodes publish. It is
owned there, not here.

## Layout

```
src/lib/ deployed to the board's /lib, which MicroPython puts on sys.path
tools/ bench utilities, never deployed
tests/ host pytest — stubs `machine` and `utime`, needs no hardware
scripts/ run_tests.sh
docs/ pin allocation, hardware notes, plans
src/lib/ deployed to the board's /lib, which MicroPython puts on sys.path
src/apps/io-node/ deployed to the board's root: main.py and config.py
tools/ bench utilities, never deployed
tests/ host pytest — stubs `machine` and `utime`, needs no hardware
scripts/ deploy.sh, run_tests.sh
docs/ wiring, pin allocation, startup and LED codes, broker auth, plans
```

A node imports `mqtt_at`, not `lib.mqtt_at`, because `/lib` is on the path. Tests import
device modules by exactly the name the board uses.

`src/apps/io-node/` and `src/apps/rfid-node/` do not exist yet — see the issues below.
`main.py` is the pre-split combined app, kept running until they land.
Each node keeps all of its wiring and identity in its own `config.py`. Nothing under
`src/lib/` hardcodes a pin, an address or a layout id. `src/apps/rfid-node/` does not
exist yet; see [#4](https://github.com/bazauto/layout-feedback/issues/4).

## Hardware

| Device | Bus | Notes |
|---|---|---|
| MCP23017 ×2 | I2C0, SDA=GP4 SCL=GP5, `0x20`/`0x21` | 16 pins each. Board 1 current sensing, board 2 IR — see `docs/pin-allocation.md` |
| MCP23017 ×3 | I2C0, SDA=GP4 SCL=GP5, `0x20`/`0x21`/`0x22` | 16 pins each. Board 1 current sensing, board 2 IR, board 3 point feedback. See `docs/pin-allocation.md` |
| PCF8591 ADC | I2C0, `0x48` | 8-bit, 4 channels. Currently unused |
| PN7150 NFC | I2C1, SDA=GP2 SCL=GP3, `0x28` | NCI + IRQ. The IRQ means "a message is ready", not "a tag is present" |
| TCA9548A mux | I2C1, `0x70` | Fans I2C1 out to up to 8 readers |
| ESP-AT modem | UART1, TX=GP8 RX=GP9, 9600 | Wired ethernet; reports `+ETH_GOT_IP` when ready |
| Legacy Models LM-iD.1 | board 1 inputs | The current-sensing detectors. Output stage and its two modes: `docs/block-detector-wiring.md` |
| Waveshare IR reflective | board 2 inputs | LM393, run at 3.3 V. A newer part with none of the LM-iD's ambiguity |
| Cobalt iP Digital points | board 3 (`0x22`), planned | Commanded over DCC, never MQTT. Position comes only from its `S2` changeover — `docs/point-position-feedback.md` |
| Legacy Models LM-iD.1 | board 1 inputs | The current-sensing detectors, active low. Output stage and its two modes: `docs/block-detector-wiring.md` |
| Waveshare IR reflective | board 2 inputs | LM393, run at 3.3 V, active low |
| Cobalt iP Digital points | board 3 (`0x22`), planned | Commanded over DCC, never MQTT, and can report nothing itself. Neither changeover is free (`S2` powers the frog), so the feedback source is undecided. See `docs/point-position-feedback.md` |

Polarity is set per sensor in `config.py`, because it is a property of the device and not of
the board. The `block-detection` board is active high, so a broken wire reads occupied.

## Testing

Expand All @@ -49,32 +55,45 @@ python -m pytest # or: bash scripts/run_tests.sh
```

No hardware needed. `tests/conftest.py` installs stand-ins for `machine` and `utime` before
any device module is imported, including a clock that drives timers — so debounce and
timeout behaviour is tested by advancing time rather than by sleeping.

Paths come from `pytest.ini`; the old `PYTHONPATH=.` prefix is no longer needed.
any device module is imported, including a clock that drives timers. So debounce and
timeout behaviour is tested by advancing time, not by sleeping.

## Deploying

The Pico lives on the bench machine, not on a dev machine. See `.claude/skills/deploy/SKILL.md`
for the whole procedure, including which `by-id` serial device belongs to this repo — two of
the three on that box belong to other projects.
```bash
bash scripts/deploy.sh io-node
```

The Pico is plugged into the bench machine, not a dev machine, so the script stages the files
there and drives `mpremote` over USB. It refuses to deploy if the host suite is red.
`.claude/skills/deploy/SKILL.md` has the whole procedure. That includes which `by-id` serial
device belongs to this repo: two of the three on that box belong to other projects.

The broker refuses anonymous clients. A node's MQTT password lives only on the bench and is
copied onto the board on every deploy, so it is never committed. See `docs/broker-auth.md`.

A node that cannot start flashes its failure stage on the onboard LED and retries. Solid
means it is running. The codes are in `docs/startup-and-status-led.md`.

## Status

Being restructured and brought onto the MQTT contract:
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.

Still open:

- [#1](https://github.com/bazauto/layout-feedback/issues/1) — restructure into two nodes
- [#2](https://github.com/bazauto/layout-feedback/issues/2) — meet the MQTT contract
- [#3](https://github.com/bazauto/layout-feedback/issues/3) — end-to-end bring-up, one
current-sensing and one IR sensor
- [#4](https://github.com/bazauto/layout-feedback/issues/4) — RFID restructure only, pending
hardware and `layout-orchestration#39`
- [#9](https://github.com/bazauto/layout-feedback/issues/9) — a broken sensor wire reads as
clear track. Reopened: the closed-circuit fix it rejected turns out to be available
(`docs/block-detector-wiring.md`)
- [#9](https://github.com/bazauto/layout-feedback/issues/9): a broken sensor wire reads as
clear track. The current-sensing fix is the `block-detection` board, and it is under test.
IR is still open.
- [#10](https://github.com/bazauto/layout-feedback/issues/10): the Goods Shed IR beam
oscillates with a stationary loco over it.
- Point feedback on board 3 is built (#15), but it publishes nothing until a feedback source
is chosen. See `docs/point-position-feedback.md`.
- [#25](https://github.com/bazauto/layout-feedback/issues/25): TLS on the broker.
- [#4](https://github.com/bazauto/layout-feedback/issues/4): RFID. Restructure only, pending
hardware and `layout-orchestration#39`.

Plan: `docs/plans/2026-08-23-node-split.md`. Working agreement: `CLAUDE.md`.
Working agreement: `CLAUDE.md`.

## Licence

Expand Down
Loading