Getting Started with sim-hw¶
This guide walks you through setting up and running the sim-hw device simulator.
Prerequisites¶
- Python 3.11+ --- sim-hw uses modern Python features and type hints
- AWS IoT Core --- An IoT Core endpoint with Fleet Provisioning configured
- Claim certificates --- A claim certificate, private key, and root CA for Fleet Provisioning
- sim-dashboard running --- The Fastify server (port 3001) and React frontend (port 5173) should be running
- Backend running --- The Go backend (port 4000) must be running to process heartbeats and commands
Installation¶
Clone the repository and install in editable mode:
Virtual Environment
It is recommended to use a virtual environment to avoid conflicts with system packages:
Configuration¶
Create a .env file in the project root:
INKLET_IOT_ENDPOINT=xxxx-ats.iot.us-east-1.amazonaws.com
INKLET_CLAIM_DIR=/path/to/awsiot/.secrets/claim
FACTORY_SECRET=your-32-byte-hex-secret
INKLET_SIM_URL=http://localhost:3001
INKLET_UI_URL=http://localhost:5173
| Variable | Description |
|---|---|
INKLET_IOT_ENDPOINT |
AWS IoT Core data endpoint (find in AWS Console under IoT Core > Settings) |
INKLET_CLAIM_DIR |
Directory containing Fleet Provisioning claim certificates (claim.cert.pem, claim.private.key, root.pem) |
FACTORY_SECRET |
32-byte hex string used as the HMAC key for NFC signature generation |
INKLET_SIM_URL |
URL of the sim-dashboard Fastify server for framebuffer pushes |
INKLET_UI_URL |
URL of the sim-dashboard React frontend (used for browser auto-open) |
Running the Simulator¶
Start a simulated device:
This creates a device with a unique hardware ID stored in devices/kitchen/. The directory name is arbitrary --- use meaningful names to distinguish simulated devices (e.g., devices/kitchen, devices/bedroom, devices/office).
First Run: Provisioning Flow¶
On the very first run with a new --data-dir, sim-hw performs Fleet Provisioning:
1. Connects to AWS IoT Core using claim certificate
2. Requests a new device certificate via Fleet Provisioning
3. Receives: device certificate, private key, and thingName
4. Stores credentials in {data-dir}/certs/
5. Saves state to {data-dir}/state.json
6. Generates NFC payload to {data-dir}/nfc-payload
7. Disconnects and reconnects with the new device certificate
8. Begins normal operation
Provisioning is One-Time
Fleet Provisioning only happens once per data directory. The resulting certificates and Thing name are stored locally and reused on subsequent runs.
You will see output similar to:
INFO Fleet Provisioning started...
INFO Certificate created: certs/cert.pem
INFO Thing registered: inklet-a1b2c3d4
INFO Provisioning complete. Reconnecting with device certificate...
INFO Connected as inklet-a1b2c3d4
INFO NFC payload: inklet:1:a1b2c3d4-5678-9012-abcd-ef0123456789:3f7a8b2c1d9e0f4a
INFO Sending heartbeat...
INFO Subscribed to inklet/dev/inklet-a1b2c3d4/down/cmd
Subsequent Runs¶
On subsequent runs, sim-hw detects existing credentials and skips provisioning:
INFO Found existing credentials for inklet-a1b2c3d4
INFO Connected as inklet-a1b2c3d4
INFO NFC payload: inklet:1:a1b2c3d4-5678-9012-abcd-ef0123456789:3f7a8b2c1d9e0f4a
INFO Sending heartbeat...
INFO Subscribed to inklet/dev/inklet-a1b2c3d4/down/cmd
CLI Flags¶
All flags can also be set via environment variables or .env.
| Flag | Default | Description |
|---|---|---|
--data-dir |
(required) | Per-device working directory for certificates, state, and NFC payload |
--hw-id |
Auto-generated UUID | Override the hardware UUID (useful for testing specific device IDs) |
--iot-endpoint |
$INKLET_IOT_ENDPOINT |
AWS IoT Core endpoint |
--claim-dir |
$INKLET_CLAIM_DIR |
Path to claim certificate directory |
--heartbeat-interval |
30 |
Heartbeat interval in seconds |
--factory-secret |
$FACTORY_SECRET |
HMAC secret for NFC payload signing |
--sim-url |
http://localhost:3001 |
sim-dashboard Fastify server URL |
--ui-url |
http://localhost:5173 |
sim-dashboard frontend URL |
--no-browser |
false |
Do not automatically open the browser |
--once |
false |
Send one heartbeat and exit (useful for scripting and testing) |
Examples:
# Run with custom heartbeat interval
python -m eink_hw --data-dir devices/kitchen --heartbeat-interval 10
# Run with a specific hardware ID
python -m eink_hw --data-dir devices/test --hw-id "00000000-0000-0000-0000-000000000001"
# Run without opening browser
python -m eink_hw --data-dir devices/kitchen --no-browser
# Send one heartbeat and exit
python -m eink_hw --data-dir devices/kitchen --once
Data Directory Layout¶
Each --data-dir represents a single simulated device and contains:
devices/kitchen/
├── hw-id # Hardware UUID (plain text)
├── state.json # Provisioning state
├── nfc-payload # NFC binding payload
└── certs/
├── cert.pem # Device certificate (from Fleet Provisioning)
├── private.key # Device private key (from Fleet Provisioning)
└── root.pem # AWS IoT Root CA
hw-id¶
Plain text file containing the device's hardware UUID. Generated automatically on first run or set via --hw-id.
state.json¶
Tracks provisioning state and the assigned Thing name.
nfc-payload¶
The NFC binding string, generated on every startup.
certs/¶
X.509 certificates obtained during Fleet Provisioning. These are used for all subsequent MQTT connections.
Certificate Security
The certs/ directory contains private key material. Do not commit it to version control. The .gitignore in the sim-hw repository excludes the devices/ directory by default.
Running Multiple Devices¶
To simulate multiple devices, run separate instances with different data directories:
Each instance gets its own hardware ID, Thing name, certificates, and NFC payload. All devices appear independently in the sim-dashboard and backend.
Troubleshooting¶
Connection refused¶
Ensure the AWS IoT Core endpoint is correct and your network allows outbound connections on port 8883.
Provisioning fails¶
Verify that:
- The claim certificates in
--claim-dirare valid and not expired - The Fleet Provisioning template is configured in AWS IoT Core
- The
inklet-claim-policyis attached to the claim certificate
sim-dashboard not receiving frames¶
Ensure sim-dashboard is running on the expected port (default: 3001). Check the --sim-url flag.