> For the complete documentation index, see [llms.txt](https://docs.sonoransoftware.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sonoransoftware.com/store/sonoran-station-displays/dui-message-protocol.md).

# DUI Message Protocol

This page documents the internal boundary between the Lua client and the bundled browser. It is useful to maintainers but is not a promise that third-party browser replacements will remain compatible across versions.

The DUI URL is fixed:

```
https://cfx-nui-sonoran-stationdisplay/web/index.html?dui=1&displayId=<id>
```

Display configuration cannot provide a browser or stream URL.

## Browser to Lua

### `duiReady`

After initialization:

```json
{"displayId": "ssd-12345678-abcdef"}
```

Lua accepts it only for an existing renderer, marks the DUI ready, and forces the current state, game time, and weather. A renderer that does not become ready within the internal 10-second timeout is destroyed and retried after the internal delay.

### `bodycamFeedStatus`

The bodycam viewer can report:

```json
{"displayId": "ssd-...", "unitId": "unit-id", "status": "unavailable"}
```

The client acknowledges the callback and increments its diagnostic load-failure counter for `unavailable`. It does not trust the callback to change server runtime state.

### `bodycamSubscriptions`

The visible bodycam DUI sends its mounted display ID and up to four visible unit IDs. The server validates permission, range, routing bucket, current page, service filter, active state, and unit ownership. A 4-second heartbeat keeps the subscription alive; it expires after 8 seconds.

## Lua to browser

### `DISPLAY_STATE_UPDATE`

This is the primary state message:

```json
{
  "action": "DISPLAY_STATE_UPDATE",
  "payload": {
    "displayId": "ssd-12345678-abcdef",
    "displayName": "Mission Row Briefing",
    "theme": "MODERN",
    "page": "ACTIVE_UNITS",
    "serviceFilter": "LEO",
    "grouping": "SERVICE",
    "sorting": "CALLSIGN",
    "units": [],
    "emergencyCalls": [],
    "dispatchCalls": [],
    "bodycams": [],
    "bodycamConfig": {
      "layout": "AUTO",
      "maximumFeeds": 4,
      "rotationInterval": 15
    },
    "bodycamIntegration": {
      "available": true,
      "enabled": true,
      "mode": "LOCAL_CAPTURE",
      "captureIntervalMs": 4000,
      "maximumVisibleFeeds": 4,
      "maximumFrameBytes": 307200
    },
    "bodycamPresentation": {
      "delayedAfterSeconds": 8,
      "unavailableAfterSeconds": 30,
      "loadFailures": 0
    },
    "map": {
      "mode": "AUTO_FIT",
      "center": {"x": 215, "y": -810},
      "zoom": 1,
      "showCalls": true,
      "showStationMarker": true
    },
    "mapPresentation": {
      "minimumSpan": 600,
      "maximumSpan": 24000,
      "padding": 0.12,
      "debug": false
    },
    "station": {"x": 425, "y": -980, "z": 30},
    "maximumUnitsPerPage": 12,
    "maximumCallsPerPage": 8,
    "visibleCallFields": {
      "priority": true,
      "status": true,
      "address": true,
      "postal": true,
      "description": true,
      "assignedUnits": true,
      "caller": false
    },
    "rotation": {
      "enabled": true,
      "paused": false,
      "interval": 30,
      "elapsed": 0,
      "progress": 0,
      "pages": ["ACTIVE_UNITS", "EMERGENCY_CALLS"],
      "index": 1
    },
    "connected": true,
    "revision": 1,
    "staleAfterSeconds": 30,
    "serverTime": 1785182400
  }
}
```

Lua sends a changed state after readiness, cache/display/bodycam changes, explicit refresh, and runtime page changes. It also checks rotation progress at most once per second. Identical encoded state is suppressed unless forced; the browser animates progress between messages.

Important unit fields include ID, callsign, name, status/status code, service, agency, subdivision, location/postal, coordinates, assigned call, panic, update time, and stale state.

Important call fields include ID, kind, title/code, priority/status, address/postal, description, assigned IDs/callsigns, optional emergency caller, coordinates, timestamps, and classified service.

A bodycam feed includes joined server-known unit metadata plus active/mode, activation/update values, and state sequence. It contains no peer, URL, credential, or image payload.

### `BODYCAM_FRAME`

The server sends a subscribed viewer's client a validated JPEG with a rate-controlled latent event. Lua forwards it only to subscribed bodycam DUIs:

```json
{
  "action": "BODYCAM_FRAME",
  "payload": {
    "unitId": "unit-id",
    "sequence": 42,
    "mimeType": "image/jpeg",
    "image": "data:image/jpeg;base64,...",
    "receivedAt": 1785200000,
    "byteLength": 82413
  }
}
```

The browser converts it to a Blob object URL and revokes the previous URL. Frame contents are not included in state snapshots or diagnostics.

### `BODYCAM_STATUS`

Small status messages report `CAPTURE_FAILED` or `OFFLINE` without image data. The browser retains a prior frame briefly for delayed/failure presentation and removes it at the stale timeout.

### `GAME_TIME_UPDATE`

```json
{
  "action": "GAME_TIME_UPDATE",
  "payload": {
    "hours": 21,
    "minutes": 42,
    "seconds": 5,
    "format": 12,
    "showSeconds": false
  }
}
```

The client sends only when the formatted GTA/FiveM time changes, or when forced after readiness.

### `WEATHER_UPDATE`

```json
{
  "action": "WEATHER_UPDATE",
  "payload": {
    "weatherType": "CLEAR",
    "temperature": 78,
    "unit": "F"
  }
}
```

Weather values describe the GTA world and an estimated temperature.

### `MAP_PAN`

```json
{
  "action": "MAP_PAN",
  "payload": {"direction": "LEFT", "step": 0.2}
}
```

It is accepted only for an available local renderer currently on Live Map. The Lua side clamps the step to `0.05` through `0.5`. Reset is represented by the allowed reset direction used by the browser handler.

### `BODYCAM_NAVIGATE`

```json
{
  "action": "BODYCAM_NAVIGATE",
  "payload": {"direction": "NEXT"}
}
```

It is accepted only for an available local renderer currently on Bodycams and changes that DUI's bodycam feed page.

## Versioning

There is no explicit protocol-version field. Update Lua and all `web/` files together. Do not replace only `web/app.js` or copy browser files between resource releases.

The web source also contains dormant administration markup/handlers. The current Lua resource does not open that NUI or register its administrative callbacks; the supported customer interface is WarMenu.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.sonoransoftware.com/store/sonoran-station-displays/dui-message-protocol.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
