# Pacemaker simulator: a HEART-embedded system you can run and watch

**A demonstration only.** It simulates a patient, a pacemaker and its HEART. It is not a medical device, uses no real patient data, and must never be used for anything medical.

One Python file, `pacemakerSimulator.py`, with only the standard library. Run it, register it on [HEART Hub](https://heartcortex.ai/monitor) with the code it prints, and watch its health on your own private page while it lives a simulated life: day and night, walks, slow-heart pauses, atrial fibrillation, electrical noise, a lost transmitter link, a broken lead, a loss of capture and an ageing battery.

The case study behind it, step by step for a real product, is [`guides/Pacemaker.md`](../guides/Pacemaker.md).

**On a real device:** [Pacemaker, for real](https://heartcortex.ai/docs/real-devices/Pacemaker.md) shows the hardware we assume, where the HEART runs, the code to change, how to build and install it, the factory steps and what the owner sees. For illustration only.


| File | What it is |
|---|---|
| `pacemakerSimulator.py` | The simulator: patient, pacemaker, HEART, local HEART API, HEART Hub reporting |
| `test_e2e.py` | End-to-end test against a monitor site on your own computer |
| `README.md` | This file |

---

## 1. Download and run

You need Python 3.8 or newer. Nothing to install.

```sh
python3 pacemakerSimulator.py --scenario all
```

It starts at once and prints three things:

```
  HEART Hub: register this simulated pacemaker
    1. Sign in at https://heartcortex.ai/login with your email (a 6-digit code is sent to you).
    2. Open https://heartcortex.ai/register?id=3f9c0a71b2d4e865
    3. Enter the claim code  K7Q2M-9XMBA-3RTVW-HP6DZ
    4. Watch it at https://heartcortex.ai/device/3f9c0a71b2d4e865  (private to you until you share it)

10:26:01 HEART API on http://127.0.0.1:43278/heart/v1/pulse  (owner key for private views: ...)
10:26:16 rate    72/min (own) · paced  0.0% · lead   559 ohm · threshold 0.71 V · battery  95.20 mo · link up · HEART healthy
```

Every 15 seconds it prints one line of what the simulated implant measures. When something goes wrong, its HEART says so:

```
10:28:37 HEART critical system.main-function-lost: Pacing is not capturing the heart. Pacing at 2.5 V, but the
         capture threshold is 3.1 V or the lead is not carrying the pulse. Contact the clinic now.
10:29:12 HEART resolved   system.main-function-lost: pacing is not capturing the heart
```

Stop it with Ctrl-C. The device ID, claim code and key are kept in `pacemaker-monitor.json` (readable only by you), so the same simulated device comes back next time. Delete that file to make a new device.

## 2. Register it and watch it on HEART Hub

1. **Sign in.** Open `https://heartcortex.ai/login` and enter your email address. A 6-digit code arrives; type it in. There are no passwords.
2. **Register.** Open the `register?id=...` link the simulator printed, and enter the **claim code**. The claim code is the proof that you hold the device: it is shown only on the simulator's console, and the site holds only its hash.
3. **Watch.** Your device's page, `https://heartcortex.ai/device/<ID>`, shows its status (healthy, degraded or failing) and its open and recent alerts, each with the plain-language meaning of its fault code. All your devices are at `https://heartcortex.ai/devices`.
4. **Share, if you want.** The page is private until you make a share link or make it public.
5. **Silence.** Stop the simulator, or wait for the staged transmitter-link loss: after three missed Pulse intervals (90 seconds) the page shows a **Flatline**, and you get one email. The site checks for silent devices every 5 minutes, so the email can take a few minutes more. When it reports again, you get one more.

Until you register it, the site stores nothing: the simulator's reports are answered "not registered yet" and dropped. After registering, it can take up to five minutes for the first report to arrive, or less as soon as something changes.

**What the site sees.** Only the public Pulse and public alerts, and it keeps only the status and timing and each alert's fault code, severity and times. Never a heart rate, a lead measurement, a rhythm episode or anything private. A real pacemaker maker should run its own monitor site rather than send patients' alerts to a public one: see the privacy warning in the case study, Step 8.

## 3. The staged faults

With `--scenario`, each fault starts at a set time, lasts a while, and is then "fixed at the clinic", so you can watch an alert open, escalate and resolve. Times are in simulated seconds: at `--speed 1` they are real seconds.

| Scenario | Starts | Lasts | What you see |
|---|---|---|---|
| `noise` | 1 min | 45 s | `sensors.noisy` (warning): noise taken for heartbeats |
| `link` | 3 min | 6 min | `connectivity.link-lost` (warning). No reports while the link is down: the page shows a Flatline and you get an email, then it recovers |
| `lead` | 10 min | 4 min | `sensors.out-of-range` (error): lead impedance about 3000 ohms, a likely fracture |
| `capture` | 15 min | 3 min | `system.main-function-lost` (critical): a slow-heart pause, and pacing that does not capture |
| `battery` | at once | to the end | `power.battery-low` (error) within a minute, `power.battery-failing` (critical) about 25 minutes later: aged 4000 times faster |
| `all` | | | All of the above |

Without `--scenario`, faults still happen now and then on random timers, as in life, but rarely: noise every 30 to 90 minutes, a link loss every 2 to 6 hours.

## 4. Options

| Option | Default | Meaning |
|---|---|---|
| `--scenario` | none | `noise`, `link`, `capture`, `lead`, `battery` (comma-separated) or `all` |
| `--speed` | 1 | Simulated seconds per real second. `--speed 5` runs the staged faults in about 4 minutes (too fast for Flatline emails, which need real minutes) |
| `--monitor-url` | `https://heartcortex.ai` | Any HEART Hub site, including your own. `http` only for `127.0.0.1` |
| `--no-monitor` | | Report to no site: local only |
| `--state-file` | `pacemaker-monitor.json` | Where the device ID, claim code and key are kept |
| `--port` | 43278 | The local HEART API (falls back to 7685; `0` for none) |
| `--seed` | random | Repeat the same simulated life |
| `--duration` | until Ctrl-C | Stop after this many real seconds |

## 5. The local HEART API

The simulator also serves the HEART API on your own computer, as a real system would:

```sh
curl http://127.0.0.1:43278/.well-known/heart-card.json         # the Card: what it is
curl http://127.0.0.1:43278/heart/v1/pulse                       # public health
curl http://127.0.0.1:43278/heart/v1/arrhythmias                 # public alerts: codes only
curl http://127.0.0.1:43278/heart/v1/properties                  # public values: battery status only
KEY=...   # the owner key it printed
curl -H "Authorization: Bearer $KEY" http://127.0.0.1:43278/heart/v1/properties    # all values
curl -H "Authorization: Bearer $KEY" http://127.0.0.1:43278/heart/v1/arrhythmias   # alerts with explanations
curl -H "Authorization: Bearer $KEY" http://127.0.0.1:43278/heart/v1/rhythm        # private rhythm episodes
curl -X PUT http://127.0.0.1:43278/heart/v1/properties           # 403: HEART never changes pacing
```

Every message it serves is a valid HEART message: `test_e2e.py` checks each one against the official schemas.

## 6. How the code is built

```
Body  ──►  Pacemaker  ──(read only)──►  Heart  ──►  local HEART API      (127.0.0.1)
patient     the therapy                  watches     └►  HEART Hub    (outbound only)
```

| Part | In the file | In a real product |
|---|---|---|
| `Body` | The simulated patient: heart rate, movement, temperature, each on its own random timer, with episodes that come and go | The real patient |
| `Pacemaker` | The simulated therapy: senses, paces, rate response; a battery, lead, capture threshold and transmitter link that age, drift and fail | The implant and its certified software. **Never part of HEART** |
| `Heart` | Reads the pacemaker's values; `RULES` turn them into alerts with fault codes; `escalate()` tells a person; serves only what each caller may see | **The code you add**, on the home transmitter |
| `monitor()` | Hello, then reports to HEART Hub; follows the site's `nextSec`; stops for a wrong key; says hello again if forgotten | The same, unchanged |
| `handler()` | The HEART API: Card, Pulse, alerts, properties, rhythm; every change refused | The same, behind TLS on a network |

Nothing in `Heart` can write to `Pacemaker`: there is no setter, no command and no capability. That is the design, not an omission.

## 7. What code to add to make a real product HEART-embedded

For a real pacing system, HEART runs on the **home transmitter** (or the patient's phone app), never in the implant: see Step 1 of the case study. Pick a starting point, then add four things.

**Starting points**

| Your transmitter | Start from |
|---|---|
| Embedded Linux | This file's `Heart`, `monitor()` and `handler()`; or the Python template `heart-templates/templates/python`; or the Rust Core `heartd` (`heart-core-rs`) for HEART Full |
| A microcontroller with Wi-Fi | The ESP32 Full starter `heart-templates/templates/esp32-full` (its signal table, main-job check and Monitor reporting) |
| A phone app | The Python or JavaScript template, inside the app |

**The four things you add**

1. **Read the implant.** Replace `Body` and `Pacemaker` with a reader for the telemetry your transmitter already receives. It must be read only: HEART gets values, never a way to send anything to the implant.

   ```python
   class Telemetry:
       """CHANGE: fill these from your transmitter's existing telemetry. Read only."""
       def refresh(self):
           t = your_transmitter.latest()          # your code
           self.paced, self.captured = t.pacing_needed, t.capture_confirmed
           self.impedance_now, self.threshold_now = t.lead_impedance_ohm, t.capture_threshold_v
           self.noise, self.link_down = t.noise_detected, not t.implant_in_range
           self.months = t.battery_months_left
   ```

2. **Your rules and thresholds.** Edit `RULES` and `EXPLAIN` with the limits from your implant's own specification. Keep the standard fault codes (Part 3), so every clinic system and monitor site reads them the same way.

3. **Your escalation route.** Change `escalate()` to flag the transmission to the clinic, and to light or sound the transmitter so the patient knows to call, with no internet needed.

4. **Your privacy table.** Go through `properties()` and give every value its level (Step 3 of the case study). Health data is `private` or `internal`; only status and fault codes are `public`. Keep patient names and numbers off the transmitter.

Then keep `monitor()` as it is, store the device ID, claim code and key in the transmitter's secure storage, and print the claim code and a QR code of `https://<your monitor site>/register?id=<ID>` on a label in the box. Serve the API over TLS (or keep it on loopback), run the HEART-Rating client and Part 9's tests, and claim HEART Basic, Autonomy Level A1.

## 8. Test it

From the project's top folder, with Node 20+ and Python 3 with `pip install --user "jsonschema[format]>=4.18" referencing`:

```sh
python3 pacemakerSimulator/test_e2e.py
```

It starts a HEART Hub site on this computer, runs the simulator with every staged fault at 5x speed, signs in, registers the device through the site's own form, and checks for about four minutes that every message is valid, public views hold only public data, nothing can change pacing, every staged fault reaches the owner's page with its fault code, no measurement reaches the site, and the page stays private.

---

Licence: Apache-2.0. Part of HEART, the open standard at [heartcortex.ai](https://heartcortex.ai).
