RMI provides access to controller status, user frames and tools, digital and generic I/O, position reading, registers, system variables, payload, and TCP speed.

## Controller status

`GetStatus()` returns the full controller state. Use it before `Initialize()` to verify the controller is ready.

**C# : RmiFramesIoStatus**
```csharp
using System;
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.Rmi.Data;

public class RmiFramesIoStatus
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);

        /**/
        // Basic status: servo, TP mode, RMI running, override
        RmiControllerStatusResponse status = robot.Rmi.GetStatus();
        Console.WriteLine("Servo ready:   " + status.ServoReady);
        Console.WriteLine("TP enabled:    " + status.TPEnabled);
        Console.WriteLine("RMI running:   " + status.RmiMotionStatus);
        Console.WriteLine("Program state: " + status.ProgramStatus);
        Console.WriteLine("Override:      " + status.SpeedOverride + "%");
        Console.WriteLine("UFrame count:  " + status.NumberUFrame);
        Console.WriteLine("UTool count:   " + status.NumberUTool);

        // Extended status: drive power, control mode, speed clamp
        RmiExtendedControllerStatusResponse ext = robot.Rmi.GetExtendedStatus();
        Console.WriteLine("Drives on:     " + ext.DrivesPowered);
        Console.WriteLine("In motion:     " + ext.InMotion);

        // Read last controller error (up to 5 at once)
        RmiControllerErrorTextResponse errors = robot.Rmi.ReadError(count: 3);
        foreach (string entry in errors.ErrorDataEntries)
            Console.WriteLine("Error: " + entry);

        // HOLD state
        Console.WriteLine("In HOLD:       " + robot.Rmi.IsInHoldState);
        /**/

        robot.Disconnect();
    }
}
```

**Python : RmiFramesIoStatus**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)

##
# Basic status: servo, TP mode, RMI running, override
status = robot.rmi.get_status()
print("Servo ready:   ", status.servo_ready)
print("TP enabled:    ", status.tp_enabled)
print("RMI running:   ", status.rmi_motion_status)
print("Program state: ", status.program_status)
print("Override:      ", status.speed_override, "%")
print("UFrame count:  ", status.number_u_frame)
print("UTool count:   ", status.number_u_tool)

# Extended status: drive power, control mode, speed clamp
ext = robot.rmi.get_extended_status()
print("Drives on:     ", ext.drives_powered)
print("In motion:     ", ext.in_motion)

# Read last controller error (up to 5 at once)
errors = robot.rmi.read_error(count=3)
for entry in errors.error_data_entries:
    print("Error:", entry)

# HOLD state
print("In HOLD:       ", robot.rmi.is_in_hold_state)
##

robot.disconnect()
```

## Admin commands

**C# : RmiFramesIoAdmin**
```csharp
using UnderAutomation.Fanuc;

public class RmiFramesIoAdmin
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);
        robot.Rmi.Initialize();

        /**/
        // Set speed override (1-100 %)
        robot.Rmi.SetOverride(50);

        // Pause and resume the motion program
        robot.Rmi.Pause();
        robot.Rmi.Continue();

        // Reset controller errors and exit the HOLD state
        robot.Rmi.Reset();

        // Resynchronize the sequence ID counter
        robot.Rmi.AutoSetNextSequenceId();

        // Get/set current UFRAME and UTOOL numbers
        var ut = robot.Rmi.GetUFrameUTool();
        System.Console.WriteLine("Frame: " + ut.Frame + ", Tool: " + ut.Tool);
        robot.Rmi.SetUFrameUTool(uframe: 1, utool: 2);

        // Abort the RMI_MOVE program
        robot.Rmi.Abort();
        /**/

        robot.Disconnect();
    }
}
```

**Python : RmiFramesIoAdmin**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)
robot.rmi.initialize()

##
# Set speed override (1-100 %)
robot.rmi.set_override(50)

# Pause and resume the motion program
robot.rmi.pause()
robot.rmi.continue_()

# Reset controller errors and exit the HOLD state
robot.rmi.reset()

# Resynchronize the sequence ID counter
robot.rmi.auto_set_next_sequence_id()

# Get/set current UFRAME and UTOOL numbers
ut = robot.rmi.get_u_frame_u_tool()
print("Frame:", ut.frame, "Tool:", ut.tool)
robot.rmi.set_u_frame_u_tool(uframe=1, utool=2)

# Abort the RMI_MOVE program
robot.rmi.abort()
##

robot.disconnect()
```

## Position reading

Read the current robot position and TCP speed. On firmware MajorVersion >= 6, the position reflects actual encoder counts.

**C# : RmiFramesIoPosition**
```csharp
using System;
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.Rmi.Data;

public class RmiFramesIoPosition
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);

        /**/
        // Read current Cartesian position.
        // On MajorVersion >= 6, returns actual encoder position.
        RmiCartesianPositionResponse cart = robot.Rmi.ReadCartesianPosition();
        Console.WriteLine($"X={cart.Position.X:F1}  Y={cart.Position.Y:F1}  Z={cart.Position.Z:F1}");
        Console.WriteLine($"W={cart.Position.W:F1}  P={cart.Position.P:F1}  R={cart.Position.R:F1}");
        Console.WriteLine($"Tool={cart.Position.Tool}  Frame={cart.Position.Frame}");
        Console.WriteLine($"Timestamp={cart.TimeTag}");

        // Read current joint angles
        RmiJointAnglesSampleResponse joints = robot.Rmi.ReadJointAngles();
        Console.WriteLine($"J1={joints.JointAngle.J1:F2}  J2={joints.JointAngle.J2:F2}  J3={joints.JointAngle.J3:F2}");
        Console.WriteLine($"J4={joints.JointAngle.J4:F2}  J5={joints.JointAngle.J5:F2}  J6={joints.JointAngle.J6:F2}");

        // Read TCP speed (mm/s)
        RmiTcpSpeedResponse speed = robot.Rmi.ReadTcpSpeed();
        Console.WriteLine($"TCP speed={speed.Speed:F2} mm/s");
        /**/

        robot.Disconnect();
    }
}
```

**Python : RmiFramesIoPosition**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)

##
# Read current Cartesian position.
# On MajorVersion >= 6, returns actual encoder position.
cart = robot.rmi.read_cartesian_position()
print(f"X={cart.position.x:.1f}  Y={cart.position.y:.1f}  Z={cart.position.z:.1f}")
print(f"W={cart.position.w:.1f}  P={cart.position.p:.1f}  R={cart.position.r:.1f}")
print(f"Tool={cart.position.tool}  Frame={cart.position.frame}")
print(f"Timestamp={cart.time_tag}")

# Read current joint angles
joints = robot.rmi.read_joint_angles()
print(f"J1={joints.joint_angle.j1:.2f}  J2={joints.joint_angle.j2:.2f}  J3={joints.joint_angle.j3:.2f}")

# Read TCP speed (mm/s)
speed = robot.rmi.read_tcp_speed()
print(f"TCP speed={speed.speed:.2f} mm/s")
##

robot.disconnect()
```

## User frames and tools

Read and write user frames (UFrame) and tools (UTool):

**C# : RmiFramesIoFrames**
```csharp
using System;
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.Common;
using UnderAutomation.Fanuc.Rmi.Data;

public class RmiFramesIoFrames
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);

        /**/
        // Read UFRAME 1
        RmiIndexedFrameResponse uf = robot.Rmi.ReadUFrame(1);
        Console.WriteLine($"UFrame 1: X={uf.Frame.X:F1}  Y={uf.Frame.Y:F1}  Z={uf.Frame.Z:F1}");

        // Write UFRAME 1
        robot.Rmi.WriteUFrame(1, new XYZWPRPosition { X = 100, Y = 0, Z = 0, W = 0, P = 0, R = 0 });

        // Read UTOOL 1
        RmiIndexedFrameResponse ut = robot.Rmi.ReadUTool(1);
        Console.WriteLine($"UTool 1: X={ut.Frame.X:F1}  Y={ut.Frame.Y:F1}  Z={ut.Frame.Z:F1}");

        // Write UTOOL 1
        robot.Rmi.WriteUTool(1, new XYZWPRPosition { X = 0, Y = 0, Z = 200, W = 0, P = 0, R = 0 });

        // Get current UFRAME and UTOOL numbers
        RmiUFrameUToolNumbersResponse current = robot.Rmi.GetUFrameUTool();
        Console.WriteLine($"Active UFRAME={current.Frame}  UTOOL={current.Tool}");

        // Set the active UFRAME and UTOOL
        robot.Rmi.SetUFrameUTool(uframe: 1, utool: 2);
        /**/

        robot.Disconnect();
    }
}
```

**Python : RmiFramesIoFrames**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters
from underautomation.fanuc.common.xyzwpr_position import XYZWPRPosition

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)

##
# Read UFRAME 1
uf = robot.rmi.read_u_frame(1)
print(f"UFrame 1: X={uf.frame.x:.1f}  Y={uf.frame.y:.1f}  Z={uf.frame.z:.1f}")

# Write UFRAME 1
frame_pos = XYZWPRPosition()
frame_pos.x = 100
robot.rmi.write_u_frame(1, frame_pos)

# Read UTOOL 1
ut = robot.rmi.read_u_tool(1)
print(f"UTool 1: X={ut.frame.x:.1f}  Y={ut.frame.y:.1f}  Z={ut.frame.z:.1f}")

# Write UTOOL 1
tool_pos = XYZWPRPosition()
tool_pos.z = 200
robot.rmi.write_u_tool(1, tool_pos)

# Get current UFRAME and UTOOL numbers
current = robot.rmi.get_u_frame_u_tool()
print(f"Active UFRAME={current.frame}  UTOOL={current.tool}")

# Set the active UFRAME and UTOOL
robot.rmi.set_u_frame_u_tool(uframe=1, utool=2)
##

robot.disconnect()
```

## Digital I/O

**C# : RmiFramesIoIo**
```csharp
using System;
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.Rmi.Data;

public class RmiFramesIoIo
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);

        /**/
        // Read digital input DI[2]
        RmiDigitalInputValueResponse din = robot.Rmi.ReadDIN(2);
        Console.WriteLine($"DI[2] = {din.PortValue}");

        // Write digital output DO[1]
        robot.Rmi.WriteDOUT(1, RmiOnOff.ON);
        robot.Rmi.WriteDOUT(1, RmiOnOff.OFF);
        /**/

        robot.Disconnect();
    }
}
```

**Python : RmiFramesIoIo**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters
from underautomation.fanuc.rmi.data.rmi_on_off import RmiOnOff

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)

##
# Read digital input DI[2]
din = robot.rmi.read_din(2)
print(f"DI[2] = {din.port_value}")

# Write digital output DO[1]
robot.rmi.write_dout(1, RmiOnOff.ON)
robot.rmi.write_dout(1, RmiOnOff.OFF)
##

robot.disconnect()
```

## Generic I/O ports

`ReadIOPort` and `WriteIOPort` work with any port type (DI, DO, AI, AO, GO, RO, FLAG, RI, UI, UO):

**C# : RmiFramesIoGenericIo**
```csharp
using System;
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.Rmi.Data;

public class RmiFramesIoGenericIo
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);

        /**/
        // Read any IO port type: DI, DO, AI, AO, GO, RO, FLAG, RI, UI, UO
        RmiIoPortValueResponse di = robot.Rmi.ReadIOPort(RmiIoPortType.DI, 1);
        Console.WriteLine($"DI[1] = {di.Value}");

        RmiIoPortValueResponse ai = robot.Rmi.ReadIOPort(RmiIoPortType.AI, 1);
        Console.WriteLine($"AI[1] = {ai.Value}");

        RmiIoPortValueResponse flag = robot.Rmi.ReadIOPort(RmiIoPortType.FLAG, 5);
        Console.WriteLine($"FLAG[5] = {flag.Value}");

        // Write AO[1] = 2.5
        robot.Rmi.WriteIOPort(RmiIoPortType.AO, 1, 2.5);

        // Write GO[1] = 7
        robot.Rmi.WriteIOPort(RmiIoPortType.GO, 1, 7);

        // Write FLAG[5] = 1
        robot.Rmi.WriteIOPort(RmiIoPortType.FLAG, 5, 1);
        /**/

        robot.Disconnect();
    }
}
```

**Python : RmiFramesIoGenericIo**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters
from underautomation.fanuc.rmi.data.rmi_io_port_type import RmiIoPortType

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)

##
# Read any IO port type: DI, DO, AI, AO, GO, RO, FLAG, RI, UI, UO
di = robot.rmi.read_io_port(RmiIoPortType.DI, 1)
print(f"DI[1] = {di.value}")

ai = robot.rmi.read_io_port(RmiIoPortType.AI, 1)
print(f"AI[1] = {ai.value}")

flag = robot.rmi.read_io_port(RmiIoPortType.FLAG, 5)
print(f"FLAG[5] = {flag.value}")

# Write AO[1] = 2.5
robot.rmi.write_io_port(RmiIoPortType.AO, 1, 2.5)

# Write GO[1] = 7
robot.rmi.write_io_port(RmiIoPortType.GO, 1, 7)

# Write FLAG[5] = 1
robot.rmi.write_io_port(RmiIoPortType.FLAG, 5, 1)
##

robot.disconnect()
```

## Position registers

**C# : RmiFramesIoPositionRegisters**
```csharp
using System;
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.Common;
using UnderAutomation.Fanuc.Rmi.Data;

public class RmiFramesIoPositionRegisters
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);

        /**/
        // Read position register PR[1]
        RmiPositionRegisterDataResponse pr = robot.Rmi.ReadPositionRegister(1);
        Console.WriteLine($"PR[1]: X={pr.CartesianPosition.X:F1}  Y={pr.CartesianPosition.Y:F1}  Z={pr.CartesianPosition.Z:F1}");

        // Write position register PR[2] with a Cartesian value
        robot.Rmi.WritePositionRegisterCartesian(2,
            new CartesianPositionWithUserFrame(500, 200, 300, 0, 90, 0, tool: 1, frame: 0));
        /**/

        robot.Disconnect();
    }
}
```

**Python : RmiFramesIoPositionRegisters**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters
from underautomation.fanuc.common.cartesian_position_with_user_frame import CartesianPositionWithUserFrame

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)

##
# Read position register PR[1]
pr = robot.rmi.read_position_register(1)
print(f"PR[1]: X={pr.cartesian_position.x:.1f}  Y={pr.cartesian_position.y:.1f}  Z={pr.cartesian_position.z:.1f}")

# Write position register PR[2] with a Cartesian value
robot.rmi.write_position_register_cartesian(
    2,
    CartesianPositionWithUserFrame(500, 200, 300, 0, 90, 0, 1, 0)
)
##

robot.disconnect()
```

## Numeric registers and system variables

**C# : RmiFramesIoRegisters**
```csharp
using System;
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.Rmi.Data;

public class RmiFramesIoRegisters
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);

        /**/
        // Read numeric register R[1]
        RmiNumericRegisterValueResponse r1 = robot.Rmi.ReadNumericRegister(1);
        Console.WriteLine($"R[1] IsInteger={r1.Value.IsInteger}  Value={r1.Value.RealValue}");

        // Write integer value to R[1]
        robot.Rmi.WriteNumericRegisterAsInteger(1, 42);

        // Write float value to R[2]
        robot.Rmi.WriteNumericRegisterAsDouble(2, 3.14);

        // Read system variable $MCR.$GENOVERRIDE (include the leading $)
        RmiVariableValueResponse var = robot.Rmi.ReadVariable("$MCR.$GENOVERRIDE");
        Console.WriteLine($"Speed override = {var.RealValue}");

        // Write system variable
        robot.Rmi.WriteVariableAsInteger("$MCR.$GENOVERRIDE", 80);
        /**/

        robot.Disconnect();
    }
}
```

**Python : RmiFramesIoRegisters**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)

##
# Read numeric register R[1]
r1 = robot.rmi.read_numeric_register(1)
print(f"R[1] is_integer={r1.value.is_integer}  value={r1.value.real_value}")

# Write integer value to R[1]
robot.rmi.write_numeric_register_as_integer(1, 42)

# Write float value to R[2]
robot.rmi.write_numeric_register_as_double(2, 3.14)

# Read system variable $MCR.$GENOVERRIDE (include the leading $)
var = robot.rmi.read_variable("$MCR.$GENOVERRIDE")
print(f"Speed override = {var.real_value}")

# Write system variable
robot.rmi.write_variable_as_integer("$MCR.$GENOVERRIDE", 80)
##

robot.disconnect()
```

## Payload

Define payload mass, center of gravity, and inertia for a schedule. You can also send a `SetPayloadTpInstruction` as a motion-sequence instruction.

**C# : RmiFramesIoPayload**
```csharp
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.Rmi.Data;

public class RmiFramesIoPayload
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);

        /**/
        // Define payload for schedule 1: 5 kg, center of gravity offset 0.1 m in Z
        robot.Rmi.SetPayloadValue(
            scheduleNumber: 1,
            massKg: 5.0f,
            cgXm: 0f, cgYm: 0f, cgZm: 0.1f);

        // Include inertia values
        robot.Rmi.SetPayloadValue(
            scheduleNumber: 2,
            massKg: 3.0f,
            cgXm: 0.05f, cgYm: 0f, cgZm: 0.08f,
            inertiaXkgm2: 0.002f, inertiaYkgm2: 0.002f, inertiaZkgm2: 0.001f);

        // Define payload compensation
        robot.Rmi.SetPayloadCompensation(
            scheduleNumber: 1,
            massKg: 1.0f,
            cgXm: 0f, cgYm: 0f, cgZm: 0.05f,
            inertiaXkgm2: 0.001f, inertiaYkgm2: 0.001f, inertiaZkgm2: 0.0005f);

        // Activate schedule 1 immediately (command, not a TP instruction)
        robot.Rmi.SetPayloadSchedule(1);
        /**/

        robot.Disconnect();
    }
}
```

**Python : RmiFramesIoPayload**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)

##
# Define payload for schedule 1: 5 kg, center of gravity offset 0.1 m in Z
robot.rmi.set_payload_value(
    schedule_number=1,
    mass_kg=5.0,
    cg_xm=0.0, cg_ym=0.0, cg_zm=0.1
)

# Include inertia values
robot.rmi.set_payload_value(
    schedule_number=2,
    mass_kg=3.0,
    cg_xm=0.05, cg_ym=0.0, cg_zm=0.08,
    inertia_xkgm2=0.002, inertia_ykgm2=0.002, inertia_zkgm2=0.001
)

# Define payload compensation
robot.rmi.set_payload_compensation(
    schedule_number=1,
    mass_kg=1.0,
    cg_xm=0.0, cg_ym=0.0, cg_zm=0.05,
    inertia_xkgm2=0.001, inertia_ykgm2=0.001, inertia_zkgm2=0.0005
)

# Activate schedule 1 immediately (command, not a TP instruction)
robot.rmi.set_payload_schedule(1)
##

robot.disconnect()
```

## Position recording

The RMI Position Record menu (UTILITIES on the teach pendant) lets an operator jog the robot to a position and press **Record**. The controller sends the position back to the connected remote device as a packet. Subscribe to `RecordedCartesianPositionReceived` or `RecordedJointPositionReceived` to receive these positions.

The position ID is assigned by the controller and increments with each recorded position. Use it to correlate incoming positions with your application data.

**C# : RmiFramesIoPositionRecord**
```csharp
using System;
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.Rmi.Data;

public class RmiFramesIoPositionRecord
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);

        /**/
        // Subscribe before connecting to the RMI session.
        // The controller fires these events when the operator uses the
        // RMI Position Record menu on the teach pendant (UTILITIES > RMI Position Record)
        // and presses the Record key.

        robot.Rmi.RecordedCartesianPositionReceived += (RmiRecordedCartesianPosition rec) =>
        {
            Console.WriteLine($"Recorded Cartesian pos {rec.PositionId}:");
            Console.WriteLine($"  X={rec.Position.X:F1}  Y={rec.Position.Y:F1}  Z={rec.Position.Z:F1}");
            Console.WriteLine($"  W={rec.Position.W:F1}  P={rec.Position.P:F1}  R={rec.Position.R:F1}");
            Console.WriteLine($"  Tool={rec.Position.Tool}  Frame={rec.Position.Frame}");
        };

        robot.Rmi.RecordedJointPositionReceived += (RmiRecordedJointPosition rec) =>
        {
            Console.WriteLine($"Recorded joint pos {rec.PositionId}:");
            Console.WriteLine($"  J1={rec.Joints.J1:F2}  J2={rec.Joints.J2:F2}  J3={rec.Joints.J3:F2}");
            Console.WriteLine($"  J4={rec.Joints.J4:F2}  J5={rec.Joints.J5:F2}  J6={rec.Joints.J6:F2}");
        };
        /**/

        // Keep the application alive while the operator records positions
        Console.WriteLine("Waiting for positions. Press ENTER to exit.");
        Console.ReadLine();

        robot.Disconnect();
    }
}
```

**Python : RmiFramesIoPositionRecord**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)

##
# Subscribe before connecting to the RMI session.
# The controller fires these events when the operator uses the
# RMI Position Record menu on the teach pendant (UTILITIES > RMI Position Record)
# and presses the Record key.

def on_cartesian(rec):
    print(f"Recorded Cartesian pos {rec.position_id}:")
    print(f"  X={rec.position.x:.1f}  Y={rec.position.y:.1f}  Z={rec.position.z:.1f}")
    print(f"  W={rec.position.w:.1f}  P={rec.position.p:.1f}  R={rec.position.r:.1f}")
    print(f"  Tool={rec.position.tool}  Frame={rec.position.frame}")

robot.rmi.recorded_cartesian_position_received(on_cartesian)

def on_joint(rec):
    print(f"Recorded joint pos {rec.position_id}:")
    print(f"  J1={rec.joints.j1:.2f}  J2={rec.joints.j2:.2f}  J3={rec.joints.j3:.2f}")
    print(f"  J4={rec.joints.j4:.2f}  J5={rec.joints.j5:.2f}  J6={rec.joints.j6:.2f}")

robot.rmi.recorded_joint_position_received(on_joint)
##

# Keep the application alive while the operator records positions
input("Waiting for positions. Press ENTER to exit.")

robot.disconnect()
```

## Complete example

**C# : RmiFramesIo**
```csharp
using System;
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.Common;
using UnderAutomation.Fanuc.Rmi.Data;
using UnderAutomation.Fanuc.Rmi.TpInstructions;

public class RmiFramesIo
{
    static void Main()
    {
        FanucRobot robot = new FanucRobot();
        ConnectionParameters parameters = new ConnectionParameters("192.168.0.1");
        parameters.Rmi.Enable = true;
        robot.Connect(parameters);

        Console.WriteLine("Protocol version: " + robot.Rmi.MajorVersion + "." + robot.Rmi.MinorVersion);

        // Verify the controller is ready
        var status = robot.Rmi.GetStatus();
        if (!status.ServoReady || status.TPEnabled)
        {
            Console.WriteLine("Controller not ready for RMI.");
            return;
        }

        // Read current position before moving
        var pos = robot.Rmi.ReadCartesianPosition();
        Console.WriteLine($"Start: X={pos.Position.X:F1} Y={pos.Position.Y:F1} Z={pos.Position.Z:F1}");

        // Read UFrame 1
        var uf = robot.Rmi.ReadUFrame(1);
        Console.WriteLine($"UFrame 1 origin: X={uf.Frame.X:F1}");

        // Read DI[1]
        var din = robot.Rmi.ReadDIN(1);
        Console.WriteLine($"DI[1] = {din.PortValue}");

        // Read R[1]
        var r1 = robot.Rmi.ReadNumericRegister(1);
        Console.WriteLine($"R[1] = {r1.Value.RealValue}");

        // Initialize and send some motion
        robot.Rmi.Initialize();
        robot.Rmi.SetOverride(50);

        robot.Rmi.SendTpInstruction(new LinearMotionTpInstruction
        {
            SpeedType = RmiLinearSpeedType.MmSec,
            Speed = 100,
            TermType = RmiTerminationType.Fine,
            Target = new CartesianPositionWithUserFrame(500, 200, 300, 0, 90, 0, 1, 0)
        }).WaitForCompletion();

        // Write DO[1] ON
        robot.Rmi.WriteDOUT(1, RmiOnOff.ON);

        // Read TCP speed
        var tcp = robot.Rmi.ReadTcpSpeed();
        Console.WriteLine($"TCP speed: {tcp.Speed:F1} mm/s");

        robot.Rmi.Abort();
        robot.Disconnect();
    }
}
```

**Python : RmiFramesIo**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from underautomation.fanuc.connection_parameters import ConnectionParameters
from underautomation.fanuc.rmi.tp_instructions.linear_motion_tp_instruction import LinearMotionTpInstruction
from underautomation.fanuc.rmi.data.rmi_linear_speed_type import RmiLinearSpeedType
from underautomation.fanuc.rmi.data.rmi_termination_type import RmiTerminationType
from underautomation.fanuc.rmi.data.rmi_on_off import RmiOnOff
from underautomation.fanuc.common.cartesian_position_with_user_frame import CartesianPositionWithUserFrame
from underautomation.fanuc.common.xyzwpr_position import XYZWPRPosition

robot = FanucRobot()
parameters = ConnectionParameters("192.168.0.1")
parameters.rmi.enable = True
robot.connect(parameters)

print("Protocol version:", robot.rmi.major_version, ".", robot.rmi.minor_version)

# Verify the controller is ready
status = robot.rmi.get_status()
if not status.servo_ready or status.tp_enabled:
    print("Controller not ready for RMI.")
    robot.disconnect()
    exit()

# Read current position before moving
pos = robot.rmi.read_cartesian_position()
print(f"Start: X={pos.position.x:.1f} Y={pos.position.y:.1f} Z={pos.position.z:.1f}")

# Read UFrame 1
uf = robot.rmi.read_u_frame(1)
print(f"UFrame 1 origin: X={uf.frame.x:.1f}")

# Read DI[1]
din = robot.rmi.read_din(1)
print(f"DI[1] = {din.port_value}")

# Read R[1]
r1 = robot.rmi.read_numeric_register(1)
print(f"R[1] = {r1.value.real_value}")

# Initialize and send some motion
robot.rmi.initialize()
robot.rmi.set_override(50)

instr = LinearMotionTpInstruction()
instr.speed_type = RmiLinearSpeedType.MmSec
instr.speed = 100
instr.term_type = RmiTerminationType.Fine
instr.target = CartesianPositionWithUserFrame(500, 200, 300, 0, 90, 0, 1, 0)
robot.rmi.send_tp_instruction(instr).wait_for_completion()

# Write DO[1] ON
robot.rmi.write_dout(1, RmiOnOff.ON)

# Read TCP speed
tcp = robot.rmi.read_tcp_speed()
print(f"TCP speed: {tcp.speed:.1f} mm/s")

robot.rmi.abort()
robot.disconnect()
```

## API reference

**Members of Rmi.Data.RmiControllerStatusResponse**
```csharp
public class RmiControllerStatusResponse : RmiResponseBase {
    public RmiControllerStatusResponse()

    // Indicates the value of $RMI_CFG.$Chk_seqID, which is the configuration value that determines whether the controller checks valid incremented sequence IDs on incoming instructions.
    public bool CheckSequenceId { get; }

    public override bool Equals(object obj)

    public override int GetHashCode()

    // The next valid sequence ID. This key is only valid if the system variable $RMI_CFG.$Chk_seqID = TRUE
    public int? NextSequenceId { get; set; }

    // Number of user frames available in the robot controller
    public byte NumberUFrame { get; set; }

    // Number of user tools available in the robot controller
    public byte NumberUTool { get; set; }

    // RMI_MOVE program status
    public TaskStatus ProgramStatus { get; set; }

    // The Remote Motion Interface is running
    public bool RmiMotionStatus { get; set; }

    // The robot controller is ready for motion
    public bool ServoReady { get; set; }

    // Single step mode
    public bool SingleStepMode { get; set; }

    // The current speed override setting (1–100).
    public byte SpeedOverride { get; set; }

    // Teach Pendant Enabled (Switch on position ON) The Remote Motion interface only works when the teach pendant is disabled
    public bool TPEnabled { get; set; }

    public override string ToString()
}
```

**Members of Rmi.Data.RmiExtendedControllerStatusResponse**
```csharp
public class RmiExtendedControllerStatusResponse : RmiResponseBase {
    public RmiExtendedControllerStatusResponse()

    // Active control mode string (e.g. "AUTO"), or null when unavailable.
    public string ControlMode { get; set; }

    // Whether the servo drives are powered on.
    public bool DrivesPowered { get; set; }

    // Last reported error code text, or null when no error is active.
    public string ErrorCode { get; set; }

    // General speed override percentage.
    public int GenOverride { get; set; }

    // Whether the robot is currently executing a motion.
    public bool InMotion { get; set; }

    // Speed clamp limit in mm/s, or null when not configured.
    public double? SpeedClampLimit { get; set; }

    public override string ToString()
}
```

**Members of Rmi.Data.RmiUFrameUToolNumbersResponse**
```csharp
public class RmiUFrameUToolNumbersResponse : RmiResponseBase {
    public RmiUFrameUToolNumbersResponse()

    public override bool Equals(object obj)

    // Current user frame number.
    public byte Frame { get; set; }

    public override int GetHashCode()

    // Motion group number, or null when not applicable.
    public byte? Group { get; set; }

    public override string ToString()

    // Current user tool number.
    public byte Tool { get; set; }
}
```

**Members of Rmi.Data.RmiIndexedFrameResponse**
```csharp
public class RmiIndexedFrameResponse : RmiResponseBase {
    public RmiIndexedFrameResponse()

    public override bool Equals(object obj)

    // Frame data.
    public XYZWPRPosition Frame { get; set; }

    public override int GetHashCode()

    // Index (UFRAME or UTOOL number).
    public byte Index { get; set; }

    public override string ToString()
}
```

**Members of Rmi.Data.RmiDigitalInputValueResponse**
```csharp
public class RmiDigitalInputValueResponse : RmiResponseBase {
    public RmiDigitalInputValueResponse()

    public override bool Equals(object obj)

    public override int GetHashCode()

    // Port number.
    public short PortNumber { get; set; }

    // Port value
    public RmiOnOff PortValue { get; set; }

    public override string ToString()
}
```

**Members of Rmi.Data.RmiIoPortValueResponse**
```csharp
public class RmiIoPortValueResponse : RmiResponseBase {
    public RmiIoPortValueResponse()

    // Port number.
    public int PortNumber { get; set; }

    // Port type (DI, DO, AI, AO, GO, etc.).
    public RmiIoPortType PortType { get; set; }

    public override string ToString()

    // Current port value.
    public double Value { get; set; }
}
```

**Members of Rmi.Data.RmiCartesianPositionResponse**
```csharp
public class RmiCartesianPositionResponse : RmiTimedResponse {
    public RmiCartesianPositionResponse()

    public override bool Equals(object obj)

    public override int GetHashCode()

    // Current TCP position including configuration and active frame/tool numbers.
    public CartesianPositionWithUserFrame Position { get; set; }

    public override string ToString()
}
```

**Members of Rmi.Data.RmiJointAnglesSampleResponse**
```csharp
public class RmiJointAnglesSampleResponse : RmiTimedResponse {
    public RmiJointAnglesSampleResponse()

    public override bool Equals(object obj)

    public override int GetHashCode()

    // Joint angle set in degrees.
    public JointsPosition JointAngle { get; set; }

    public override string ToString()
}
```

**Members of Rmi.Data.RmiPositionRegisterDataResponse**
```csharp
public class RmiPositionRegisterDataResponse : RmiResponseBase {
    public RmiPositionRegisterDataResponse()

    // Position register value.
    public CartesianPositionWithUserFrame CartesianPosition { get; set; }

    public override bool Equals(object obj)

    public override int GetHashCode()

    // Register number
    public short RegisterNumber { get; set; }

    public override string ToString()
}
```

**Members of Rmi.Data.RmiNumericRegisterValueResponse**
```csharp
public class RmiNumericRegisterValueResponse : RmiResponseBase {
    public RmiNumericRegisterValueResponse()

    // Register number.
    public int RegisterNumber { get; set; }

    public override string ToString()

    // Register value.
    public NumericRegister Value { get; set; }
}
```

**Members of Rmi.Data.RmiVariableValueResponse**
```csharp
public class RmiVariableValueResponse : RmiResponseBase {
    public RmiVariableValueResponse()

    // Gets or sets the value as an integer. Internally stored as a double.
    public int IntegerValue { get; set; }

    // Whether the variable holds a floating-point value.
    public bool IsInteger { get; set; }

    // Variable name, including the leading <code>$</code> character.
    public string Name { get; set; }

    // Gets or sets the value as a double-precision floating-point number.
    public double RealValue { get; set; }

    public override string ToString()
}
```

**Members of Rmi.Data.RmiTcpSpeedResponse**
```csharp
public class RmiTcpSpeedResponse : RmiTimedResponse {
    public RmiTcpSpeedResponse()

    public override bool Equals(object obj)

    public override int GetHashCode()

    // Current tool center point speed in mm/s.
    public double Speed { get; set; }

    public override string ToString()
}
```

**Members of Rmi.Data.RmiSetPayloadParameters**
```csharp
public class RmiSetPayloadParameters {
    public RmiSetPayloadParameters()

    // Center-of-gravity X offset in meters.
    public float CgXm { get; set; }

    // Center-of-gravity Y offset in meters.
    public float CgYm { get; set; }

    // Center-of-gravity Z offset in meters.
    public float CgZm { get; set; }

    // Optional motion group number. <code>null</code> uses the active group.
    public byte? Group { get; set; }

    // Inertia around the X axis in kg·m². <code>null</code> omits this field from the command.
    public float? InertiaXkgm2 { get; set; }

    // Inertia around the Y axis in kg·m². <code>null</code> omits this field from the command.
    public float? InertiaYkgm2 { get; set; }

    // Inertia around the Z axis in kg·m². <code>null</code> omits this field from the command.
    public float? InertiaZkgm2 { get; set; }

    // Payload mass in kilograms.
    public float MassKg { get; set; }

    // Payload schedule number to configure.
    public byte ScheduleNumber { get; set; }
}
```

**Members of Rmi.Data.RmiSetPayloadCompensationParameters**
```csharp
public class RmiSetPayloadCompensationParameters {
    public RmiSetPayloadCompensationParameters()

    // Center-of-gravity X offset in meters.
    public float CgXm { get; set; }

    // Center-of-gravity Y offset in meters.
    public float CgYm { get; set; }

    // Center-of-gravity Z offset in meters.
    public float CgZm { get; set; }

    // Optional motion group number. <code>null</code> uses the active group.
    public byte? Group { get; set; }

    // Inertia around the X axis in kg·m².
    public float InertiaXkgm2 { get; set; }

    // Inertia around the Y axis in kg·m².
    public float InertiaYkgm2 { get; set; }

    // Inertia around the Z axis in kg·m².
    public float InertiaZkgm2 { get; set; }

    // Payload mass in kilograms.
    public float MassKg { get; set; }

    // Payload schedule number to configure.
    public byte ScheduleNumber { get; set; }
}
```

**Members of Rmi.Data.RmiRecordedCartesianPosition**
```csharp
public class RmiRecordedCartesianPosition {
    public RmiRecordedCartesianPosition()

    // Recorded Cartesian position including arm configuration and active frame/tool numbers.
    public CartesianPositionWithUserFrame Position { get; set; }

    // Position identifier assigned by the controller.
    public ushort PositionId { get; set; }

    public override string ToString()
}
```

**Members of Rmi.Data.RmiRecordedJointPosition**
```csharp
public class RmiRecordedJointPosition {
    public RmiRecordedJointPosition()

    // Recorded joint angles in degrees.
    public JointsPosition Joints { get; set; }

    // Position identifier assigned by the controller.
    public ushort PositionId { get; set; }

    public override string ToString()
}
```