Declaration
One rule covers everything: annotate what you read.None guards are needed inside execute(). The wait is 3 s for cameras, 6 s for the battery (it only publishes at ~0.2 Hz), 2 s for everything else. Appending | None makes the feed best effort instead: injected when available, None otherwise, and every read must be guarded.
Reading an undeclared feed raises with a hint (declare it on the class with a type annotation), and your editor flags it before you ship.
Available State
Camera Frames
Cameras must be declared — the server starts them per run (frame encoding is too expensive to keep warm). A required camera fails the run if no frame arrives within a short grace period; an optional (| None) camera never delays the start — wait for it in execute() if you need to (self.wait_for(lambda: self.image)).
An Image is the base64 JPEG text (it subclasses str, so anything that treated frames as base64 strings keeps working); .jpeg is the decoded bytes:
DepthMap is a (height, width) numpy array (uint16 mm or float32 m), declared as depth: DepthMap.
Odometry
A flat 2D snapshot — MARS is a differential-drive base on flat ground, so its pose is fully(x, y, theta):
pose: Pose has the same shape in the map frame — the coordinates navigate_to_position targets. Which one you want depends on the frame:
odom resets every boot but is always there. pose reads None until the robot is localized, so declare it Pose | None unless localization is guaranteed.
Map
The occupancy grid:Other feeds
arm: Arm is the ambient 50 Hz feed; self.manipulation.pose returns the same Arm type on demand (and raises if the FK feed is down) — see Body Control Interfaces.
Example: CaptureImages
Capture images while rotating. The camera and mobility are declared with bare annotations, so both are guaranteed insideexecute():
Example: MonitorPosition
Track how far the robot moves over a window.self.sleep() is what makes the loop interruptible:

