Skip to content

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:

git clone https://github.com/Inklet-2026/sim-hw.git
cd sim-hw
pip install -e .

Virtual Environment

It is recommended to use a virtual environment to avoid conflicts with system packages:

python -m venv .venv
source .venv/bin/activate  # macOS/Linux
.venv\Scripts\activate     # Windows
pip install -e .

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:

python -m eink_hw --data-dir devices/kitchen

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.

a1b2c3d4-5678-9012-abcd-ef0123456789

state.json

Tracks provisioning state and the assigned Thing name.

{
  "provisioned": true,
  "thingName": "inklet-a1b2c3d4",
  "provisionedAt": "2026-01-15T10:30:00Z"
}

nfc-payload

The NFC binding string, generated on every startup.

inklet:1:a1b2c3d4-5678-9012-abcd-ef0123456789:3f7a8b2c1d9e0f4a

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:

python -m eink_hw --data-dir devices/kitchen
python -m eink_hw --data-dir devices/bedroom
python -m eink_hw --data-dir devices/office

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

ERROR  Connection failed: [Errno 111] Connection refused

Ensure the AWS IoT Core endpoint is correct and your network allows outbound connections on port 8883.

Provisioning fails

ERROR  Fleet Provisioning failed: ...

Verify that:

  • The claim certificates in --claim-dir are valid and not expired
  • The Fleet Provisioning template is configured in AWS IoT Core
  • The inklet-claim-policy is attached to the claim certificate

sim-dashboard not receiving frames

WARNING  Failed to push framebuffer: Connection refused

Ensure sim-dashboard is running on the expected port (default: 3001). Check the --sim-url flag.