3D printer simulator: a HEART-embedded system you can run and watch#
A demonstration only. It simulates a room, a 3D printer, its firmware and its HEART. It drives no real printer, heater or motor, its numbers are plausible rather than a model of any real machine, and nothing in it is a safety design.
One Python file, printer3dSimulator.py, with only the standard library. Run it, register it on HEART Hub with the code it prints, and watch its health on your own private page while it lives a simulated life: print jobs queued now and then, a hotend and a bed heating with a little overshoot, layers, progress and filament use, a room that is warmer by day and drafty now and then, and faults: a slow heater, a hot stepper driver, a stuck bed thermistor, a lost Wi-Fi link, a layer shift, a filament run-out, a thermal-runaway shutdown by the firmware and an impossible thermistor reading.
This HEART only watches. The printer's firmware owns heating, motion, pausing and its thermal-runaway protection. HEART notices, explains and tells a person; every change request to it is refused.
The case study behind it, step by step for a real product, is guides/3D-Printer.md.
On a real device: 3D Printer, for real 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 |
|---|---|
printer3dSimulator.py | The simulator: room, printer and firmware, 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.
python3 printer3dSimulator.py --scenario all
It starts at once and prints three things:
HEART Hub: register this simulated 3D printer
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:47:01 HEART API on http://127.0.0.1:43307/heart/v1/pulse (owner key for private views: ...)
10:47:16 heating · hotend 41.5/210 · bed 30.8/60 Cel · fan 0% · 0.0% layer 0 · filament 800 g · driver 27.9 Cel · wifi up · HEART healthy
Every 15 seconds it prints one line of what the simulated printer measures. When something goes wrong, its HEART says so:
10:59:21 HEART critical system.main-function-lost: The firmware has shut the heaters down. The printer's firmware cut
the heaters by itself (its thermal-runaway protection: the hotend reading stayed below target while the heater
ran at full power. The thermistor may have come loose from the heater block). The job was aborted. HEART only
reports this: check the hotend before resetting the printer.
11:00:41 HEART resolved system.main-function-lost: the firmware has shut the heaters down
Stop it with Ctrl-C. The device ID, claim code and key are kept in printer3d-monitor.json (readable only by you), so the same simulated printer comes back next time. Delete that file to make a new device.
2. Register it and watch it on HEART Hub#
- Sign in. Open
https://heartcortex.ai/loginand enter your email address. A 6-digit code arrives; type it in. There are no passwords. - Register. Open the
register?id=...link the simulator printed, and enter the claim code. The claim code is the proof that you hold the printer: it is shown only on the simulator's console, and the site holds only its hash. - 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 athttps://heartcortex.ai/devices. - Share, if you want. The page is private until you make a share link or make it public.
- Silence. Stop the simulator, or wait for the staged Wi-Fi 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 temperature, a job name, a layer or anything else the printer measures.
3. The staged faults#
With --scenario, each fault starts at a set time, lasts a while, and is then "fixed by a person", so you can watch an alert open, escalate and resolve. With any scenario, one staged print job (gift-box-for-mum.gcode, a file name that shows why job names are private) is queued at once so the faults have a print to happen to; random jobs start after about 17 minutes. Times are in simulated seconds: at --speed 1 they are real seconds.
| Scenario | Starts | Lasts | What happens | What HEART raises |
|---|---|---|---|---|
heater | at once | 3 min 20 s | The hotend heater gives under half its power: the first heat-up takes far longer than normal | system.main-function-degraded (warning) after 2 minutes of heating |
stepper | 4 min | 2 min | The stepper drivers' fan stops: the driver heatsink climbs past 80 Cel | performance.too-hot (warning) |
stuck | 5 min | 1 min 30 s | The bed thermistor reading freezes at one value. The firmware still sees "at target" and does not notice | sensors.stuck (error) after 30 s with no change |
wifi | 5 min 30 s | 3 min | The printer's Wi-Fi link is down. No reports reach the site; printing goes on | connectivity.link-lost (warning), shown on the page when the link is back. At --speed 1 the page also shows a Flatline |
layer | 7 min | 1 min | The X driver reports stalls and current spikes: the head skipped steps | actuators.position-mismatch (error) |
runout | 8 min 40 s | 2 min | The spool runs out. The firmware's run-out sensor pauses the job and parks the head; a person loads a new spool | system.main-function-lost (error) |
runaway | 11 min 40 s | 2 min | The hotend thermistor slips out of the heater block. The firmware sees the reading fall while heating and cuts the heaters (thermal-runaway protection); the job is aborted. A person reseats it and resets | system.main-function-lost (critical) |
range | 13 min 50 s | 1 min | The hotend thermistor connector opens: the reading is about -38 Cel. The firmware refuses to heat | sensors.out-of-range (error) |
all | All of the above |
Without --scenario, the printer lives a random life: a job every 20 to 90 minutes, each 10 minutes to 2 hours long in PLA or PETG; drafts every 30 minutes to 2 hours; a Wi-Fi drop every 2 to 6 hours; a layer shift every 6 to 24 hours of printing; and a filament run-out whenever the spool is used up (a person loads a new one 10 to 40 minutes later).
Two more rules are in the HEART and do not need staging: performance.overshoot (the hotend reads more than 15 degrees over its target after heating up to it; normal PID overshoot is 1 to 4 degrees), and the firmware's other halts (its heating watch and its maximum temperature), reported as system.main-function-lost.
4. Options#
| Option | Default | Meaning |
|---|---|---|
--scenario | none | heater, stepper, stuck, wifi, layer, runout, runaway, range (comma-separated) or all |
--speed | 1 | Simulated seconds per real second. --speed 5 runs the staged faults in about 3 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 | printer3d-monitor.json | Where the device ID, claim code and key are kept |
--port | 43307 | 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 printer would:
curl http://127.0.0.1:43307/.well-known/heart-card.json # the Card: what it is
curl http://127.0.0.1:43307/heart/v1/pulse # public health
curl http://127.0.0.1:43307/heart/v1/arrhythmias # public alerts: codes only
curl http://127.0.0.1:43307/heart/v1/properties # public values: the printer state only
KEY=... # the owner key it printed
curl -H "Authorization: Bearer $KEY" http://127.0.0.1:43307/heart/v1/properties # all values, with the job name
curl -H "Authorization: Bearer $KEY" http://127.0.0.1:43307/heart/v1/arrhythmias # alerts with explanations
curl -H "Authorization: Bearer $KEY" http://127.0.0.1:43307/heart/v1/jobs # private: current, queued and past jobs
curl -H "Authorization: Bearer $KEY" -X PUT http://127.0.0.1:43307/heart/v1/properties # 403 capability-not-allowed
Every change request (PUT, POST, PATCH, DELETE, on any path, with or without the key) is refused with 403 capability-not-allowed: HEART never heats, moves, pauses or resumes the printer. Every message it serves is a valid HEART message: test_e2e.py checks each one against the official schemas.
Privacy of every value
| Value | Property | Privacy | Why |
|---|---|---|---|
| Healthy or not; open fault codes | Pulse, alerts | public | The only things HEART Hub ever receives |
| Printer state: idle, heating, printing, paused, halted | printer_state | public | Like the light on the front of the printer: anyone in the room sees it |
| Hotend temperature and target | hotend_temp, hotend_target | internal | For the owner and the people who look after the printer |
| Bed temperature and target | bed_temp, bed_target | internal | |
| Part fan speed | fan_speed | internal | |
| Job progress, layer, layers in the job, print time | job_progress, layer, layers_total, print_time | internal | Reveal how the printer is used, not who |
| Filament remaining | filament_remaining | internal | |
| Stepper driver temperature | stepper_driver_temp | internal | |
| Room temperature | room_temp | internal | |
| Wi-Fi link | wifi_link | internal | |
| Job name | job_name | private | File names can be personal: a name, a gift, a medical part |
| Job history and events | /heart/v1/jobs | private | What was printed and when |
6. How the code is built#
Room ──► Printer + firmware ──(read only)──► Heart ──► local HEART API (127.0.0.1)
drafts, jobs, PID heating, watches └► HEART Hub (outbound only)
Wi-Fi thermal protection
| Part | In the file | In a real product |
|---|---|---|
Timer, Episode | Values that change on random timers; things that happen now and then, for a while | |
Room | Room temperature with day and night, drafts, the home Wi-Fi | The real room |
PID, Printer | The simulated printer and firmware: job queue, heating with a PID-like controller (full power far from target, a little overshoot near it), layers, progress, filament, run-out pause, driver temperatures and stall reports, and the firmware's own protection: heating watch, maximum temperature and thermal runaway | The printer and its firmware (Marlin, Klipper, RepRapFirmware or your own). Never part of HEART |
Heart | Reads the printer's values; RULES turn them into alerts with fault codes (two rules may share a code, each with its own alert); EXPLAIN says what happened and what to do; escalate() tells a person; serves only what each caller may see | The code you add, on the printer's host computer |
monitor() | Hello, then reports to HEART Hub; follows the site's nextSec; stops for a wrong key; says hello again if forgotten; nothing while Wi-Fi is down | The same, unchanged |
handler() | The HEART API: Card, Pulse, alerts, properties, jobs; every change refused | The same, behind TLS on a network |
Nothing in Heart can write to Printer: there is no setter, no command and no capability. When the firmware stops for thermal runaway, the heaters are already off before HEART notices; HEART's job is to say so, clearly, to the right person.
7. What code to add to make a real product HEART-embedded#
For a real printer, HEART runs on the host that already talks to the firmware: a Raspberry Pi or similar running the print server (OctoPrint, Klipper with Moonraker, or the maker's own), or the printer's own Linux mainboard. It never runs inside the motion and heater firmware. See Step 1 of the case study. Pick a starting point, then add four things.
Starting points
| Your printer | Start from |
|---|---|
| Linux host or Linux mainboard | This file's Heart, monitor() and handler(); or the Python template heart-templates/templates/python; or the Rust Core heartd (heart-core-rs/crates/heartd) for HEART Full |
| A host reading the firmware over a serial line | The bridge helper heart-templates/templates/bridge (heart_bridge.py, serial line to HTTP server) |
| A Wi-Fi microcontroller beside the mainboard | The ESP32 Full starter heart-templates/templates/esp32-full (its signal table, main-job check and Monitor reporting) |
| A print server written in JavaScript | The Node template heart-templates/templates/javascript-node |
The four things you add
- Read the printer. Replace
Room,PIDandPrinterwith a reader for what your host already receives: the firmware's temperature reports (M105, or auto-reports withM155), job progress from the print server, the run-out sensor state, the stepper drivers' temperature and stall flags (for example TMC drivers' over-temperature prewarning and StallGuard), the firmware's halt messages. It must be read only.
``python class Printer: """CHANGE: fill these from your firmware and print server. Read only: never send G-code from here.""" def refresh(self): s = your_print_server.status() # your code, e.g. Moonraker's /printer/objects/query self.state = s.state # idle, heating, printing, paused, halted self.hotend, self.hotend_target = s.extruder.temperature, s.extruder.target self.bed, self.bed_target = s.heater_bed.temperature, s.heater_bed.target self.runout, self.halted_reason = s.filament_sensor.empty, s.halt_reason self.driver_temp, self.shift_layer = s.driver_temp_max, s.stall_layer ``
- Your rules and thresholds. Edit
RULESandEXPLAINwith your printer's own numbers: normal heat-up time, driver limits, which readings are impossible. Keep the standard fault codes (Part 3), so every dashboard and monitor site reads them the same way. Leave protection where it is: the firmware's thermal-runaway, heating-watch and maximum-temperature checks must work with HEART stopped. - Your escalation route. Change
escalate()to show the alert on the printer's screen, sound its buzzer, and send it to the owner's phone through the print server's own notifications, with no internet needed for the first two. - Your privacy table. Go through
properties()and give every value its level (the table above, and Step 3 of the case study). Job names and job history areprivate; only status and fault codes arepublic.
Then keep monitor() as it is, store the device ID, claim code and key in the host's protected storage, and print the claim code and a QR code of https://<your monitor site>/register?id=<ID> on a label on the printer. 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.
If you later want a command. This HEART offers none. A later version could map one firmware command, such as setting the nozzle temperature, as a capability with guards: see spec/examples/custom/gcode-printer-set_nozzle.json (G-code M104, a 0 to 260 Cel range, allowed only when idle or printing, at most 60 an hour, physical: true, callers: a human). Heating is physical, so even then it would need a person present to approve, and the firmware's own limits would still apply.
8. Test it#
From the project's top folder, with Node 20+ and Python 3 with pip install --user "jsonschema[format]>=4.18" referencing:
python3 printer3dSimulator/test_e2e.py
It starts a HEART Hub site on this computer (port 8807), runs the simulator with every staged fault at 5x speed (local API on port 43307), signs in, registers the device through the site's own form, and checks for about three and a half minutes that every message is valid against the schemas, public views hold only public data, the job name and job history are private, every change is refused with 403 capability-not-allowed, every staged fault reaches the owner's page with its fault code, no temperature or job name reaches the site, and the page stays private.
Licence: Apache-2.0. Part of HEART, the open standard at heartcortex.ai.