p4n4-emu (tools/emu/) is a workstation hardware emulator. It applies Docker resource constraints (CPU, RAM, disk I/O) and optional QEMU ARM64 emulation as a Compose overlay on top of the existing p4n4 stacks — making a developer’s workstation behave like target edge hardware without modifying any production files.
p4n4-emu generates a docker-compose.emu.yml overlay per stack and passes it to:
docker compose -f docker-compose.yml -f *.emu.yml up -d
The overlay injects deploy.resources.limits (CPU, memory) and optional blkio_config (disk I/O) per service. Production files are never modified.
| Profile | CPU | Memory | Disk R/W | Arch |
|---|---|---|---|---|
rpi4 |
4 cores | 3.5 GB | 50 MB/s | arm64 |
rpi5 |
4 cores | 7 GB | 100 MB/s | arm64 |
mcu-class |
1 core | 256 MB | 10 MB/s | x86_64 |
nuc |
4 cores | 14 GB | 200 MB/s | x86_64 |
cat /sys/fs/cgroup/cgroup.controllers--arch arm64cd tools/emu
uv sync # or: pip install -e .
Verify:
uv run p4n4-emu --help
uv run p4n4-emu setup --check-only
All items should show OK. A cgroup v2 warning means CPU/memory limits won’t be enforced.
Required only when using an arm64 profile (e.g. rpi4/rpi5) on an x86 host:
uv run p4n4-emu setup --arch arm64
# IoT stack with Raspberry Pi 5 constraints
uv run p4n4-emu up --stack-dir ~/p4n4/stacks/iot --profile rpi5
# All stacks + synthetic sensor data
uv run p4n4-emu up --stack all --stack-dir ~/p4n4/docker --profile rpi5 --sim
Use --dry-run to inspect the generated overlay before any containers start:
uv run p4n4-emu up --stack-dir ~/p4n4/stacks/iot --profile rpi5 --dry-run
uv run p4n4-emu status --profile rpi5
uv run p4n4-emu down --profile rpi5 # stop containers
uv run p4n4-emu down --profile rpi5 --volumes # stop and remove data volumes
p4n4-emu setup [--arch arm64] [--check-only]
p4n4-emu up [--profile PROFILE] [--stack iot|ai|edge|all]
[--stack-dir PATH] [--arch arm64] [--sim] [--dry-run]
p4n4-emu down [--profile PROFILE] [--stack iot|ai|edge|all]
[--stack-dir PATH] [--volumes]
p4n4-emu status [--profile PROFILE] [--stack iot|ai|edge|all]
p4n4-emu profile list
p4n4-emu profile show <name>
p4n4-emu sim start [--interval 2.0] [--devices 1] [--mqtt-host p4n4-mqtt]
p4n4-emu sim stop
p4n4-emu sim status
The built-in simulator (--sim) publishes synthetic MQTT payloads on topics the p4n4 stack already consumes:
sensors/temperature {"value": 23.4, "unit": "C", "device": "emu-sensor-0"}
sensors/humidity {"value": 58.2, "unit": "%", "device": "emu-sensor-0"}
sensors/pressure {"value": 1012.7, "unit": "hPa", "device": "emu-sensor-0"}
sensors/raw {"values": [0.01, -0.02, 1.00], "cpu_pct": 42.3, "device": "emu-sensor-0"}
Run standalone against a local Mosquitto:
MQTT_HOST=localhost uv run python -m p4n4_emu.sim.sensor_sim
p4n4_emu.hw.gpio_stub is a drop-in RPi.GPIO replacement for running RPi5 hardware scripts on a workstation:
import sys
import p4n4_emu.hw.gpio_stub as GPIO
sys.modules["RPi"] = type(sys)("RPi")
sys.modules["RPi.GPIO"] = GPIO
Set logging.basicConfig(level=logging.DEBUG) to see pin state transitions in your terminal.
tc netem).