Skip to Content
DevelopersSimulated bring-up

Simulated bring-up

Until a real Gardyn is on the bench, Phase 1 runs against a simulated unit. tests/sim/bringup.mjs starts the stack exactly as it runs on a Pi, with the hardware swapped out:

  • hald --sim with a hal.toml for a fully fitted unit: the stock sensor set, an EZO pH probe, dose_1 with dosing enabled, and one camera;
  • an in-process MQTT broker (aedes) in place of Mosquitto;
  • the built core server, connected through the real MqttHalClient (HAL_MQTT_URL, HAL_UNIT_ID, HAL_HTTP_URL) with its background services on;
  • an in-process S3 server for the mirror and the nightly backups, and the real restore helper, whose systemctl calls come back to the harness (tests/sim/fake-systemctl.mjs), so it can stop and start hald and core.
cargo build --manifest-path hal/Cargo.toml -p hald pnpm build pnpm test:sim-bringup # HALD_BIN=<path> to test another binary

CI runs it as Sim Bring-up on dev and on PRs touching hal/, src/, server/ or tests/sim/.

Checklist

#CheckThrough
1hald --check accepts the unit config and plans the expected capabilitiesCLI
2Core sees hald online with the right unit id, model, sensors and actuatorsGET /api/system
3Telemetry flows from hald through MQTT into core (air, water distance, pH)GET /api/sensors
4Home Assistant discovery is retained for every entity, including dose_faulthomeassistant/#
5A light command round-trips and hald echoes the new statePOST /api/light
6Pump duty is clamped to the guard (30 %) and the run stops at its boundPOST /api/pump/run-once
7A still is fetched over hald’s HTTP server and storedPOST /api/camera/upper/capture
8Both on-device analysers score that still on the worker threadGET /api/vision/captures/{id}, /api/vision/status
9The dataset export streams a tar with the manifest, tables and stillsGET /api/datasets/export
10scripts/analysis-worker.mjs posts results that compare next to the on-device ones; ingest is off without a tokenPOST /api/analyses/results, GET /api/vision/compare/1
11EZO calibration reaches the probe; an unconfigured probe returns 412POST /api/sensors/{id}/calibrate
12Exhausting the doser raises dose_fault (not pump_fault); acknowledging it clears the latch in haldMQTT actuator/dose_1/set, POST /api/alerts/{id}/acknowledge
13A probe added from the app goes live (readings, inventory, HA entity) and is removed again; removing a hal.toml probe is refusedPOST/DELETE /api/devices/sensors
14An armed Autopilot rule drives the light through hald; the kill switch releases itPOST /api/automations, GET /api/light/owner, PUT /api/autopilot/enabled
15A bucket backup lands under the mirror prefix, and a download carries the database, hal.toml, the broker password and hald’s sensors.jsonPOST /api/backup/run, GET /api/backup/bucket, GET /api/backup/download against an in-process S3 server
16Backup → wipe → restore: with the database, hald’s state and the broker password deleted and a stock hal.toml, the fresh core offers Welcome. Restoring the bucket backup through the real helper brings back the settings, schedule, grow cycle, Autopilot rule, app-added probe (live in hald again), hal.toml, password and LAN listener. The helper stops and starts both services in orderPOST /api/backup/browse, POST /api/backup/restore-from-bucket, scripts/seedypod-restore.cjs with tests/sim/fake-systemctl.mjs
17A bundle from newer software is refused with the version to install, and the unit is untouchedPOST /api/backup/restore → 409
18Undo puts the fresh card back; a second undo redoes the restorePOST /api/backup/undo
19Killing hald is noticed through the broker’s last willhal_offline alert

What it found

The first run showed that core never subscribed to hald’s topics. The factory awaits connectAsync(), so the client is already connected when MqttHalClient registers its connect handler, and that handler never ran. Every real install would have shown the hardware as offline. The client now also subscribes at construction time when it is handed an already-connected client.

What it does not cover

The simulation checks the software chain (contract, guards, MQTT, core, Home Assistant). It cannot check anything physical: GPIO and I²C addresses, driver timing and CRCs, INA219 calibration, button and GPIO25 polarity, the PWM overlay, camera exposure, or the Raspberry Pi OS install steps (including the SD card wear settings, sudo for the restore helper and systemd-run). Those stay in the Phase 1 checklist for a real unit.


Sources: Gardyn hardware map (Phase 1 checklist) · hald — the hardware daemon · ADR 0007: HAL contract v1

Imported from docs/hardware/sim-bringup.md in seedypod/core. Edit it there, not here.

Last updated on