Protective Stop is an open hardware remote for robots and automation. A PoE- or USB-powered ESP32-S3 watches a physical stop switch and reports its state to machine-side pstop logic. A STOP message, a wiring fault, or a lost heartbeat causes the machine process to request STOP.
Each machine sets its own heartbeat window; the remote normally transmits halfway through it. The network update rate is therefore configured, not fixed.
This is a protective stop, not an emergency stop. It does not replace a required emergency-stop function, including hardwired power removal where applicable. OSHWA certification covers open source hardware; it is not functional-safety certification.
The switch has two normally closed contacts, each wired to a separate GPIO loop and read by one CPU core. Each core builds the full pstop message independently; transmission occurs only when both encoded messages match exactly.
Pressing the switch opens both loops and sends STOP. A broken wire or other fault in one loop makes the cores disagree, while a stalled task or loss of all network paths prevents transmission. In either case, the machine requests STOP when its silence timeout expires.
The wire protocol and machine-side state logic come from vendored
pstop_c, which remains unchanged and follows a separate
certification track. The surrounding firmware handles sensing, networking, and
recovery.
flowchart LR
subgraph REMOTE["REMOTE - ESP32-S3"]
SW["DPST NC stop switch"]
C0["core 0: read loop A and encode"]
C1["core 1: read loop B and encode"]
CMP{"byte-for-byte comparator"}
SW --> C0 --> CMP
SW --> C1 --> CMP
end
NET(["Tailscale path<br/>WireGuard direct or DERP relay"])
subgraph MACHINE["MACHINE - robot host"]
APP["machine wrapper"]
LIB["pstop_c state machine"]
STOP["STOP request"]
APP --> LIB --> STOP
end
CMP -->|"machine-controlled heartbeat"| NET --> APP
Peers addressed directly over LAN or USB-NCM bypass Tailscale and are not WireGuard-encrypted. Tailscale peers use WireGuard, with DERP relay fallback when a direct path is unavailable. Their sockets are pinned to the tunnel, so losing the VPN causes silence instead of a plaintext downgrade. Ethernet is preferred, with USB-NCM and WiFi available as fallbacks.
| Path | Contents |
|---|---|
hardware/ |
Editable FreeCAD enclosure, printable files, BOM, wiring, and photographed assembly guide |
firmware/ |
ESP-IDF remote firmware and the dual-core sensing/comparison logic |
host/ |
Plain-C machine process and its documented configuration |
ros2/ |
ROS 2 machine node and messages |
components/ |
Embedded networking, USB tether, and pstop_c integration |
docs/ |
API, recovery, testing, networking, and safety-case documentation |
tools/ and test/ |
Protocol, chaos, soak, flashing, and static-analysis tools (see tools/README.md for the uv setup) |
Tailscale support uses
microlink, an embedded client from
Malone Technologies.
docs/QUICKSTART.md walks a fresh laptop through
the whole thing: Tailscale account and key, USB tether, build and flash,
the ROS 2 machine node, pairing, and a bench test through STOP and ARM.
The short version (ESP-IDF 5.5 required):
cp firmware/sdkconfig.credentials.example firmware/sdkconfig.credentials
$EDITOR firmware/sdkconfig.credentials # Tailscale auth key + admin password
cd firmware && . /path/to/esp-idf-v5.5/export.sh
idf.py build && idf.py -p /dev/ttyACM0 flash
cd ../ros2 && colcon build --packages-up-to protective_stop_machine
ros2 run protective_stop_machine machine_bridge_node
curl -X POST "http://<remote>/api/pstop_peer?ip=<laptop-tailscale-ip>&port=8890"A plain-C machine process (cd host && make && ./machine_app_runner) is the
non-ROS alternative. Later firmware updates go over the authenticated admin
API (POST /admin/api/ota). Hardware assembly starts with
hardware/README.md and the photographed
hardware/ASSEMBLY.md.
The network update rate and stop-on-silence time are machine-controlled:
remote send interval = clamp(heartbeat_ms / 2, 100 ms, 1000 ms)
stop-on-silence time = heartbeat_ms * max_missed_heartbeats
The default configuration uses a 400 ms heartbeat window and five missed windows, giving a send interval of about 200 ms and a stop-on-silence threshold of about 2 seconds. Integrators should choose values within the validated limits so the timeout fits their process safety time and network conditions.
The machine starts stopped. To arm it, press and hold the switch for at least
min_stop_ms (500 ms by default), then release it. Whether a remote may re-arm
is the remote's own declared role: a new remote announces stop_only (it can
stop a machine but never arm it) until promoted to operator through its
authenticated /api/role. The change applies live β an armed machine keeps
running when its operator demotes itself, but refuses to re-arm until a remote
announcing operator performs the gesture.
Machines optionally restrict who may bond at all with a global allowlist
and/or denylist (empty by default = everyone). A refused remote is told so
(UNBOND) and stops retrying until an operator asks it to rebond.
One remote can maintain independent sessions with up to four machines. Each machine controls its own heartbeat window, timeout and admission policy.
The repository includes host-side protocol tests, fault-injection and chaos
ladders, transport soaks, ROS 2 tests, firmware builds, and a MISRA C:2012
pre-check. Start with docs/TESTING.md; long-run transport
testing is documented in
docs/CONNECTIVITY_SOAK.md.
make -C host test
./tools/misra_check.sh
pre-commit run --all-filesThe Protective Stop v1.2 hardware design was OSHWA-certified as open source hardware on August 27, 2026:
- OSHWA UID: US002846
- Hardware: CERN-OHL-P-2.0
- Software and firmware: Apache-2.0
- Documentation: CC-BY-4.0 unless a file states otherwise
The source package, licensing map, and mark guidance are summarized in
docs/OSHWA_COMPLIANCE.md. Full license texts are
in LICENSE and LICENSES/.
The design target for the on-demand stop function is SIL 3 under IEC 61508 and
PL e under ISO 13849. These are engineering targets, not current
functional-safety certifications. The safety case and remaining evidence gaps
are tracked in docs/safety/ and
docs/safety/OPEN_ITEMS.md.
See CONTRIBUTING.md for development requirements. Report
security issues through SECURITY.md.
