Full Nori A3 docs coming soon. What works today.
Skip to content

The safety contract

The core promise, stated precisely: every software safety mechanism lives on the robot, and none of them are negotiable from this SDK.

Clamping, watchdog, software E-STOP, stall handling, and the motor torque lifecycle all run in the robot's control daemon. No message you can send — malformed, malicious, or buggy — disables or loosens them. The physical E-stop is a separate electrical control that cuts power to the motors; the master switch cuts power to the entire robot.

Limits are disclosed, not negotiated: the handshake tells you what they are (robotInfo().watchdogProfile, descriptor.ranges), and there is deliberately no API to change them. If your use case genuinely needs different limits, that's a conversation with us, not a parameter.

What the robot enforces, and how you see it

telemetry.safety (SafetyState)

ValueWhat the robot is doingWhat you do
"ok"Normal operation.Carry on.
"safe_hold"Refusing motion to protect itself — either the Pi is too hot, or your control frames went silent past the watchdog stop threshold. Not a latch.Fix the cause (let it cool / restore your control stream); it clears itself.
"latched"Something latched. A software E-STOP operator command, or a motor-protection trip — an over-temperature or sustained-over-current joint cut. Motion blocked.Clear deliberately with command("reset_latch") once the situation is safe.

latched is no longer synonymous with E-STOP

A robot can latch on its own to protect a motor. Read latch_reason before you assume a human pressed something — see Motor protection below.

latch_reason

Reason strings follow "<cause>:<detail>":

ValueMeaning
estop:<reason>A human used the app or SDK command("estop"). safety = latched.
overtemp:<motor>That servo's case temperature crossed the over-temp threshold. Torque cut on that joint. safety = latched.
overcurrent:<motor>That joint drew too much current for too long (a sustained-load integral, not an instantaneous spike). Torque cut on that joint. safety = latched.
stall:<motor>That joint is pressing into an obstruction. safety stays ok — a stall is not a global stop.
nullNothing latched and nothing stalled.

latch_reason can be set while safety is ok

A stall reports itself through latch_reason — and as an action_status with reason: "stall:<joint>" — but deliberately does not raise safety. Branch on safety for "is the robot stopped", and read latch_reason for "why"; a non-null latch_reason is not a stop.

Do not switch exhaustively on these. The <cause> set is open and grows; show the string.

Motor protection

Beyond E-STOP, the daemon runs three independent motor guards. Two of them latch, which is the behavior change most likely to surprise a client written before them:

GuardTrips onEffectRecovery
Stall detectorA joint pressing into an obstruction.That joint's torque is capped low and its goal parked at the present position — it holds at the obstacle instead of cranking into it. Everything else keeps running.Self-clears: jog that joint to a new target. Or reset_latch.
Sustained-current tripA joint drawing above the current floor for long enough that the integral exceeds its budget. Higher current trips faster (a hard stall in seconds, a mild cook in tens of seconds). Grippers exempt.That joint's torque is cut and latched off. safetylatched, latch_reasonovercurrent:<motor>.reset_latch only.
Over-temp latchA servo's case temperature crossing the threshold (read ~1 Hz).That joint's torque is cut and latched off. safetylatched, latch_reasonovertemp:<motor>.reset_latch only, and only once it has cooled — it will re-trip otherwise.

The current and temperature guards exist because a joint can cook while drawing too little to look like a stall. They are deliberately not tunable from a client.

Software and physical E-stops affect torque differently

A software E-STOP blocks motion but leaves torque engaged — dropping a gravity-loaded arm is more dangerous than holding it. The physical E-stop cuts power to every motor, so an arm can go limp and fall. The per-joint guards above cut torque on the one offending joint, on purpose, because that joint is the thing being damaged. A joint that goes limp can fall.

telemetry.watchdog (WatchdogState)

"ok""warn" (your control frames went quiet past t_warn_ms, or the robot is shedding thermal load) → "stop" (quiet past t_stop_ms; motion blocked until frames resume).

The thresholds are per-link (LAN vs WAN) and arrive in the handshake. You can read them, not set them.

The three commands

CommandEffect
"estop"Trip the software E-STOP latch now (safety → "latched"). Always available; never rate-limited. Torque stays engaged. This does not operate the physical motor-power cutoff.
"reset_latch"Clear every latch — E-STOP, stall, over-temp, over-current — and re-engage torque on any joint that was cut. A deliberate act; nothing else clears a latch.
"reset"Return the arm(s) to their neutral pose. A motion command, not a latch operation — so it's refused while latched.

Out-of-range targets are clamped, never rejected

Sending a .pos beyond descriptor.ranges moves the joint to the boundary and (for tagged actions) reports clamped in the action status.

Use ranges to scale your inputs, not to pre-validate them. A value you thought was rejected was actually executed, at the limit.

Apache-2.0