# Latency Measurement

Measure and troubleshoot OCP latency from the operator station.

Last validated: 2026-05-04

Oden Control Pipeline (OCP) reports several latency-related values. Use this page to verify that the latency loop is closed, understand what the operator should see, and decide whether a fault points at networking, video metadata, the vehicle-side TCP integration, or an operator-side client.

The important distinction is that the operator-side numbers are health indicators, while the `High Latency` fault is raised by the Streamer-side timestamp loop.

| Value | Where you see it | What it means |
| --- | --- | --- |
| `ping_latency_ms` | `ocp_vehicle_user_data.vehicle_feedback.<vehicle>.ping_latency_ms` and the OCP operator GUI as `Ping Latency` | A lightweight Oden com-channel ping from operator to vehicle and back. It is useful for network health, but it is not the video latency loop. |
| `message_latency_roundtrip` | `ocp_vehicle_user_data.vehicle_feedback.<vehicle>.message_latency_roundtrip` and the OCP operator GUI as `Operator Latency` | An operator-control-message timestamp returned by the Streamer feedback message. It shows that OCP control and feedback messages are flowing. It can be `null` before feedback arrives and can look stale when feedback is lost. |
| OCP timestamp-loop latency | Vehicle-side `telemetry.latency`, plus `receiver_fault` values such as `High Latency` or `Latency Loop Not Closed` | The Streamer-side measurement used for the `High Latency` fault. It follows the vehicle TCP response, video metadata path, operator echo, and return control message. |

## How the timestamp loop works

The latency loop does not require synchronized clocks between the vehicle computer and the operator computer. OCP uses a Streamer-side elapsed-time clock and protects the timestamp with MAC values.

```none
Streamer OCP elapsed clock
  -> OcpControlMessage over vehicle TCP
  -> vehicle integration copies ack fields into VehicleResponseMessage
  -> Streamer embeds ack fields as video frame metadata
  -> Player extracts metadata from the Remote Streamer
  -> operator-side OCP or client returns latest ack fields
  -> control message reaches Streamer
  -> Streamer validates MAC and computes elapsed_ms - returned_ack_time
```

The external values you must preserve are:

`ack_time`

A `u64` elapsed-milliseconds timestamp generated by Streamer-side OCP.

`ack_time_mac`

A `u32` MAC for `ack_time`. OCP randomizes the Streamer-side key on startup.

Do not edit, reinterpret, scale, round, or regenerate either field. Operator-side TCP clients and plugins should copy `ack_time` to `ack_time_returned` and `ack_time_mac` to `ack_time_mac_returned`. The OCP operator sender performs the internal return MAC calculation before sending the control message back to the Streamer.

## Message paths and ports

| Path | Default | Latency role |
| --- | --- | --- |
| Vehicle-side TCP | `127.0.0.1:4000` | Streamer OCP sends `OcpControlMessage` to the vehicle integration. The integration returns `VehicleResponseMessage` with `ack_time` and `ack_time_mac`. |
| Operator-side TCP | `127.0.0.1:4001` | Optional external operator client receives `VehicleOcpShared` feedback and sends `ClientOcpShared` data. If used, it must return the latest timestamp fields. |
| Webview messages | `ocp_vehicle_user_data` and `ocp_client_user_data` | Webviews receive vehicle feedback and can send operator-side user data. The injected `OdenLayoutClient` automatically echoes timestamps. |
| Plugin shared data | `vehicle_ocp` and `client_ocp` | Custom operator-side plugins read feedback and publish client data. Plugins must copy the timestamp fields manually. |
| Oden com-channel messages | `CONTROL_COMMAND_ID`, `FEEDBACK_MESSAGE_ID`, `PING_MESSAGE_ID`, `PONG_MESSAGE_ID` | Internal OCP transport for control, feedback, and ping measurements. |
| Video metadata | Streamer frame metadata / SEI | Carries the returned vehicle-side `ack_time` and `ack_time_mac` from Streamer video output to the operator-side Remote Streamer. |

Both TCP APIs use a 4-byte little-endian length prefix followed by UTF-8 JSON. Messages larger than 16 KB are dropped. By default, TCP servers bind only to localhost. Set `ocp_tcp_allow_remote` only when a TCP client must connect from another host, and protect that port at the network boundary.

## Configure the measurement

OCP plugin parameters are passed at startup with `--plugin-param`.

```shell
oden-streamer --plugin-param latency_limit 750
```

| Parameter | Default | Use |
| --- | --- | --- |
| `tcp_port` | `4000` | Vehicle-side TCP server port on the Streamer. |
| `ocp_player_tcp_port` | `4001` | Operator-side TCP server port on OdenVR / Player. |
| `ocp_tcp_allow_remote` | unset | Binds OCP TCP servers to `0.0.0.0` instead of `127.0.0.1`. |
| `latency_limit` | `500` | Threshold in milliseconds for the Streamer-side `High Latency` fault. Increase it only when the measured end-to-end control path is acceptable for the vehicle. |

These are the OCP settings that usually matter for latency measurement. For the overall OCP model, see [Oden Control Pipeline overview](ocp-overview.md).

## Validate a basic setup

Use this flow when OCP should handle gamepad input and no custom operator-side TCP or plugin client is involved.

1.  Enable the OCP global plugin in Oden Streamer and OdenVR / Player.
    
2.  Start the Streamer output and connect the Player so video is visible.
    
3.  Start the vehicle integration and connect it to the Streamer TCP server, normally `127.0.0.1:4000`.
    
4.  In every vehicle response, copy `ack_time` and `ack_time_mac` from the latest `OcpControlMessage`.
    
    ```json
    {
      "ack_time": 123456,
      "ack_time_mac": 987654,
      "vehicle_user_data": {
        "user_data": {}
      }
    }
    ```
    
5.  On the operator station, open the OCP plugin GUI or read `ocp_vehicle_user_data`.
    
6.  Confirm that `ping_latency_ms` is finite, `message_latency_roundtrip` becomes non-null, `ack_time` changes over time, and `receiver_fault` does not contain `High Latency` or `Latency Loop Not Closed`.
    

With no external client data source, OCP extracts the timestamp from video metadata and echoes it back internally. No JavaScript, operator plugin, or player-side TCP client is required.

## Add operator-side data safely

Only one operator-side client data source may send OCP client data in a session. The first source that sends data becomes the locked source. If OCP later receives client data from another source, it logs an error, stops all client data communication, and requires a restart.

Webview

Use `sendNamedUserMessage("ocp_client_user_data", payload)`. The injected `OdenLayoutClient` stores the latest `ack_time` and `ack_time_mac` from `ocp_vehicle_user_data` and injects them into each matching `client_user_data` entry. If no custom JavaScript sends OCP client data, the webview client auto-echoes timestamps with `user_data: null`.

Player-side TCP

Read `ack_time` and `ack_time_mac` from `VehicleOcpShared.vehicle_feedback`. Return them as `ack_time_returned` and `ack_time_mac_returned` in the matching vehicle entry.

Custom operator plugin

Read `vehicle_ocp` shared data. Publish `client_ocp` shared data with `ack_time_returned`, `ack_time_mac_returned`, optional `user_data`, and optional `ocp_disable_gamepad`.

For a TCP or plugin client, send the latest timestamp for the same vehicle name that received it:

```json
{
  "active_vehicle": "vehicle_a",
  "client_user_data": {
    "vehicle_a": {
      "user_data": {
        "mode": "work"
      },
      "ocp_disable_gamepad": false,
      "ack_time_returned": 123456,
      "ack_time_mac_returned": 987654
    }
  }
}
```

In multi-vehicle sessions, return timestamp fields for each vehicle you include in `client_user_data`. Set `active_vehicle` to the vehicle that should receive OCP gamepad input.

## Read operator feedback

A webview can display the values the operator needs with the Oden JavaScript SDK:

```javascript
const client = getOrCreateOdenLayoutClient();

client.registerUserMessageCallback("ocp_vehicle_user_data", (payload) => {
  for (const [vehicleName, feedback] of Object.entries(payload.vehicle_feedback ?? {})) {
    console.log(vehicleName, {
      ping: feedback.ping_latency_ms,
      messageRoundtrip: feedback.message_latency_roundtrip,
      ackTime: feedback.ack_time,
      senderFaults: feedback.sender_fault,
      receiverFaults: feedback.receiver_fault,
    });
  }
});
```

For operator UIs, show both fault arrays and at least these values:

-   `ping_latency_ms` for network health.
    
-   `message_latency_roundtrip` for OCP control/feedback message health.
    
-   `receiver_fault` for Streamer-side faults, especially `High Latency` and `Latency Loop Not Closed`.
    
-   `sender_fault` for operator-side faults, especially `Input Lost`, `No Feedback Data`, and `Vehicle Control Disabled`.
    
-   `ack_time` when diagnosing whether video metadata is arriving.
    

Do not treat `ping_latency_ms` or `message_latency_roundtrip` as the same value that raises `High Latency`. The high-latency decision is made on the Streamer from the validated `ack_time` loop.

## OCP GUI field guide

The OCP plugin GUI is available from the engineering sidebar when the plugin is enabled.

On OdenVR / Player:

Operator Latency

Round-trip time from operator to vehicle and back for OCP message feedback. It may be stale when feedback is lost.

Ping Latency

Lightweight network ping from operator to vehicle and back.

Last Timestamp

Last Streamer-side `ack_time` received from video metadata.

Controller

Last local controller input.

Vehicle Data

Latest vehicle-side user data repeated from feedback.

Missing Cameras

Cameras whose drop detector reports missing frames. An unnamed 2D video appears as `Unknown Camera`; inputs with drop detection disabled are ignored.

Faults

Active OCP faults for the operator and vehicle path.

Client Data

Operator-side data being sent into OCP and forwarded to the Streamer side.

On Oden Streamer:

Ack Time

Timestamp values OCP adds to outgoing frames for latency measurement.

Faults

Active receiver faults on the Streamer side.

Controller Commands

Last control data sent over the vehicle-side TCP connection, including fault state.

Vehicle Userdata

Last vehicle data received from the vehicle-side TCP client.

## Troubleshoot symptoms

| Symptom | What to check |
| --- | --- |
| `Vehicle Not Responding` | No vehicle-side TCP client is connected to the Streamer OCP server. Start the vehicle integration, verify the port, and remember that the default bind address is `127.0.0.1`. |
| `No Control Messages` | Streamer OCP has not received operator control messages for more than 1000 ms. Check the Player connection, Remote Streamer mapping, Fleet active vehicle state, and OCP plugin state on both sides. |
| `No Feedback Data` | Operator OCP is not receiving Streamer feedback messages. Check the video/control connection first, then check Streamer-side OCP faults and logs. |
| `Camera Lost` | Streamer OCP found a monitored camera whose valid-frame timestamp is too old. Check the camera input, the Output Alignment children, and the camera drop detector. Cameras with drop detection disabled are not reported in this list. |
| `Streamer Slow` | The Streamer frame loop is taking too long. Check CPU/GPU load, encoder load, blocking plugins, and whether the Streamer is overloaded. |
| `Input Lost` | No gamepad input is available while OCP expects to forward gamepad input. Connect a gamepad or set `ocp_disable_gamepad: true` from the single operator-side client that provides its own controls. |
| `Vehicle Control Disabled` | This vehicle is not the active control target. In multi-vehicle sessions, send `active_vehicle`. It is normal for connected but non-active vehicles to report this. |
| `Latency Loop Not Closed` | Timestamp validation failed. Usually `ack_time` or `ack_time_mac` was missing, modified, returned for the wrong vehicle, or not copied through an operator-side TCP/plugin client. Also check that a webview, TCP client, and plugin are not all sending OCP client data in the same session. |
| `High Latency` | The timestamp loop is valid, but the latest returned timestamp is older than `latency_limit`. Check video transport latency, network congestion, Player decode/display latency, vehicle integration delay, and how often the operator-side client returns fresh timestamps. |
| `Operator Station Has Error` | The operator side reported a sender fault to the Streamer. Read the same vehicle’s `sender_fault` array and fix `Input Lost`, `No Feedback Data`, or `Vehicle Control Disabled` first. |
| `ack_time` stays `0` or does not change | The operator is not receiving valid video metadata. Confirm the vehicle integration returns `ack_time` and `ack_time_mac`, Streamer output is running, the Player receives the Remote Streamer video, and metadata extraction is enabled for the receive path. If another plugin writes Streamer frame metadata in the same frame, OCP can fail to set its timestamp metadata and will log that failure. |
| `ping_latency_ms` is missing, huge, or unstable | The Oden com-channel ping/pong path is unhealthy. Check Remote Streamer connection state, packet loss, route, firewall, relay path, and whether the vehicle is still connected. |
| Client data stops after it was working | Look for an OCP GUI error saying client data was received from another source. Restart Oden after removing the duplicate source. An open webview can auto-echo timestamps and become the webview source. |
| `Internal Error (1)` | OCP’s internal control-thread queue is delayed. Check CPU load, Streamer frame time, blocking plugin work, and application logs. |

## Delivery checklist

Before handing over an OCP integration:

-   Vehicle TCP connects on the intended `tcp_port` and reconnects cleanly.
    
-   Every `VehicleResponseMessage` copies `ack_time` and `ack_time_mac` unmodified.
    
-   Only one operator-side client data source sends `ocp_client_user_data` or `client_ocp`.
    
-   Multi-vehicle operator data uses vehicle names consistently and sets `active_vehicle`.
    
-   Operator UI shows `ping_latency_ms`, `message_latency_roundtrip`, `sender_fault`, and `receiver_fault`.
    
-   `High Latency` and `Latency Loop Not Closed` are absent during a realistic driving or operating test.
    

See also [Oden Control Pipeline overview](ocp-overview.md), [Vehicle-side control](vehicle-side-control.md), [Operator-side control](operator-side-control.md), [Webview and JavaScript SDK](webview-sdk.md), and [Oden troubleshooting](../operate/troubleshooting.md).
