> ## Documentation Index
> Fetch the complete documentation index at: https://innateinc-docs-skills-0-7-interface.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Robot State

export const RobotStateAvailableTable = () => {
  const rows = [{
    state: "Main camera",
    typeEnum: "image: MainImage",
    description: "The latest frame. The value IS the base64 JPEG string; .jpeg gives raw bytes."
  }, {
    state: "Wrist camera",
    typeEnum: "wrist_image: WristImage",
    description: "The same, from the camera on the gripper."
  }, {
    state: "Depth",
    typeEnum: "depth: DepthMap",
    description: "A (height, width) numpy array of depth — uint16 millimetres from the stereo pipeline."
  }, {
    state: "Odometry",
    typeEnum: "odom: Odometry",
    description: "x, y, theta in the odom frame plus linear_velocity / angular_velocity. Drifts, never jumps."
  }, {
    state: "Map pose",
    typeEnum: "pose: Pose",
    description: "x, y, theta in the map frame, from localization. Corrects, so it can jump."
  }, {
    state: "Battery",
    typeEnum: "battery: Battery",
    description: "percentage (0-1), voltage, current, charging."
  }, {
    state: "Lidar",
    typeEnum: "lidar: Lidar",
    description: "One sweep: ranges, angle_min, angle_increment, plus a min_range(from_deg, to_deg) sector helper."
  }, {
    state: "Arm pose",
    typeEnum: "arm: Arm",
    description: "End-effector x, y, z and orientation, with .rpy and .gripper."
  }, {
    state: "Joint states",
    typeEnum: "joint_states: JointStates",
    description: "Parallel name / position / velocity / effort tuples, plus .of(\"joint2\")."
  }, {
    state: "Head position",
    typeEnum: "head_position: HeadState",
    description: "pitch_degrees, and the driver's limits when it reports them."
  }, {
    state: "Map",
    typeEnum: "map: Map",
    description: "Occupancy grid metadata, with .grid decoded lazily into a numpy array."
  }];
  return <div className="interface-methods-table-wrap">
      <table className="interface-methods-table">
        <thead>
          <tr>
            <th>Feed</th>
            <th>Declare as</th>
            <th>What you get</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(row => <tr key={row.typeEnum}>
              <td>{row.state}</td>
              <td>
                <span className="interface-param-badge">{row.typeEnum}</span>
              </td>
              <td>{row.description}</td>
            </tr>)}
        </tbody>
      </table>
    </div>;
};

Robot state reaches your skill as **typed values**, declared the same way as everything else: annotate the attribute with the type you want, and the runtime injects it and keeps it fresh at 50 Hz while the skill runs.

```python theme={null}
from innate import MainImage, Odometry, Skill


class MySkill(Skill):
    """..."""

    image: MainImage
    odom: Odometry

    def execute(self):
        x, y = self.odom.position
        self.logger.info(f"at ({x:.2f}, {y:.2f}), yaw {self.odom.theta_degrees:.0f}°")
```

A plain annotation is **guaranteed**: the run fails up front if the feed never arrives, so there is nothing to check for `None`. Append `| None` (`battery: Battery | None`) to make a feed best effort instead — injected when available, `None` otherwise, and the run starts either way.

Every state type is a frozen dataclass with no ROS in it, so you can import and unit-test them off the robot.

## Available state

<RobotStateAvailableTable />

## Odometry and pose

MARS is a differential-drive base on flat ground, so its pose is fully `(x, y, theta)` — no quaternions to unpack.

```python theme={null}
class MySkill(Skill):
    """..."""

    odom: Odometry
    pose: Pose

    def execute(self):
        self.odom.x, self.odom.y          # metres, odom frame
        self.odom.theta                   # radians, CCW, wrapped to (-pi, pi]
        self.odom.theta_degrees           # the same in degrees
        self.odom.position                # (x, y)
        self.odom.linear_velocity         # m/s, negative when reversing
        self.odom.angular_velocity        # rad/s
        self.odom.stamp                   # sensor time, seconds

        self.pose.x, self.pose.y, self.pose.theta   # the same shape, map frame
```

Which one you want depends on the frame:

|          | `odom: Odometry`                               | `pose: Pose`                          |
| -------- | ---------------------------------------------- | ------------------------------------- |
| Frame    | `odom` — where the robot thinks it has driven  | `map` — where localization says it is |
| Behavior | drifts slowly, never jumps                     | corrects, so it *can* jump            |
| Use for  | relative moves: "go forward 0.4 m", "turn 90°" | absolute positions on the map         |

If you genuinely need the full ROS message — covariances, `z`, the real quaternion — `odom.raw` hands it over as plain data.

## Camera frames

An `Image` **is** the base64 JPEG string, so it drops straight into a vision API body, and `.jpeg` gives you the raw bytes:

```python theme={null}
import io

from PIL import Image as PILImage

from innate import MainImage, Skill, WristImage


class MySkill(Skill):
    """..."""

    image: MainImage
    wrist_image: WristImage

    def execute(self):
        response = vision_api.analyze(self.image)          # already base64 text
        img = PILImage.open(io.BytesIO(self.image.jpeg))   # or raw bytes
```

`depth: DepthMap` gives a `(height, width)` numpy array of depth from the stereo pipeline, in uint16 millimetres.

## Lidar

One sweep, with a sector helper so you don't have to do the angle arithmetic:

```python theme={null}
class MySkill(Skill):
    """..."""

    lidar: Lidar

    def execute(self):
        ahead = self.lidar.min_range(-20, 20)     # closest return in a ±20° wedge
        if ahead is not None and ahead < 0.4:
            self.fail("Something is right in front of me")

        self.lidar.ranges            # metres, one per beam
        self.lidar.angle_min         # angle of beam 0, radians
        self.lidar.angle_increment   # radians between beams
```

## Arm and joints

```python theme={null}
class MySkill(Skill):
    """..."""

    arm: Arm
    joint_states: JointStates
    head_position: HeadState

    def execute(self):
        self.arm.x, self.arm.y, self.arm.z    # end-effector, metres
        self.arm.rpy                          # (roll, pitch, yaw), radians
        self.arm.gripper                      # claw joint, radians

        self.joint_states.of("joint2")        # (position, velocity, effort)

        self.head_position.pitch_degrees      # current head tilt
```

`arm: Arm` is the same type `Manipulation` returns from a motion, so a servoing loop reads the ambient feed and the move result in one shape.

## Map

Grid metadata arrives immediately; the cells are decoded lazily, so a skill that only wants the resolution never pays to unpack thousands of them.

```python theme={null}
class MySkill(Skill):
    """..."""

    map: Map

    def execute(self):
        self.map.resolution                       # metres per cell
        self.map.width, self.map.height           # cells
        self.map.origin_x, self.map.origin_y      # world position of cell (0, 0)

        grid = self.map.grid                      # (height, width) int8 numpy array
        # -1 unknown, 0 free, 100 occupied
```

## Example: capture images while rotating

```python theme={null}
import math

from innate import MainImage, Mobility, Skill


class CaptureImages(Skill):
    """Capture images from several directions. Use to survey a room before
    deciding where to go."""

    mobility: Mobility
    image: MainImage

    def execute(self, num_directions: int = 4):
        images = []
        step = (2 * math.pi) / num_directions

        for i in range(num_directions):
            images.append(str(self.image))
            self.feedback(f"Captured {i + 1}/{num_directions}")
            if i < num_directions - 1:
                self.mobility.rotate(step)

        return f"Captured {len(images)} images"
```

No cancellation handling: `mobility.rotate()` raises `SkillCancelled` the moment a Stop lands, and the framework reports it.

## Example: monitor how far the robot moves

```python theme={null}
import math
import time

from innate import Odometry, Skill


class MonitorPosition(Skill):
    """Watch how far the robot moves over a duration. Use to check whether
    something is pushing the robot around."""

    odom: Odometry

    def execute(self, duration: float = 5.0):
        start = self.odom.position
        deadline = time.monotonic() + duration

        while time.monotonic() < deadline:
            moved = math.dist(self.odom.position, start)
            self.feedback(f"Moved {moved:.2f}m from start")
            self.sleep(0.5)

        return f"Moved {math.dist(self.odom.position, start):.2f}m in {duration:.0f}s"
```

<Note>
  Skills written against 0.6.x read state as dictionaries (`odom["theta_degrees"]`, `odom["pose"]["pose"]["position"]["x"]`). That still works — the typed values keep a mapping shim — but it is deprecated. New skills should use the attributes.
</Note>
