# llms.txt - Guidance for generating Flexiv robot control code in this repository

Scope: This file captures repository-specific, API-accurate knowledge for LLMs that generate Python control scripts using `flexivrdk`.

## 0) Authoritative References
- RDK Manual: https://www.flexiv.com/software/rdk/manual
- API Documentation (latest): https://www.flexiv.com/software/rdk/api
- Python package page: https://pypi.org/project/flexivrdk
- Primitive catalog and documentation: https://www.flexiv.com/software/primitives

Use these sources for API behavior details and argument meaning when in doubt.

## 0.1) C++ and Python API Parity
- In this project, Python RDK is generated from pybind bindings of C++ RDK.
- Function names and function documentation are expected to match between C++ and Python APIs.
- Generation rule: if Python docs are sparse, use the corresponding C++ API docs as canonical backup for names, parameters, and semantics.
- Verification rule: before emitting a Python API call, verify parameter count, optionality, and fixed-size container constraints against API docs or headers.
- Priority references for verification: API docs first, then C++ headers (for exact sizes/types when docs are ambiguous).

## 1) Preferred Script Pattern (Python)
- Use examples under `example_py/` as style reference.
- Typical imports:
  - `import argparse`
  - `import time`
  - `import spdlog`
  - `import flexivrdk`
- Typical flow:
  1. Parse `robot_sn` argument.
  2. `robot = flexivrdk.Robot(robot_sn)`.
  3. If `robot.fault()`: call `robot.ClearFault()` and handle failure.
  4. `robot.ServoOn()` and wait until `robot.operational()`.
  5. Get applicable joint groups from `robot.info()`:
     - for primitive execution, use `single_arm_groups = robot.info().single_arm_groups`
     - for inspection/logging of all groups, use `robot.info().all_groups`
  6. If needed, use `robot.Home()` (or `robot.Home([...])`) as the blocking convenience wrapper for homing.
  7. Switch to primitive execution with `robot.SwitchMode(flexivrdk.Mode.NRT_PRIMITIVE_EXECUTION)`.
  8. Execute primitives with `robot.ExecutePrimitive({group: flexivrdk.PrimitiveArgs(name, params) for group in single_arm_groups})`.
  9. Wait for primitive completion using the primitive's documented Default Transition Condition from each running group's primitive state map.
  10. End with `robot.Stop()`.

## 2) Primitive Execution Conventions
- Use explicit primitive names such as `Home`, `MoveJ`, `MoveL`.
- For each primitive call, wrap parameters in `flexivrdk.PrimitiveArgs(name, params)` and send as a map keyed by joint group.
- Use only existing single-arm joint groups, typically the keys of `robot.info().single_arm_groups`, with `robot.ExecutePrimitive(...)`.
- Keep transitions explicit in code (sequential primitive calls) unless user asks for asynchronous behavior.
- Do not assume `robot.ExecutePrimitive(...)` blocks until completion. It blocks until request delivery or primitive start; users must still wait on primitive states.
- Primitive transition rule:
  - Each primitive exposes a set of primitive states as listed in Primitive State Parameters.
  - In most cases, use the primitive's documented Default Transition Condition. But other Primitive State Parameters can also be used as transition conditions.
  - Do not assume `reachedTarget` is the only valid transition condition for all primitives.
- Polling loop pattern:
  - `all_states = robot.primitive_states()`
  - `robot.primitive_states()` only contains joint groups that are currently running primitives
  - check the documented state key(s) from `state.names_and_values` for each target joint group
  - primitive-state booleans are typically read back as integer `1` or `0`
  - `time.sleep(0.1-1.0)` in the loop

### 2.1) RDK v2.x Joint-Group API Rule
- `robot.states()` returns `dict[JointGroup, RobotStates]`, not a single `RobotStates`.
- `robot.primitive_states()` returns `dict[JointGroup, PrimitiveStates]`, not a flat key-value map.
- `robot.ExecutePrimitive(...)` takes `dict[JointGroup, PrimitiveArgs]`, not `(name, params)`.
- `robot.Home(groups=...)` is the blocking convenience wrapper for the Home primitive and accepts a list of single-arm joint groups. If omitted, it homes all applicable single-arm groups.
- When using fields like `tcp_pose`, first choose a specific joint group, usually one from `robot.info().single_arm_groups`.

## 3) Data Type Mapping for `ExecutePrimitive`
Map plan-like values to RDK Python objects:
- `JPOS` -> `flexivrdk.JPos(q_m, q_e)` or `flexivrdk.JPos(q_m)`
- `COORD` -> `flexivrdk.Coord(position, orientation, ref_frame, ref_q_m, ref_q_e)`
- `ARRAY_COORD` -> Python list of `flexivrdk.Coord(...)`
- Scalar types:
  - `BOOL` -> `bool`
  - `INT` -> `int`
  - `DOUBLE` -> `float`
  - `TYPE` -> `str`
  - `VEC_3d` -> Python list/array length 3

### 3.1) Fixed-Size Argument Checklist (Pybind)
- Treat constructor/type hints with fixed sizes as strict runtime requirements.
- `flexivrdk.JPos(q_m)` requires `q_m` length 7. `flexivrdk.JPos(q_m, q_e)` requires `q_e` length 6.
- `flexivrdk.Coord(position, orientation, ref_frame)` requires lengths 3, 3, and 2 respectively.
- Optional `Coord` arguments still have fixed sizes when provided: `ref_q_m` length 7 and `ref_q_e` length 6.
- If `ref_q_e` is provided, also provide `ref_q_m`.
- Prefer omitting optional constructor arguments entirely over passing empty or wrong-sized placeholders.

## 4) Critical `Coord` Rule (Do Not Drop Provided Fields)
From `include/flexiv/rdk/data.hpp`, the current `Coord` signature is:

`flexivrdk.Coord(position, orientation, ref_frame, ref_q_m, ref_q_e)`

Its members are, in order:
1. `position`
2. `orientation`
3. `ref_frame`
4. `ref_q_m`
5. `ref_q_e`

Generation rule:
- `ref_q_m` and `ref_q_e` are optional in the constructor, but if source data provides them, populate them.
- Do not silently drop `ref_q_m` or `ref_q_e`.
- If specifying `ref_q_e`, also specify `ref_q_m`.

## 5) Units Guidance
- RDK `JPos` and `Coord` objects use SI length unit `[m]` on the API side.
- Header note: conversion between RDK `[m]` and Flexiv Elements custom-type `[mm]` is handled automatically for `JPOS` and `COORD` exchanged through APIs such as `ExecutePrimitive()`.
- Primitive-documentation caveat: primitive input parameters do not universally use SI units.
  - For scalar primitive parameters, follow the primitive page exactly.
  - For `flexivrdk.Coord(...)` and `flexivrdk.JPos(...)`, supply values in the RDK object units defined by the headers.

## 5.1) Upright Posture Convention
- The robot upright posture is defined as all joint positions being 0.

## 5.2) Euler Readback Normalization
- Robot Euler orientation readback is normalized to the range [-180, 180] degrees.
- If an Euler angle increases past 180, it wraps to -180. If it decreases past -180, it wraps to 180.
- This wraparound does not indicate a different physical orientation; it is only a different representation of the same rotation.
- When comparing Euler angles from commanded values versus robot readback, compare with wraparound awareness instead of raw numeric equality.

## 6) Safety and Robustness Defaults
- Always include fault clear path before enabling.
- Always wait for operational state before switching mode or sending primitives.
- Use try/except and log exceptions with `spdlog`.
- Avoid busy-spin loops; include `time.sleep(...)` in polling loops.
- Prefer timeout-backed wait helpers over unbounded polling loops when generating new scripts.
- Keep command sequence deterministic and human-readable.

## 7) Output Style for Generated Scripts
- Prefer one self-contained runnable script per task.
- Include short comments only for non-obvious behavior.
- Keep numeric trajectory data hardcoded only when user explicitly asks for hardcoding.
- Do not parse external plan files if the user asks for hardcoded primitive calls.

## 8) Repository Anchors
- Python examples: `example_py/`
- Agent primitive-only reference: `example_py/AGENT_REFERENCE_primitives_only.py`
- Primitive tutorial reference: `example_py/basics3_primitive_execution.py`
- Primitive wait helper reference: `example_py/utility.py`
- Core robot API surface: `include/flexiv/rdk/robot.hpp`
- Core data type definitions: `include/flexiv/rdk/data.hpp`
- Repo readme and setup guide: `README.md`

## 9) Minimal Skeleton
```python
import argparse
import time
import spdlog
import flexivrdk
import utility


def wait_primitive_transition(robot, transition_keys_by_group, dt=0.2, timeout_s=60.0):
    """Block until primitive_states() for all groups satisfy their transition key.

    Use the primitive's default transition key unless a custom transition condition
    is intended. Check primitive docs to confirm available primitive state keys.
    """
    t0 = time.time()
    while True:
        if utility.primitive_state_true_for_groups(
            robot.primitive_states(),
            transition_keys_by_group,
        ):
            return

        if time.time() - t0 > timeout_s:
            raise TimeoutError(
                "Timed out waiting for primitive transition states: "
                f"{ {flexivrdk.kJointGroupNames[g]: k for g, k in transition_keys_by_group.items()} }"
            )
        time.sleep(dt)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("robot_sn")
    args = parser.parse_args()

    logger = spdlog.ConsoleLogger("LLMGenerated")
    try:
        robot = flexivrdk.Robot(args.robot_sn)

        if robot.fault():
            logger.warn("Fault detected, clearing")
            if not robot.ClearFault():
                raise RuntimeError("Cannot clear fault")

        robot.ServoOn()
        while not robot.operational():
            time.sleep(1)

        single_arm_groups = robot.info().single_arm_groups
        if not single_arm_groups:
            raise RuntimeError("No single-arm joint group found on the connected robot")

        robot.SwitchMode(flexivrdk.Mode.NRT_PRIMITIVE_EXECUTION)

        robot.ExecutePrimitive(
            {
                group: flexivrdk.PrimitiveArgs("Home", {})
                for group in single_arm_groups
            }
        )
        wait_primitive_transition(robot, {group: "reachedTarget" for group in single_arm_groups})

        # Add other primitive calls here.

        robot.Stop()

    except Exception as e:
        logger.error(f"Unhandled exception: {e}")
        raise


if __name__ == "__main__":
    main()
```
