This article shows how to follow the state of a Universal Robots cobot from a PC, in C# or Python: robot mode, power, program, protective and emergency stops, safety status and connection. It compares the interfaces and gives a program that prints each change.

## Which way to choose

| Way               | Frequency     | What it gives                                                                               |
| ----------------- | ------------- | ------------------------------------------------------------------------------------------- |
| Primary Interface | 10 Hz, events | `RobotModeData`: robot mode, power, program running or paused, stops, speed fraction. `MasterboardData.Safetymode` |
| RTDE              | Up to 500 Hz  | `RobotMode`, `SafetyMode`, `SafetyStatus`, `RuntimeState`, `RobotStatusBits`, `SpeedScaling`, as numbers |
| Dashboard Server  | When you ask  | `GetRobotMode()`, `GetProgramState()` with the name of the program, `GetSafetyStatus()`, `IsInRemoteControl()`, `GetOperationalMode()` |
| REST API          | When you ask  | `GetProgramState()` on PolyScope X                                                          |

The Primary Interface gives typed values and events, with no setup. RTDE gives the same states as numbers, faster: see the [RTDE guide](https://www.universal-robots.com/articles/ur/interface-communication/real-time-data-exchange-rtde-guide/) of Universal Robots for their values. The Dashboard Server adds the name of the loaded program and the remote control.

## Prerequisites

- The services `Primary Client Interface` and `Dashboard Server` are enabled: see [Prepare the robot](/universal-robots/documentation/connect#prepare_the_robot).
- No remote control is needed to read the state.

## Example

The program prints the robot mode package when it changes, the safety status when it is not normal, and the loaded program every 2 s, until the connection is lost.

**C# : HowToMonitorState**
```csharp
using UnderAutomation.UniversalRobots;
using UnderAutomation.UniversalRobots.Common;
using UnderAutomation.UniversalRobots.PrimaryInterface;

class HowToMonitorState
{
  static void Main(string[] args)
  {
    /**/
    var robot = new UR();

    // Primary Interface and Dashboard Server
    robot.Connect("192.168.0.1");

    // Robot mode package, 10 times per second: print the changes only
    string last = null;
    robot.PrimaryInterface.RobotModeDataReceived += (sender, e) =>
    {
      string state =
        $"mode={e.RobotMode} powered={e.RobotPowerOn} running={e.ProgramRunning} " +
        $"paused={e.ProgramPaused} protective stop={e.ProtectiveStopped} " +
        $"emergency stop={e.EmergencyStopped} speed={e.TargetSpeedFraction:P0}";

      if (state != last) Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " " + state);
      last = state;
    };

    // Safety status, in the masterboard package
    robot.PrimaryInterface.MasterboardDataReceived += (sender, e) =>
    {
      if (e.Safetymode != SafetyStatus.Normal) Console.WriteLine("Safety: " + e.Safetymode);
    };

    // Lost connection: the property becomes false, there is no event
    while (robot.PrimaryInterface.Connected)
    {
      // Loaded program and its state, every 2 s
      var program = robot.Dashboard.GetProgramState();
      if (program.Succeed) Console.WriteLine(program.Value.Name + ": " + program.Value.State);

      Thread.Sleep(2000);
    }

    Console.WriteLine("Connection lost");
    /**/
  }
}
```

**Python : HowToMonitorState**
```python
import time
from datetime import datetime
from underautomation.universal_robots.ur import UR
from underautomation.universal_robots.common.safety_status import SafetyStatus

##
robot = UR()

# Primary Interface and Dashboard Server
robot.connect("192.168.0.1")

# Robot mode package, 10 times per second: print the changes only
last = [""]

def on_robot_mode(sender, e):
    m = robot.primary_interface.robot_mode_data
    state = (f"mode={m.robot_mode} powered={m.robot_power_on} running={m.program_running} "
             f"paused={m.program_paused} protective stop={m.protective_stopped} "
             f"emergency stop={m.emergency_stopped} speed={m.target_speed_fraction:.0%}")
    if state != last[0]:
        print(datetime.now().strftime("%H:%M:%S.%f")[:-3], state)
    last[0] = state

robot.primary_interface.robot_mode_data_received(on_robot_mode)

# Safety status, in the masterboard package
def on_masterboard(sender, e):
    safety = robot.primary_interface.masterboard_data.safetymode
    if safety != SafetyStatus.Normal:
        print("Safety:", safety)

robot.primary_interface.masterboard_data_received(on_masterboard)

# Lost connection: the property becomes False, there is no event
while robot.primary_interface.connected:
    # Loaded program and its state, every 2 s
    program = robot.dashboard.get_program_state()
    if program.succeed:
        print(f"{program.value.name}: {program.value.state}")
    time.sleep(2)

print("Connection lost")
##
```

The events run in a thread of the SDK. Keep them short, and copy the values you need to your own thread.

## Connection

`PrimaryInterface.Connected` and `Rtde.Connected` become `false` when the connection is lost: no event is raised and the SDK does not reconnect. Poll them, and call `Connect` again. `InternalErrorOccured` is raised for the errors of the background threads.

## What to read next

- [Read and reset alarms](/universal-robots/documentation/how-to-read-reset-alarms): the messages of the controller and the safety status.
- [Primary Interface](/universal-robots/documentation/data-streaming): the content of each package.