Skip to content

feat(fire-sim): Modbus TCP simulator for 100-building load tests#1

Open
kiro-agent[bot] wants to merge 4 commits into
masterfrom
feat/fire-sim-modbus-simulator
Open

feat(fire-sim): Modbus TCP simulator for 100-building load tests#1
kiro-agent[bot] wants to merge 4 commits into
masterfrom
feat/fire-sim-modbus-simulator

Conversation

@kiro-agent

@kiro-agent kiro-agent Bot commented May 20, 2026

Copy link
Copy Markdown

This pull request was created by @kiro-agent on behalf of @maheshkumar-iisc 👻

Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro autonomous agent


What

A Modbus TCP simulator that mimics 100 STM32-backed fire panels on one machine, so the central poller, time-series pipeline, and dashboard can be developed and load-tested without 100 real buildings.

Key features

  • One Modbus TCP server per simulated building, on its own port (defaults 5020–5119)
  • Input register map matches the planned firmware contract: fire/supervisory/power state, 32-bit heartbeat counter, uptime, firmware version
  • HTTP control plane (FastAPI) for scripted scenarios:
    • per-building: fire, supervisory, power, frozen, offline
    • fleet: storm, random-offline, random-freeze, chaos, reset
  • Failure injection covers every mode the central poller must detect:
    • fire alarm / fault, supervisory trouble, mains -> battery -> fail
    • MCU freeze (server still answers, heartbeat stops — silent failure case)
    • network down (server stops listening)
  • Bundled async load-test client with p50/p95/p99 latency
  • Docker + docker-compose for reproducible runs

Verification

Tested on a single host with all 100 panels:

[round 1/5] elapsed=0.13s ok=100 fail=0 p50=5.5ms p95=7.0ms p99=7.0ms
...
[round 5/5] elapsed=0.12s ok=100 fail=0 p50=3.7ms p95=4.2ms p99=4.2ms

# After injecting 25 alarms + 5 offline + 5 frozen:
[round 1/1] elapsed=0.12s ok=95 fail=5 ...
  ! BLDG-030: connect_failed   <- offline buildings detected
  ! BLDG-040: connect_failed
  ! BLDG-054: connect_failed
  ! BLDG-086: connect_failed
  ! BLDG-090: connect_failed

state summary:
  alarm=25 fault=0 frozen=5 offline=5

Try it

cd fire-sim
python -m venv .venv && source .venv/bin/activate
pip install -e .
fire-sim --buildings 100 --base-port 5020 --api-port 9090
# in another shell:
fire-sim-poll --buildings 100 --rounds 5
bash scripts/fullrun.sh   # baseline + failure injection + reset

Or docker compose up --build.

Known limitations

  • Latency injection (artificial slow responses) is not yet implemented — only freeze and offline. Easy to add as a follow-up.
  • One process serves all simulated buildings; for >500 panels per host you'd want to shard across processes.
  • Tested against pymodbus 3.8.6 + Python 3.9; should work on 3.10+ unchanged.

Next steps once this lands

This is the load-test bed. Logical follow-ups:

  1. Build the async poller service (the production counterpart that will consume this)
  2. Pipe events into NATS JetStream / Kafka
  3. Stand up TimescaleDB schema + ingestion
  4. FastAPI + WebSocket UI

kiro-agent and others added 4 commits May 20, 2026 17:29
… tests

- Async Modbus TCP servers (one per simulated building, ports 5020+)
- Input register map matching the planned STM32 firmware contract
  (fire/supervisory/power state, 32-bit heartbeat, uptime, fw version)
- HTTP control plane (FastAPI) for scripted scenarios:
    * per-building: fire/supervisory/power/frozen/offline
    * fleet: storm, random-offline, random-freeze, chaos, reset
- Failure injection covers all the modes the central poller must detect:
    * fire alarm / fault / supervisory / power loss
    * MCU freeze (server up, heartbeat stops)
    * network down (server stops listening)
- Bundled async load-test client reports per-round p50/p95/p99 latency
- Smoke test on a single host: 100 buildings, full poll round in ~130ms,
  p99 ~5-9ms, fault injection observed correctly by the polling client
- Docker + docker-compose for reproducible runs

Co-authored-by: maheshkumar-iisc <74917498+maheshkumar-iisc@users.noreply.github.com>
Comprehensive reference for firmware engineers, poller developers,
and QA. Covers:
- Input registers (FC 04): fire/supervisory/power state, 32-bit heartbeat,
  uptime, zone masks, detector counts, voltages, temperatures, events
- Holding registers (FC 03): building identity, serial, HW revision, config
- Write register (FC 06): optional reverse-heartbeat watchdog kick
- Timing & performance requirements
- Error handling matrix
- Safety/compliance notes
- Visual register map diagram
Complementary reference to the register map spec, covering:
- High-level system topology (panel/STM32 server <-> central poller client)
- Modbus TCP frame anatomy (MBAP header + PDU)
- Sequence diagram of a full read transaction with TCP handshake
- Byte-level worked example: reading the first 8 input registers
- Exception/error response format
- Function code cheat sheet (FC 03/04/06 used in this project)
- Address-space conventions (1-based notation vs 0-based on-wire offsets)
- Healthy connection state machine with circuit-breaker behaviour
- Where Modbus stops and the rest of the architecture takes over
3 files only:
  config.py  — all settings (buildings, ports, timeouts, register offsets)
  poller.py  — reads one building, decodes registers, detects state changes
  main.py    — asyncio loop, one task per building, signal handling

- Clear heading comments explain what each file owns
- Inline TODO markers show exactly where DB / NATS code gets added later
- Tested against fire-sim: detects NORMAL→ALARM and MAINS→BATTERY in real time
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant