# DVM Host REST API Technical Documentation **Version:** 1.0 **Date:** December 3, 2025 **Author:** AI Assistant (based on source code analysis) AI WARNING: This document was mainly generated using AI assistance. As such, there is the possibility of some error or inconsistency. Examples in Section 13 are *strictly* examples only for how the API *could* be used. --- ## Table of Contents 1. [Overview](#1-overview) 2. [Authentication](#2-authentication) 3. [System Endpoints](#3-system-endpoints) 4. [Modem Control](#4-modem-control) 5. [Trunking and Supervisory Control](#5-trunking-and-supervisory-control) 6. [Radio ID Lookup](#6-radio-id-lookup) 7. [DMR Protocol Endpoints](#7-dmr-protocol-endpoints) 8. [P25 Protocol Endpoints](#8-p25-protocol-endpoints) 9. [NXDN Protocol Endpoints](#9-nxdn-protocol-endpoints) 10. [Response Formats](#10-response-formats) 11. [Error Handling](#11-error-handling) 12. [Security Considerations](#12-security-considerations) 13. [Examples](#13-examples) --- ## 1. Overview The DVM (Digital Voice Modem) Host REST API provides a comprehensive interface for managing and controlling dvmhost instances. The dvmhost software interfaces directly with radio modems to provide DMR, P25, and NXDN digital voice repeater/hotspot functionality. The REST API allows remote configuration, mode control, protocol-specific operations, and real-time monitoring. ### 1.1 Base Configuration **Default Ports:** - HTTP: User-configurable (typically 9990) - HTTPS: User-configurable (typically 9443) **Transport:** - Protocol: HTTP/1.1 or HTTPS - Content-Type: `application/json` - Character Encoding: UTF-8 **SSL/TLS Support:** - Optional HTTPS with certificate-based security - Configurable via `keyFile` and `certFile` parameters - Uses OpenSSL when `ENABLE_SSL` is defined ### 1.2 API Architecture The REST API is built on: - **Request Dispatcher:** Routes HTTP requests to appropriate handlers - **HTTP/HTTPS Server:** Handles network connections - **Authentication Layer:** Token-based authentication using SHA-256 - **Protocol Handlers:** Interfaces with DMR, P25, and NXDN control classes - **Lookup Tables:** Radio ID and Talkgroup Rules ### 1.3 Use Cases - **Remote Mode Control:** Switch between DMR, P25, NXDN, or idle modes - **Diagnostics:** Enable/disable debug logging for protocols - **Trunking Operations:** Grant channels, permit talkgroups, manage affiliations - **Radio Management:** Send radio checks, inhibits, pages, and other RID commands - **Control Channel Management:** Enable/disable control channels and broadcast modes - **System Monitoring:** Query status, voice channels, and affiliations --- ## 2. Authentication All API endpoints (except `/auth`) require authentication using a token-based system. ### 2.1 Authentication Flow 1. Client sends password hash to `/auth` endpoint 2. Server validates password and returns authentication token 3. Client includes token in `X-DVM-Auth-Token` header for subsequent requests 4. Tokens are bound to client IP/host and remain valid for the session ### 2.2 Endpoint: PUT /auth **Method:** `PUT` **Description:** Authenticate with the dvmhost REST API and obtain an authentication token. **Request Headers:** ``` Content-Type: application/json ``` **Request Body:** ```json { "auth": "sha256_hash_of_password_in_hex" } ``` **Password Hash Format:** - Algorithm: SHA-256 - Encoding: Hexadecimal string (64 characters) - Example: `"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"` (hash of "password") **Response (Success):** ```json { "status": 200, "token": "12345678901234567890" } ``` **Response (Failure):** ```json { "status": 400, "message": "invalid password" } ``` **Error Conditions:** - `400 Bad Request`: Invalid password, malformed auth string, or invalid characters - `401 Unauthorized`: Authentication failed **Notes:** - Password must be pre-hashed with SHA-256 on client side - Token is a 64-bit unsigned integer represented as a string - Tokens are invalidated when: - Client authenticates again - Server explicitly invalidates the token - Server restarts - Auth string must be exactly 64 hexadecimal characters - Valid characters: `0-9`, `a-f`, `A-F` **Example (bash with curl):** ```bash # Generate SHA-256 hash of password PASSWORD="your_password_here" HASH=$(echo -n "$PASSWORD" | sha256sum | cut -d' ' -f1) # Authenticate TOKEN=$(curl -X PUT http://dvmhost.example.com:9990/auth \ -H "Content-Type: application/json" \ -d "{\"auth\":\"$HASH\"}" | jq -r '.token') echo "Token: $TOKEN" ``` --- ## 3. System Endpoints ### 3.1 Endpoint: GET /version **Method:** `GET` **Description:** Retrieve dvmhost software version information. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Response:** ```json { "status": 200, "version": "dvmhost 4.0.0 (built Dec 03 2025 12:00:00)" } ``` **Notes:** - Returns program name, version, and build timestamp - Useful for compatibility checks and diagnostics --- ### 3.2 Endpoint: GET /status **Method:** `GET` **Description:** Retrieve current dvmhost system status and configuration. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Response:** ```json { "status": 200, "dmrEnabled": true, "p25Enabled": true, "nxdnEnabled": false, "state": 4, "isTxCW": false, "fixedMode": false, "dmrTSCCEnable": false, "dmrCC": false, "p25CtrlEnable": true, "p25CC": true, "nxdnCtrlEnable": false, "nxdnCC": false, "tx": false, "channelId": 1, "channelNo": 1, "lastDstId": 9001, "lastSrcId": 123456, "peerId": 10001, "sysId": 1, "siteId": 1, "p25RfssId": 1, "p25NetId": 48896, "p25NAC": 293, "vcChannels": [ { "channelNo": 1, "channelId": 1, "tx": false, "lastDstId": 9001, "lastSrcId": 123456 } ], "modem": { "portType": "uart", "modemPort": "/dev/ttyUSB0", "portSpeed": 115200, "pttInvert": false, "rxInvert": false, "txInvert": false, "dcBlocker": true, "rxLevel": 50.0, "cwTxLevel": 50.0, "dmrTxLevel": 50.0, "p25TxLevel": 50.0, "nxdnTxLevel": 50.0, "rxDCOffset": 0, "txDCOffset": 0, "dmrSymLevel3Adj": 0, "dmrSymLevel1Adj": 0, "p25SymLevel3Adj": 0, "p25SymLevel1Adj": 0, "nxdnSymLevel3Adj": 0, "nxdnSymLevel1Adj": 0, "fdmaPreambles": 80, "dmrRxDelay": 7, "p25CorrCount": 4, "rxFrequency": 449000000, "txFrequency": 444000000, "rxTuning": 0, "txTuning": 0, "rxFrequencyEffective": 449000000, "txFrequencyEffective": 444000000, "v24Connected": false, "protoVer": 3 } } ``` **Response Fields (Top Level):** - `status`: HTTP status code (always 200 for success) - `dmrEnabled`: DMR protocol enabled - `p25Enabled`: P25 protocol enabled - `nxdnEnabled`: NXDN protocol enabled - `state`: Current host state (0=IDLE, 1=LOCKOUT, 2=ERROR, 4=DMR, 5=P25, 6=NXDN) - `isTxCW`: Currently transmitting CW ID - `fixedMode`: Host is in fixed mode (true) or dynamic mode (false) - `dmrTSCCEnable`: DMR TSCC (Tier III Control Channel) data enabled - `dmrCC`: DMR control channel mode active - `p25CtrlEnable`: P25 control channel data enabled - `p25CC`: P25 control channel mode active - `nxdnCtrlEnable`: NXDN control channel data enabled - `nxdnCC`: NXDN control channel mode active - `tx`: Modem currently transmitting - `channelId`: Current RF channel ID - `channelNo`: Current RF channel number - `lastDstId`: Last destination ID (talkgroup) - `lastSrcId`: Last source ID (radio ID) - `peerId`: Peer ID from network configuration - `sysId`: System ID - `siteId`: Site ID - `p25RfssId`: P25 RFSS ID - `p25NetId`: P25 Network ID (WACN) - `p25NAC`: P25 Network Access Code **Voice Channels Array (`vcChannels[]`):** - `channelNo`: Voice channel number - `channelId`: Voice channel ID - `tx`: Channel currently transmitting - `lastDstId`: Last destination ID on this channel - `lastSrcId`: Last source ID on this channel **Modem Object (`modem`):** - `portType`: Port type (uart, tcp, udp, null) - `modemPort`: Serial port path - `portSpeed`: Serial port speed (baud rate) - `pttInvert`: PTT signal inverted (repeater only) - `rxInvert`: RX signal inverted (repeater only) - `txInvert`: TX signal inverted (repeater only) - `dcBlocker`: DC blocker enabled (repeater only) - `rxLevel`: Receive audio level (0.0-100.0) - `cwTxLevel`: CW ID transmit level (0.0-100.0) - `dmrTxLevel`: DMR transmit level (0.0-100.0) - `p25TxLevel`: P25 transmit level (0.0-100.0) - `nxdnTxLevel`: NXDN transmit level (0.0-100.0) - `rxDCOffset`: Receive DC offset adjustment - `txDCOffset`: Transmit DC offset adjustment - `dmrSymLevel3Adj`: DMR symbol level 3 adjustment (repeater only) - `dmrSymLevel1Adj`: DMR symbol level 1 adjustment (repeater only) - `p25SymLevel3Adj`: P25 symbol level 3 adjustment (repeater only) - `p25SymLevel1Adj`: P25 symbol level 1 adjustment (repeater only) - `nxdnSymLevel3Adj`: NXDN symbol level 3 adjustment (repeater only, protocol v3+) - `nxdnSymLevel1Adj`: NXDN symbol level 1 adjustment (repeater only, protocol v3+) - `dmrDiscBW`: DMR discriminator bandwidth adjustment (hotspot only) - `dmrPostBW`: DMR post-demod bandwidth adjustment (hotspot only) - `p25DiscBW`: P25 discriminator bandwidth adjustment (hotspot only) - `p25PostBW`: P25 post-demod bandwidth adjustment (hotspot only) - `nxdnDiscBW`: NXDN discriminator bandwidth adjustment (hotspot only, protocol v3+) - `nxdnPostBW`: NXDN post-demod bandwidth adjustment (hotspot only, protocol v3+) - `afcEnabled`: Automatic Frequency Control enabled (hotspot only, protocol v3+) - `afcKI`: AFC integral gain (hotspot only, protocol v3+) - `afcKP`: AFC proportional gain (hotspot only, protocol v3+) - `afcRange`: AFC range (hotspot only, protocol v3+) - `gainMode`: ADF7021 gain mode string (hotspot only) - `fdmaPreambles`: FDMA preamble count - `dmrRxDelay`: DMR receive delay - `p25CorrCount`: P25 correlation count - `rxFrequency`: Receive frequency (Hz) - `txFrequency`: Transmit frequency (Hz) - `rxTuning`: Receive tuning offset (Hz) - `txTuning`: Transmit tuning offset (Hz) - `rxFrequencyEffective`: Effective RX frequency (rxFrequency + rxTuning) - `txFrequencyEffective`: Effective TX frequency (txFrequency + txTuning) - `v24Connected`: V.24/RS-232 connected - `protoVer`: Modem protocol version **Notes:** - This endpoint provides comprehensive system status including modem parameters - Modem fields vary based on hardware type (hotspot vs repeater) and protocol version - Hotspot-specific fields only appear for hotspot hardware - Repeater-specific fields only appear for repeater hardware - Protocol v3+ fields only appear if modem firmware is version 3 or higher - Voice channels array populated when operating as control channel with voice channels configured --- ### 3.3 Endpoint: GET /voice-ch **Method:** `GET` **Description:** Retrieve configured voice channel information. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Response:** ```json { "status": 200, "channels": [ { "chNo": 1, "address": "192.168.1.100", "port": 54321 }, { "chNo": 2, "address": "192.168.1.101", "port": 54322 } ] } ``` **Response Fields:** - `chNo`: Channel number - `address`: Network address for voice channel - `port`: Network port for voice channel **Notes:** - Used in multi-site trunking configurations - Returns empty array if no voice channels configured - Voice channels are typically FNE peer connections --- ## 4. Modem Control ### 4.1 Endpoint: PUT /mdm/mode **Method:** `PUT` **Description:** Set the dvmhost operational mode. **Request Headers:** ``` X-DVM-Auth-Token: {token} Content-Type: application/json ``` **Request Body:** ```json { "mode": "idle" } ``` **Request Fields:** - `mode` (required, string): Operating mode to set **Supported Modes:** - `"idle"`: Dynamic mode (automatic protocol switching) - `"lockout"`: Lockout mode (no transmissions allowed) - `"dmr"`: Fixed DMR mode - `"p25"`: Fixed P25 mode - `"nxdn"`: Fixed NXDN mode **Response (Success):** ```json { "status": 200, "message": "Dynamic mode", "mode": 0 } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Mode description - `"Dynamic mode"` for `idle` and `lockout` modes - `"Fixed mode"` for `dmr`, `p25`, and `nxdn` modes - `mode`: Numeric mode value (0=IDLE, 1=LOCKOUT, 4=DMR, 5=P25, 6=NXDN) **Error Responses:** **Missing or Invalid Mode:** ```json { "status": 400, "message": "password was not a valid string" } ``` *Note: Implementation bug - error message incorrectly says "password" instead of "mode"* **Invalid Mode Value:** ```json { "status": 400, "message": "invalid mode" } ``` **Protocol Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` *Similar messages for P25 and NXDN when attempting to set disabled protocols* **Notes:** - `"idle"` mode enables dynamic protocol switching based on received data - Fixed modes (`dmr`, `p25`, `nxdn`) lock the modem to a single protocol - `"lockout"` mode prevents all RF transmissions - Attempting to set a fixed mode for a disabled protocol returns 503 Service Unavailable - Mode strings are case-insensitive - `idle` and `lockout` set `fixedMode` to false; protocol modes set it to true --- ### 4.2 Endpoint: PUT /mdm/kill **Method:** `PUT` **Description:** Request graceful shutdown of dvmhost. **Request Headers:** ``` X-DVM-Auth-Token: {token} Content-Type: application/json ``` **Request Body:** ```json { "force": false } ``` **Request Fields:** - `force` (required, boolean): Shutdown mode - `false`: Graceful shutdown (allows cleanup, wait for transmissions to complete) - `true`: Forced shutdown (immediate termination) **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **Missing or Invalid Force Parameter:** ```json { "status": 400, "message": "force was not a valid boolean" } ``` **Implementation Behavior:** - Sets global `g_killed` flag to `true` for graceful shutdown - If `force=true`, also sets `HOST_STATE_QUIT` for immediate termination - Both shutdown methods prevent new transmissions and stop RF processing - Graceful shutdown allows completion of in-progress operations - Forced shutdown terminates immediately without cleanup **Notes:** - *Implementation detail:* The function sets the success response before validating the `force` parameter, but validation still occurs and will return an error for invalid input - Graceful shutdown (`force=false`) is recommended for normal operations - Forced shutdown (`force=true`) should only be used when immediate termination is required - After successful shutdown request, the process will terminate and no further API calls will be possible - If the force parameter is not a valid boolean, a 400 error is returned despite the early success response initialization - Use with caution in production environments --- ## 5. Trunking and Supervisory Control ### 5.1 Endpoint: PUT /set-supervisor **Method:** `PUT` **Description:** Enable or disable supervisory (trunking) mode for the host. **Request Headers:** ``` X-DVM-Auth-Token: {token} Content-Type: application/json ``` **Request Body:** ```json { "state": 4, "enable": true } ``` **Request Fields:** - `state` (required, integer): Protocol state value - `4` = DMR - `5` = P25 - `6` = NXDN - `enable` (required, boolean): Enable (`true`) or disable (`false`) supervisory mode **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **Host Not Authoritative:** ```json { "status": 400, "message": "Host is not authoritative, cannot set supervisory state" } ``` **Invalid State Parameter:** ```json { "status": 400, "message": "state was not a valid integer" } ``` **Invalid Enable Parameter:** ```json { "status": 400, "message": "enable was not a boolean" } ``` **Invalid State Value:** ```json { "status": 400, "message": "invalid mode" } ``` **Protocol Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` *Similar messages for P25 and NXDN protocols* **Notes:** - Only available when host is configured as authoritative (`authoritative: true` in config) - Supervisory mode enables trunking control features for the specified protocol - The host must have the requested protocol enabled in its configuration - Each protocol (DMR, P25, NXDN) has independent supervisory mode settings --- ### 5.2 Endpoint: PUT /permit-tg **Method:** `PUT` **Description:** Permit traffic on a specific talkgroup. Used by non-authoritative hosts to allow group calls on specified talkgroups. **Request Headers:** ``` X-DVM-Auth-Token: {token} Content-Type: application/json ``` **Request Body (DMR):** ```json { "state": 4, "dstId": 1, "slot": 1 } ``` **Request Body (P25):** ```json { "state": 5, "dstId": 1, "dataPermit": false } ``` **Request Body (NXDN):** ```json { "state": 6, "dstId": 1 } ``` **Request Fields:** - `state` (required, integer): Protocol state value - `4` = DMR - `5` = P25 - `6` = NXDN - `dstId` (required, integer): Destination talkgroup ID to permit - `slot` (required for DMR, integer): TDMA slot number - `1` = Slot 1 - `2` = Slot 2 - `dataPermit` (optional for P25, boolean): Enable data permissions (default: `false`) **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **Host Is Authoritative:** ```json { "status": 400, "message": "Host is authoritative, cannot permit TG" } ``` **Invalid State Parameter:** ```json { "status": 400, "message": "state was not a valid integer" } ``` **Invalid Destination ID:** ```json { "status": 400, "message": "destination ID was not a valid integer" } ``` **Invalid Slot (DMR only):** ```json { "status": 400, "message": "slot was not a valid integer" } ``` **Illegal DMR Slot Value:** ```json { "status": 400, "message": "illegal DMR slot" } ``` *Returned when slot is 0 or greater than 2* **Invalid State Value:** ```json { "status": 400, "message": "invalid mode" } ``` **Protocol Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` *Similar messages for P25 and NXDN protocols* **Notes:** - Only available when host is configured as non-authoritative (`authoritative: false` in config) - Temporarily permits traffic on a talkgroup for the specified protocol - Used in trunking systems to allow group calls - DMR requires valid slot specification (1 or 2) - P25 supports optional `dataPermit` flag for data call permissions - NXDN only requires state and destination ID --- ### 5.3 Endpoint: PUT /grant-tg **Method:** `PUT` **Description:** Grant a voice channel for a specific talkgroup and source radio. Used by non-authoritative hosts or non-control-channel hosts to manually grant channel access. **Request Headers:** ``` X-DVM-Auth-Token: {token} Content-Type: application/json ``` **Request Body (DMR):** ```json { "state": 4, "dstId": 1, "srcId": 123456, "slot": 1, "unitToUnit": false } ``` **Request Body (P25):** ```json { "state": 5, "dstId": 1, "srcId": 123456, "unitToUnit": false } ``` **Request Body (NXDN):** ```json { "state": 6, "dstId": 1, "srcId": 123456, "unitToUnit": false } ``` **Request Fields:** - `state` (required, integer): Protocol state value - `4` = DMR - `5` = P25 - `6` = NXDN - `dstId` (required, integer): Destination talkgroup ID (must not be 0) - `srcId` (required, integer): Source radio ID requesting grant (must not be 0) - `slot` (required for DMR, integer): TDMA slot number (1 or 2) - `unitToUnit` (optional, boolean): Unit-to-unit call flag (default: `false`) - `false` = Group call (passed as `true` to grant function - inverted logic) - `true` = Unit-to-unit call (passed as `false` to grant function) **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **Host Is Authoritative Control Channel:** ```json { "status": 400, "message": "Host is authoritative, cannot grant TG" } ``` *Only returned when host is both authoritative AND configured as a control channel* **Invalid State Parameter:** ```json { "status": 400, "message": "state was not a valid integer" } ``` **Invalid Destination ID:** ```json { "status": 400, "message": "destination ID was not a valid integer" } ``` **Illegal Destination TGID:** ```json { "status": 400, "message": "destination ID is an illegal TGID" } ``` *Returned when `dstId` is 0* **Invalid Source ID:** ```json { "status": 400, "message": "source ID was not a valid integer" } ``` **Illegal Source ID:** ```json { "status": 400, "message": "soruce ID is an illegal TGID" } ``` *Note: Implementation typo - says "soruce" instead of "source". Returned when `srcId` is 0* **Invalid Slot (DMR only):** ```json { "status": 400, "message": "slot was not a valid integer" } ``` **Illegal DMR Slot Value:** ```json { "status": 400, "message": "illegal DMR slot" } ``` *Returned when slot is 0 or greater than 2* **Invalid State Value:** ```json { "status": 400, "message": "invalid mode" } ``` **Protocol Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` *Similar messages for P25 and NXDN protocols* **Notes:** - Available when host is non-authoritative OR when authoritative but not configured as a control channel - Used in trunked radio systems to manually assign voice channel grants - The `unitToUnit` parameter has inverted logic: the value is negated before being passed to the grant function - `unitToUnit: false` results in group call (`true` passed to grant function) - `unitToUnit: true` results in unit-to-unit call (`false` passed to grant function) - DMR requires valid slot specification (1 or 2) - Both `srcId` and `dstId` must be non-zero values - **Implementation Bug**: Error message for invalid source ID contains typo "soruce ID" instead of "source ID" --- ### 5.4 Endpoint: GET /release-grants **Method:** `GET` **Description:** Release all active voice channel grants across all enabled protocols. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Implementation Behavior:** - Calls `releaseGrant(0, true)` on affiliations for all enabled protocols (DMR, P25, NXDN) - Releases all grants by passing talkgroup ID `0` with `true` flag - Processes each protocol independently if enabled **Notes:** - Clears all active voice channel grants across the system - Forces radios to re-request channel grants if they wish to transmit - Useful for emergency channel clearing or system maintenance - Only affects protocols that are enabled in the host configuration - No error is returned if a protocol is not enabled; it is simply skipped --- ### 5.5 Endpoint: GET /release-affs **Method:** `GET` **Description:** Release all radio affiliations across all enabled protocols. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Implementation Behavior:** - Calls `clearGroupAff(0, true)` on affiliations for all enabled protocols (DMR, P25, NXDN) - Clears all group affiliations by passing talkgroup ID `0` with `true` flag - Processes each protocol independently if enabled **Notes:** - Clears all radio-to-talkgroup affiliations across the system - Forces radios to re-affiliate with their desired talkgroups - Used for system maintenance, troubleshooting, or forcing re-registration - Only affects protocols that are enabled in the host configuration - No error is returned if a protocol is not enabled; it is simply skipped - Different from `/release-grants` which releases active transmissions, this releases standing affiliations --- ## 6. Radio ID Lookup ### 6.1 Endpoint: GET /rid-whitelist/{rid} **Method:** `GET` **Description:** Toggle the whitelist status for a radio ID. This endpoint enables/whitelists the specified radio ID in the system. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **URL Parameters:** - `rid` (required, numeric): Radio ID to whitelist/toggle **Example URL:** ``` GET /rid-whitelist/123456 ``` **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **Invalid Arguments:** ```json { "status": 400, "message": "invalid API call arguments" } ``` **RID Zero Not Allowed:** ```json { "status": 400, "message": "tried to whitelist RID 0" } ``` **Implementation Behavior:** - Calls `m_ridLookup->toggleEntry(srcId, true)` to enable/whitelist the radio ID - RID value is extracted from URL path parameter - RID 0 is explicitly rejected as invalid **Notes:** - This is a **toggle/enable** operation, not a query operation - The endpoint name suggests "GET" but it actually modifies state by whitelisting the RID - Use this to authorize a specific radio ID to access the system - Does not return the current whitelist status; only confirms the operation succeeded - RID must be non-zero (RID 0 is reserved and cannot be whitelisted) --- ### 6.2 Endpoint: GET /rid-blacklist/{rid} **Method:** `GET` **Description:** Toggle the blacklist status for a radio ID. This endpoint disables/blacklists the specified radio ID in the system. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **URL Parameters:** - `rid` (required, numeric): Radio ID to blacklist/toggle **Example URL:** ``` GET /rid-blacklist/123456 ``` **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **Invalid Arguments:** ```json { "status": 400, "message": "invalid API call arguments" } ``` **RID Zero Not Allowed:** ```json { "status": 400, "message": "tried to blacklist RID 0" } ``` **Implementation Behavior:** - Calls `m_ridLookup->toggleEntry(srcId, false)` to disable/blacklist the radio ID - RID value is extracted from URL path parameter - RID 0 is explicitly rejected as invalid **Notes:** - This is a **toggle/disable** operation, not a query operation - The endpoint name suggests "GET" but it actually modifies state by blacklisting the RID - Use this to deny a specific radio ID access to the system - Does not return the current blacklist status; only confirms the operation succeeded - RID must be non-zero (RID 0 is reserved and cannot be blacklisted) - Blacklisted radios are denied access to the system --- ## 7. DMR Protocol Endpoints ### 7.1 Endpoint: GET /dmr/beacon **Method:** `GET` **Description:** Fire a DMR beacon transmission. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **DMR Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` **Beacons Not Enabled:** ```json { "status": 503, "message": "DMR beacons are not enabled" } ``` **Implementation Behavior:** - Sets global flag `g_fireDMRBeacon = true` to trigger beacon transmission - Requires DMR mode enabled in configuration - Requires DMR beacons enabled (`dmr.beacons.enable: true` in config) **Notes:** - Triggers immediate DMR beacon transmission on next opportunity - Beacons must be enabled in host configuration - Used for system identification, timing synchronization, and testing - Returns success immediately; beacon fires asynchronously --- ### 7.2 Endpoint: GET /dmr/debug/{debug}/{verbose} **Method:** `GET` **Description:** Get or set DMR debug logging state. Functions as both query and setter based on URL parameters. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Query Mode (No URL Parameters):** **Example URL:** ``` GET /dmr/debug ``` **Response (Query):** ```json { "status": 200, "debug": true, "verbose": false } ``` **Response Fields:** - `status`: HTTP status code (200) - `debug` (boolean): Current debug logging state - `verbose` (boolean): Current verbose logging state **Set Mode (With URL Parameters):** **URL Parameters:** - `debug` (required, numeric): Enable debug logging (`0` = disabled, `1` = enabled) - `verbose` (required, numeric): Enable verbose logging (`0` = disabled, `1` = enabled) **Example URL:** ``` GET /dmr/debug/1/1 ``` **Response (Set):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **DMR Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` **Implementation Behavior:** - If `match.size() <= 1`: Query mode - returns current debug/verbose states - If `match.size() == 3`: Set mode - updates debug and verbose flags - Parameters extracted from URL path using regex: `/dmr/debug/(\\d+)/(\\d+)` - Values converted: `1` → `true`, anything else → `false` - Calls `m_dmr->setDebugVerbose(debug, verbose)` to apply changes **Notes:** - Dual-purpose endpoint: query without parameters, set with parameters - `debug` enables standard debug logging for DMR operations - `verbose` enables very detailed logging (can be overwhelming) - Changes apply immediately without restart - Both parameters must be provided together in set mode --- ### 7.3 Endpoint: GET /dmr/dump-csbk/{enable} **Method:** `GET` **Description:** Get or set DMR CSBK (Control Signaling Block) packet dumping. Functions as both query and setter based on URL parameters. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Query Mode (No URL Parameter):** **Example URL:** ``` GET /dmr/dump-csbk ``` **Response (Query):** ```json { "status": 200, "verbose": true } ``` **Response Fields:** - `status`: HTTP status code (200) - `verbose` (boolean): Current CSBK dump state **Set Mode (With URL Parameter):** **URL Parameters:** - `enable` (required, numeric): Enable CSBK dumping (`0` = disabled, `1` = enabled) **Example URL:** ``` GET /dmr/dump-csbk/1 ``` **Response (Set):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **DMR Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` **Implementation Behavior:** - If `match.size() <= 1`: Query mode - returns current CSBK verbose state - If `match.size() == 2`: Set mode - updates CSBK verbose flag - Parameter extracted from URL path using regex: `/dmr/dump-csbk/(\\d+)` - Value converted: `1` → `true`, anything else → `false` - Calls `m_dmr->setCSBKVerbose(enable)` to apply changes **Notes:** - Dual-purpose endpoint: query without parameter, set with parameter - Query mode returns current CSBK dump state - Set mode enables/disables CSBK packet logging to console - CSBK packets contain control channel signaling information - Useful for troubleshooting trunking and control channel issues - Changes apply immediately without restart --- ### 7.4 Endpoint: PUT /dmr/rid **Method:** `PUT` **Description:** Execute DMR-specific radio ID operations (page, check, inhibit, uninhibit). **Request Headers:** ``` X-DVM-Auth-Token: {token} Content-Type: application/json ``` **Request Body:** ```json { "command": "check", "dstId": 123456, "slot": 1 } ``` **Request Fields:** - `command` (required, string): Command to execute - `"page"`: Radio Page (Call Alert) - `"check"`: Radio Check - `"inhibit"`: Radio Inhibit (disable radio) - `"uninhibit"`: Radio Un-inhibit (enable radio) - `dstId` (required, integer): Target radio ID (must not be 0) - `slot` (required, integer): TDMA slot number (1 or 2) **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **DMR Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` **Invalid Command:** ```json { "status": 400, "message": "command was not valid" } ``` **Invalid Destination ID Type:** ```json { "status": 400, "message": "destination ID was not valid" } ``` **Zero Destination ID:** ```json { "status": 400, "message": "destination ID was not valid" } ``` **Invalid Slot:** ```json { "status": 400, "message": "slot was not valid" } ``` **Invalid Slot Number:** ```json { "status": 400, "message": "invalid DMR slot number (slot == 0 or slot > 3)" } ``` **Unknown Command:** ```json { "status": 400, "message": "invalid command" } ``` **Implementation Behavior:** - Commands are case-insensitive (converted to lowercase) - Validates slot is not 0 and is less than 3 (must be 1 or 2) - `"page"`: Calls `writeRF_Call_Alrt(slot, WUID_ALL, dstId)` - sends call alert - `"check"`: Calls `writeRF_Ext_Func(slot, CHECK, WUID_ALL, dstId)` - sends radio check request - `"inhibit"`: Calls `writeRF_Ext_Func(slot, INHIBIT, WUID_STUNI, dstId)` - uses STUN Individual addressing - `"uninhibit"`: Calls `writeRF_Ext_Func(slot, UNINHIBIT, WUID_STUNI, dstId)` - removes inhibit state **Notes:** - Commands sent over DMR control channel to target radio - Slot must be 1 or 2 (DMR TDMA slots) - Slot validation correctly rejects 0 and values >= 3 - Target radio must be registered on the system - `inhibit`/`uninhibit` use STUNI (stun individual) addressing mode - `page` and `check` use WUID_ALL (all call) addressing mode - Commands execute immediately and return success before RF transmission completes --- ### 7.5 Endpoint: GET /dmr/cc-enable **Method:** `GET` **Description:** Toggle DMR control channel (CC) dedicated mode enable state. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "DMR CC is enabled" } ``` *or* ```json { "status": 200, "message": "DMR CC is disabled" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Current state after toggle **Error Responses:** **DMR Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` **TSCC Data Not Enabled:** ```json { "status": 400, "message": "DMR control data is not enabled!" } ``` **P25 Enabled (Conflict):** ```json { "status": 400, "message": "Can't enable DMR control channel while P25 is enabled!" } ``` **NXDN Enabled (Conflict):** ```json { "status": 400, "message": "Can't enable DMR control channel while NXDN is enabled!" } ``` **Implementation Behavior:** - Toggles `m_host->m_dmrCtrlChannel` flag (true ↔ false) - Requires `m_host->m_dmrTSCCData` to be enabled (TSCC control data) - Prevents enabling if P25 or NXDN protocols are active - Returns current state after toggle in message - Response message correctly reflects DMR CC state **Notes:** - This is a **toggle** operation, not a query (repeatedly calling switches state) - Toggles DMR dedicated control channel on/off - Cannot enable DMR CC while P25 or NXDN is enabled (protocols are mutually exclusive for CC) - Requires TSCC (Two-Slot Control Channel) data configuration in host config - Control channel handles trunking signaling and system management --- ### 7.6 Endpoint: GET /dmr/cc-broadcast **Method:** `GET` **Description:** Toggle DMR control channel broadcast mode (TSCC data transmission). **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "DMR CC broadcast is enabled" } ``` *or* ```json { "status": 200, "message": "DMR CC broadcast is disabled" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Current state after toggle **Error Responses:** **DMR Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` **Implementation Behavior:** - Toggles `m_host->m_dmrTSCCData` flag (true ↔ false) - Controls whether TSCC (Two-Slot Control Channel) data is broadcast - Returns current state after toggle in message **Notes:** - This is a **toggle** operation, not a query (repeatedly calling switches state) - Toggles broadcast mode for DMR control channel data - Affects how TSCC (Two-Slot Control Channel) data beacons are transmitted - TSCC data includes system information, adjacent sites, and trunking parameters - Can be toggled independently of the dedicated control channel enable state --- ### 7.7 Endpoint: GET /dmr/report-affiliations **Method:** `GET` **Description:** Get current DMR radio group affiliations. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "affiliations": [ { "srcId": 123456, "grpId": 1 }, { "srcId": 234567, "grpId": 2 } ] } ``` **Response Fields:** - `status`: HTTP status code (200) - `affiliations` (array): List of current affiliations - `srcId` (integer): Radio ID (subscriber unit) - `grpId` (integer): Talkgroup ID the radio is affiliated to **Implementation Behavior:** - Retrieves affiliation table from `m_dmr->affiliations()->grpAffTable()` - Returns `std::unordered_map` as JSON array - Map key is `srcId` (radio ID), value is `grpId` (talkgroup ID) - Returns empty array if no affiliations exist **Notes:** - Returns all current DMR group affiliations in the system - Useful for monitoring which radios are affiliated to which talkgroups - Does not include slot information (unlike what previous documentation suggested) - Affiliations persist until radio de-affiliates or system timeout - Empty affiliations array returned if no radios are currently affiliated --- ## 8. P25 Protocol Endpoints ### 8.1 Endpoint: GET /p25/cc **Method:** `GET` **Description:** Fire a P25 control channel transmission. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **P25 Not Enabled:** ```json { "status": 503, "message": "P25 mode is not enabled" } ``` **P25 Control Data Not Enabled:** ```json { "status": 503, "message": "P25 control data is not enabled" } ``` **Implementation Behavior:** - Sets global flag `g_fireP25Control = true` to trigger control channel transmission - Requires P25 mode enabled in configuration - Requires P25 control data enabled (`p25.control.enable: true` in config) **Notes:** - Triggers immediate P25 control channel burst on next opportunity - Requires P25 control channel configuration - Used for testing, system identification, and control channel synchronization - Returns success immediately; control burst fires asynchronously --- ### 8.2 Endpoint: GET /p25/debug/{debug}/{verbose} **Method:** `GET` **Description:** Get or set P25 debug logging state. Functions as both query and setter based on URL parameters. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Query Mode (No URL Parameters):** **Example URL:** ``` GET /p25/debug ``` **Response (Query):** ```json { "status": 200, "debug": true, "verbose": false } ``` **Response Fields:** - `status`: HTTP status code (200) - `debug` (boolean): Current debug logging state - `verbose` (boolean): Current verbose logging state **Set Mode (With URL Parameters):** **URL Parameters:** - `debug` (required, numeric): Enable debug logging (`0` = disabled, `1` = enabled) - `verbose` (required, numeric): Enable verbose logging (`0` = disabled, `1` = enabled) **Example URL:** ``` GET /p25/debug/1/0 ``` **Response (Set):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **P25 Not Enabled:** ```json { "status": 503, "message": "P25 mode is not enabled" } ``` **Implementation Behavior:** - If `match.size() <= 1`: Query mode - returns current debug/verbose states - If `match.size() == 3`: Set mode - updates debug and verbose flags - Parameters extracted from URL path using regex: `/p25/debug/(\\d+)/(\\d+)` - Values converted: `1` → `true`, anything else → `false` - Calls `m_p25->setDebugVerbose(debug, verbose)` to apply changes - Correctly checks `if (m_p25 != nullptr)` before accessing P25 object **Notes:** - Dual-purpose endpoint: query without parameters, set with parameters - Same behavior pattern as DMR debug endpoint - `debug` enables standard debug logging for P25 operations - `verbose` enables very detailed logging (can be overwhelming) - Changes apply immediately without restart - Both parameters must be provided together in set mode --- ### 8.3 Endpoint: GET /p25/dump-tsbk/{enable} **Method:** `GET` **Description:** Get or set P25 TSBK (Trunking Signaling Block) packet dumping. Functions as both query and setter based on URL parameters. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Query Mode (No URL Parameter):** **Example URL:** ``` GET /p25/dump-tsbk ``` **Response (Query):** ```json { "status": 200, "verbose": true } ``` **Response Fields:** - `status`: HTTP status code (200) - `verbose` (boolean): Current TSBK dump state **Set Mode (With URL Parameter):** **URL Parameters:** - `enable` (required, numeric): Enable TSBK dumping (`0` = disabled, `1` = enabled) **Example URL:** ``` GET /p25/dump-tsbk/1 ``` **Response (Set):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **P25 Not Enabled:** ```json { "status": 503, "message": "P25 mode is not enabled" } ``` **Implementation Behavior:** - If `match.size() <= 1`: Query mode - returns current TSBK verbose state - If `match.size() == 2`: Set mode - updates TSBK verbose flag - Parameter extracted from URL path using regex: `/p25/dump-tsbk/(\\d+)` - Value converted: `1` → `true`, anything else → `false` - Calls `m_p25->control()->setTSBKVerbose(enable)` to apply changes **Notes:** - Dual-purpose endpoint: query without parameter, set with parameter - Query mode returns current TSBK dump state - Set mode enables/disables TSBK packet logging to console - TSBK packets contain P25 trunking signaling information - Useful for troubleshooting P25 trunking and control channel issues - Changes apply immediately without restart --- ### 8.4 Endpoint: PUT /p25/rid **Method:** `PUT` **Description:** Execute P25-specific radio ID operations including paging, radio checks, inhibit/uninhibit, dynamic regrouping, and emergency alarms. **Request Headers:** ``` X-DVM-Auth-Token: {token} Content-Type: application/json ``` **Request Body (Basic Commands):** ```json { "command": "check", "dstId": 123456 } ``` **Request Body (Set MFID):** ```json { "command": "p25-setmfid", "mfId": 144 } ``` **Request Body (Dynamic Regroup):** ```json { "command": "dyn-regrp", "dstId": 123456, "tgId": 5000 } ``` **Request Body (Emergency Alarm):** ```json { "command": "emerg", "dstId": 5000, "srcId": 123456 } ``` **Supported Commands:** - `"p25-setmfid"`: Set manufacturer ID (no dstId required) - `"page"`: Send radio page (call alert) - `"check"`: Radio check request - `"inhibit"`: Radio inhibit (disable radio) - `"uninhibit"`: Radio un-inhibit (enable radio) - `"dyn-regrp"`: Dynamic regroup request - `"dyn-regrp-cancel"`: Cancel dynamic regroup - `"dyn-regrp-lock"`: Lock dynamic regroup - `"dyn-regrp-unlock"`: Unlock dynamic regroup - `"group-aff-req"`: Group affiliation query (GAQ) - `"unit-reg"`: Unit registration command (U_REG) - `"emerg"`: Emergency alarm **Request Fields:** - `command` (required, string): Command to execute (see above) - `dstId` (required for most commands, integer): Target radio ID (must not be 0) - **Not required for**: `"p25-setmfid"` - `mfId` (required for `p25-setmfid`, integer): Manufacturer ID (uint8_t) - `tgId` (required for `dyn-regrp`, integer): Target talkgroup ID - `srcId` (required for `emerg`, integer): Source radio ID (must not be 0) **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **P25 Not Enabled:** ```json { "status": 503, "message": "P25 mode is not enabled" } ``` **Invalid Command:** ```json { "status": 400, "message": "command was not valid" } ``` **Invalid Destination ID:** ```json { "status": 400, "message": "destination ID was not valid" } ``` **Invalid MFID:** ```json { "status": 400, "message": "MFID was not valid" } ``` **Invalid Talkgroup ID:** ```json { "status": 400, "message": "talkgroup ID was not valid" } ``` **Invalid Source ID:** ```json { "status": 400, "message": "source ID was not valid" } ``` **Unknown Command:** ```json { "status": 400, "message": "invalid command" } ``` **Implementation Behavior:** - Commands are case-insensitive (converted to lowercase) - Most commands use WUID_FNE (FNE unit ID) addressing - Command implementations: * `"p25-setmfid"`: Calls `control()->setLastMFId(mfId)` - no RF transmission * `"page"`: Calls `writeRF_TSDU_Call_Alrt(WUID_FNE, dstId)` * `"check"`: Calls `writeRF_TSDU_Ext_Func(CHECK, WUID_FNE, dstId)` * `"inhibit"`: Calls `writeRF_TSDU_Ext_Func(INHIBIT, WUID_FNE, dstId)` * `"uninhibit"`: Calls `writeRF_TSDU_Ext_Func(UNINHIBIT, WUID_FNE, dstId)` * `"dyn-regrp"`: Calls `writeRF_TSDU_Ext_Func(DYN_REGRP_REQ, tgId, dstId)` * `"dyn-regrp-cancel"`: Calls `writeRF_TSDU_Ext_Func(DYN_REGRP_CANCEL, 0, dstId)` * `"dyn-regrp-lock"`: Calls `writeRF_TSDU_Ext_Func(DYN_REGRP_LOCK, 0, dstId)` * `"dyn-regrp-unlock"`: Calls `writeRF_TSDU_Ext_Func(DYN_REGRP_UNLOCK, 0, dstId)` * `"group-aff-req"`: Calls `writeRF_TSDU_Grp_Aff_Q(dstId)` - GAQ message * `"unit-reg"`: Calls `writeRF_TSDU_U_Reg_Cmd(dstId)` - U_REG message * `"emerg"`: Calls `writeRF_TSDU_Emerg_Alrm(srcId, dstId)` **Notes:** - Commands sent via P25 TSBK (Trunking Signaling Block) messages - Target radio must be registered on the system - Dynamic regroup allows temporary talkgroup assignments for incident response - Manufacturer ID (MFID) affects radio behavior and feature availability - Emergency alarm sends alarm from srcId to dstId (dstId is typically a talkgroup) - Commands execute immediately and return success before RF transmission completes - WUID_FNE is the Fixed Network Equipment unit ID used for system commands --- ### 8.5 Endpoint: GET /p25/cc-enable **Method:** `GET` **Description:** Toggle P25 control channel (CC) dedicated mode enable state. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "P25 CC is enabled" } ``` *or* ```json { "status": 200, "message": "P25 CC is disabled" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Current state after toggle **Error Responses:** **P25 Not Enabled:** ```json { "status": 503, "message": "P25 mode is not enabled" } ``` **P25 Control Data Not Enabled:** ```json { "status": 400, "message": "P25 control data is not enabled!" } ``` **DMR Enabled (Conflict):** ```json { "status": 400, "message": "Can't enable P25 control channel while DMR is enabled!" } ``` **NXDN Enabled (Conflict):** ```json { "status": 400, "message": "Can't enable P25 control channel while NXDN is enabled!" } ``` **Implementation Behavior:** - Toggles `m_host->m_p25CtrlChannel` flag (true ↔ false) - Sets `m_host->m_p25CtrlBroadcast = true` when enabling - Sets `g_fireP25Control = true` to trigger control burst - Calls `m_p25->setCCHalted(false)` to resume control channel - Requires `m_host->m_p25CCData` to be enabled - Prevents enabling if DMR or NXDN protocols are active - Returns current state after toggle in message **Notes:** - This is a **toggle** operation, not a query (repeatedly calling switches state) - Toggles P25 dedicated control channel on/off - Cannot enable P25 CC while DMR or NXDN is enabled (protocols are mutually exclusive for CC) - Requires P25 control channel data configuration in host config - Control channel handles trunking signaling and system management - Automatically enables broadcast mode when enabling control channel --- ### 8.6 Endpoint: GET /p25/cc-broadcast **Method:** `GET` **Description:** Toggle P25 control channel broadcast mode. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "P25 CC broadcast is enabled" } ``` *or* ```json { "status": 200, "message": "P25 CC broadcast is disabled" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Current state after toggle **Error Responses:** **P25 Not Enabled:** ```json { "status": 503, "message": "P25 mode is not enabled" } ``` **P25 Control Data Not Enabled:** ```json { "status": 400, "message": "P25 control data is not enabled!" } ``` **Implementation Behavior:** - Toggles `m_host->m_p25CtrlBroadcast` flag (true ↔ false) - If disabling broadcast: * Sets `g_fireP25Control = false` to stop control bursts * Calls `m_p25->setCCHalted(true)` to halt control channel - If enabling broadcast: * Sets `g_fireP25Control = true` to start control bursts * Calls `m_p25->setCCHalted(false)` to resume control channel - Returns current state after toggle in message **Notes:** - This is a **toggle** operation, not a query (repeatedly calling switches state) - Toggles broadcast mode for P25 control channel - Affects P25 control channel beacon transmission - Requires P25 control data enabled in configuration - Can be toggled independently but requires CC data configuration - Halting broadcast stops control channel transmissions while keeping CC enabled --- ### 8.7 Endpoint: PUT /p25/raw-tsbk **Method:** `PUT` **Description:** Transmit a raw P25 TSBK (Trunking Signaling Block) packet. Allows sending custom TSBK messages for advanced control. **Request Headers:** ``` X-DVM-Auth-Token: {token} Content-Type: application/json ``` **Request Body:** ```json { "tsbk": "00112233445566778899AABB" } ``` **Request Fields:** - `tsbk` (required, string): Raw TSBK data as hexadecimal string (must be exactly 24 characters / 12 bytes) **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **P25 Not Enabled:** ```json { "status": 503, "message": "P25 mode is not enabled" } ``` **Invalid TSBK Field:** ```json { "status": 400, "message": "tsbk was not valid" } ``` **Invalid TSBK Length:** ```json { "status": 400, "message": "TSBK must be 24 characters in length" } ``` **Invalid TSBK Characters:** ```json { "status": 400, "message": "TSBK contains invalid characters" } ``` **Implementation Behavior:** - Validates TSBK string is exactly 24 hex characters (12 bytes) - Validates all characters are hexadecimal (0-9, a-f, A-F) - Converts hex string to byte array (P25_TSBK_LENGTH_BYTES = 12) - Calls `m_p25->control()->writeRF_TSDU_Raw(tsbk)` to transmit - If debug enabled, dumps raw TSBK bytes to log **Notes:** - **Advanced feature**: Requires knowledge of P25 TSBK packet structure - TSBK must be properly formatted according to P25 specification - No validation of TSBK content (opcode, manufacturer ID, etc.) - Used for testing, custom signaling, or implementing unsupported TSBK types - Transmitted directly without additional processing - Incorrect TSBK data may cause radio misbehavior or system issues - Advanced feature for custom P25 control messaging - TSBK data must be valid hexadecimal - Use with caution - invalid TSBK can disrupt system --- ### 8.8 Endpoint: GET /p25/report-affiliations **Method:** `GET` **Description:** Retrieve current P25 radio affiliations (which radios are affiliated to which talkgroups). **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Response (Success):** ```json { "status": 200, "message": "OK", "affiliations": [ { "srcId": 123456, "grpId": 1 }, { "srcId": 234567, "grpId": 2 } ] } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` - `affiliations`: Array of affiliation objects - `srcId` (uint32): Radio ID (subscriber unit) - `grpId` (uint32): Talkgroup ID the radio is affiliated to **Empty Affiliations Response:** ```json { "status": 200, "message": "OK", "affiliations": [] } ``` **Implementation Behavior:** - Retrieves affiliation table from `m_p25->affiliations()->grpAffTable()` - Returns map of srcId → grpId associations - Empty array if no affiliations exist - No pagination (returns all affiliations) **Notes:** - Shows current state of P25 group affiliations - P25 does not use TDMA slots (unlike DMR) - Radios must affiliate before they can participate in talkgroups - Affiliations can change dynamically as radios join/leave talkgroups - Used for monitoring system state and troubleshooting - Similar to DMR affiliations but without slot information --- ## 9. NXDN Protocol Endpoints ### 9.1 Endpoint: GET /nxdn/cc **Method:** `GET` **Description:** Fire an NXDN control channel transmission. Triggers an immediate control channel burst on the configured control channel. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **NXDN Not Enabled:** ```json { "status": 503, "message": "NXDN mode is not enabled" } ``` **NXDN Control Data Not Configured:** ```json { "status": 503, "message": "NXDN control data is not enabled" } ``` **Implementation Behavior:** - Checks if `m_nxdn != nullptr` (NXDN mode enabled) - Checks if `m_host->m_nxdnCCData` (control data configured) - Sets `g_fireNXDNControl = true` to trigger control channel transmission - Control channel burst fires on next opportunity **Notes:** - Triggers immediate NXDN control channel burst - Requires NXDN mode enabled in configuration - Requires NXDN control channel data configured - Used for manual control channel testing or forcing system announcements - Control burst contains site parameters, adjacent site information, etc. --- ### 9.2 Endpoint: GET /nxdn/debug/{debug}/{verbose} **Method:** `GET` **Description:** Get or set NXDN debug logging state. Functions as both query and setter based on URL parameters. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Query Mode (No URL Parameters):** **Example URL:** ``` GET /nxdn/debug ``` **Response (Query):** ```json { "status": 200, "debug": true, "verbose": false } ``` **Response Fields:** - `status`: HTTP status code (200) - `debug` (boolean): Current debug logging state - `verbose` (boolean): Current verbose logging state **Set Mode (With URL Parameters):** **URL Parameters:** - `debug` (required, numeric): Enable debug logging (`0` = disabled, `1` = enabled) - `verbose` (required, numeric): Enable verbose logging (`0` = disabled, `1` = enabled) **Example URL:** ``` GET /nxdn/debug/1/0 ``` **Response (Set):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **NXDN Not Enabled:** ```json { "status": 503, "message": "NXDN mode is not enabled" } ``` **Implementation Behavior:** - If `match.size() <= 1`: Query mode - returns current debug/verbose states - If `match.size() == 3`: Set mode - updates debug and verbose flags - Parameters extracted from URL path using regex: `/nxdn/debug/(\\d+)/(\\d+)` - Values converted: `1` → `true`, anything else → `false` - Calls `m_nxdn->setDebugVerbose(debug, verbose)` to apply changes - Correctly checks `if (m_nxdn != nullptr)` before accessing NXDN object **Notes:** - Dual-purpose endpoint: query without parameters, set with parameters - Same behavior pattern as DMR/P25 debug endpoints - `debug` enables standard debug logging for NXDN operations - `verbose` enables very detailed logging (can be overwhelming) - Changes apply immediately without restart - Both parameters must be provided together in set mode --- ### 9.3 Endpoint: GET /nxdn/dump-rcch/{enable} **Method:** `GET` **Description:** Get or set NXDN RCCH (Radio Control Channel) packet dumping. Functions as both query and setter based on URL parameters. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Query Mode (No URL Parameters):** **Example URL:** ``` GET /nxdn/dump-rcch ``` **Response (Query):** ```json { "status": 200, "verbose": true } ``` **Response Fields:** - `status`: HTTP status code (200) - `verbose` (boolean): Current RCCH verbose dump state **Set Mode (With URL Parameter):** **URL Parameter:** - `enable` (required, numeric): Enable RCCH dumping (`0` = disabled, `1` = enabled) **Example URL:** ``` GET /nxdn/dump-rcch/1 ``` **Response (Set):** ```json { "status": 200, "message": "OK" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` **Error Responses:** **NXDN Not Enabled:** ```json { "status": 503, "message": "NXDN mode is not enabled" } ``` **Implementation Behavior:** - If `match.size() <= 1`: Query mode - returns current RCCH verbose state - If `match.size() == 2`: Set mode - updates RCCH verbose flag - Parameter extracted from URL path using regex: `/nxdn/dump-rcch/(\\d+)` - Value converted: `1` → `true`, anything else → `false` - Calls `m_nxdn->setRCCHVerbose(enable)` to apply change - Correctly checks `if (m_nxdn != nullptr)` before accessing NXDN object **Notes:** - Dual-purpose endpoint: query without parameter, set with parameter - Similar pattern to DMR/P25 CSBK/TSBK dump endpoints - RCCH = Radio Control Channel (NXDN's control signaling) - Verbose mode dumps RCCH packets to log for debugging - Changes apply immediately without restart - Can generate significant log output when enabled --- ### 9.4 Endpoint: GET /nxdn/cc-enable **Method:** `GET` **Description:** Toggle NXDN control channel (CC) enable state. Switches between dedicated control channel enabled and disabled. **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "NXDN CC is enabled" } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Dynamic message indicating new CC state (`"NXDN CC is enabled"` or `"NXDN CC is disabled"`) **Error Responses:** **NXDN Not Enabled:** ```json { "status": 503, "message": "NXDN mode is not enabled" } ``` **NXDN Control Data Not Configured:** ```json { "status": 400, "message": "NXDN control data is not enabled!" } ``` **DMR Protocol Conflict:** ```json { "status": 400, "message": "Can't enable NXDN control channel while DMR is enabled!" } ``` **P25 Protocol Conflict:** ```json { "status": 400, "message": "Can't enable NXDN control channel while P25 is enabled!" } ``` **Implementation Behavior:** - Checks if `m_nxdn != nullptr` (NXDN mode enabled) - Checks if `m_host->m_nxdnCCData` (control data configured) - Checks for protocol conflicts with DMR (`m_dmr != nullptr`) - Checks for protocol conflicts with P25 (`m_p25 != nullptr`) - Toggles `m_host->m_nxdnCtrlChannel` flag (current state → opposite state) - Sets `m_host->m_nxdnCtrlBroadcast = true` (enables broadcast mode) - Sets `g_fireNXDNControl = true` (triggers control channel transmission) - Calls `m_nxdn->setCCHalted(false)` (ensures CC is not halted) **Notes:** - Toggles NXDN dedicated control channel on/off - Cannot enable NXDN CC while DMR or P25 is enabled (protocol conflict) - When enabling: Sets broadcast mode and fires control channel - When disabling: Turns off control channel operation - Automatically un-halts control channel when toggling - Used for switching between traffic and control channel modes - Similar behavior to P25 cc-enable but without broadcast toggle option --- ### 9.5 Endpoint: GET /nxdn/report-affiliations **Method:** `GET` **Description:** Retrieve current NXDN radio affiliations (which radios are affiliated to which talkgroups). **Request Headers:** ``` X-DVM-Auth-Token: {token} ``` **Request Body:** None **Response (Success):** ```json { "status": 200, "message": "OK", "affiliations": [ { "srcId": 123456, "grpId": 1 }, { "srcId": 234567, "grpId": 2 } ] } ``` **Response Fields:** - `status`: HTTP status code (200) - `message`: Always `"OK"` - `affiliations`: Array of affiliation objects - `srcId` (uint32): Radio ID (subscriber unit) - `grpId` (uint32): Talkgroup ID the radio is affiliated to **Empty Affiliations Response:** ```json { "status": 200, "message": "OK", "affiliations": [] } ``` **Implementation Behavior:** - Retrieves affiliation table from `m_nxdn->affiliations()->grpAffTable()` - Returns map of srcId → grpId associations - Empty array if no affiliations exist - No pagination (returns all affiliations) **Notes:** - Shows current state of NXDN group affiliations - NXDN does not use TDMA slots (like P25, unlike DMR) - Radios must affiliate before they can participate in talkgroups - Affiliations can change dynamically as radios join/leave talkgroups - Used for monitoring system state and troubleshooting - Similar to DMR/P25 affiliations but without slot information - NXDN uses FDMA (Frequency Division Multiple Access) --- ## 10. Response Formats ### 10.1 Standard Success Response All successful API calls return HTTP 200 with a JSON object containing at minimum: ```json { "status": 200, "message": "OK" } ``` Many endpoints omit the `message` field and only include `status`. Additional fields are added based on the endpoint's specific functionality. ### 10.2 Query Response Formats **Single Value Response:** ```json { "status": 200, "message": "OK", "value": true } ``` **Multiple Values Response:** ```json { "status": 200, "message": "OK", "debug": true, "verbose": false } ``` **Array Response:** ```json { "status": 200, "message": "OK", "affiliations": [ {"srcId": 123456, "grpId": 1, "slot": 1} ] } ``` **Complex Object Response:** ```json { "status": 200, "message": "OK", "state": 5, "dmrEnabled": true, "p25Enabled": true, "nxdnEnabled": false, "fixedMode": true } ``` ### 10.3 Standard Error Response Error responses include HTTP status code and JSON error object: ```json { "status": 400, "message": "descriptive error message" } ``` **HTTP Status Codes:** - `200 OK`: Request successful - `400 Bad Request`: Invalid request format or parameters - `401 Unauthorized`: Missing or invalid authentication token - `404 Not Found`: Endpoint does not exist - `405 Method Not Allowed`: Wrong HTTP method for endpoint - `500 Internal Server Error`: Server-side error - `503 Service Unavailable`: Requested service/protocol not enabled ### 10.4 Protocol-Specific Responses **DMR Responses:** - Include `slot` field (1 or 2) where applicable - TDMA slot-based operations - CSBK verbose state for dump endpoints **P25 Responses:** - No slot field (FDMA only) - TSBK verbose state for dump endpoints - Emergency flag for certain operations - Dynamic regrouping status **NXDN Responses:** - No slot field (FDMA only) - RCCH verbose state for dump endpoints - Simplified control channel management ### 10.5 Toggle Endpoint Response Pattern Toggle endpoints (cc-enable, cc-broadcast) return dynamic messages: ```json { "status": 200, "message": "DMR CC is enabled" } ``` or ```json { "status": 200, "message": "DMR CC is disabled" } ``` The message reflects the **new state** after toggling, not the previous state. --- ## 11. Error Handling ### 11.1 Authentication Errors **Missing Token:** ```json { "status": 401, "message": "no authentication token" } ``` **Invalid Token:** ```json { "status": 401, "message": "invalid authentication token" } ``` **Illegal Token:** ```json { "status": 401, "message": "illegal authentication token" } ``` **Authentication Failed (Wrong Password):** ```json { "status": 401, "message": "authentication failed" } ``` ### 11.2 Validation Errors **Invalid JSON:** ```json { "status": 400, "message": "JSON parse error: unexpected character" } ``` **Invalid Content-Type:** ```json { "status": 400, "message": "invalid content-type (must be application/json)" } ``` **Missing Required Field:** ```json { "status": 400, "message": "field 'dstId' is required" } ``` **Invalid Field Value:** ```json { "status": 400, "message": "dstId was not valid" } ``` **Invalid Command:** ```json { "status": 400, "message": "unknown command specified" } ``` **Invalid Hex String (P25 raw-tsbk):** ```json { "status": 400, "message": "TSBK must be 24 characters in length" } ``` or ```json { "status": 400, "message": "TSBK contains invalid characters" } ``` ### 11.3 Service Errors **Protocol Not Enabled:** ```json { "status": 503, "message": "DMR mode is not enabled" } ``` ```json { "status": 503, "message": "P25 mode is not enabled" } ``` ```json { "status": 503, "message": "NXDN mode is not enabled" } ``` **Feature Not Configured:** ```json { "status": 503, "message": "DMR beacons are not enabled" } ``` ```json { "status": 503, "message": "DMR control data is not enabled" } ``` ```json { "status": 503, "message": "P25 control data is not enabled" } ``` ```json { "status": 503, "message": "NXDN control data is not enabled" } ``` **Unauthorized Operation:** ```json { "status": 400, "message": "Host is not authoritative, cannot set supervisory state" } ``` **Protocol Conflicts:** ```json { "status": 400, "message": "Can't enable DMR control channel while P25 is enabled!" } ``` ```json { "status": 400, "message": "Can't enable P25 control channel while DMR is enabled!" } ``` ```json { "status": 400, "message": "Can't enable NXDN control channel while DMR is enabled!" } ``` ### 11.4 Parameter-Specific Errors **Invalid Slot (DMR):** ```json { "status": 400, "message": "slot is invalid, must be 1 or 2" } ``` **Invalid Source ID:** ```json { "status": 400, "message": "srcId was not valid" } ``` **Invalid Talkgroup ID:** ```json { "status": 400, "message": "tgId was not valid" } ``` **Invalid Voice Channel:** ```json { "status": 400, "message": "voiceChNo was not valid" } ``` **Invalid Mode:** ```json { "status": 400, "message": "mode is invalid" } ``` ### 11.5 Error Handling Best Practices 1. **Always check HTTP status code first** - 200 means success, anything else is an error 2. **Parse error messages** - The `message` field contains human-readable error details 3. **Handle 503 errors gracefully** - Service unavailable often means protocol not enabled in config 4. **Retry on 401** - May need to re-authenticate if token expired 5. **Log errors** - Keep error responses for debugging and audit trails 6. **Validate input before sending** - Many errors can be prevented with client-side validation 7. **Check protocol conflicts** - Only one protocol's control channel can be active at a time --- ## 12. Security Considerations ### 12.1 Password Security - **Never send plaintext passwords:** Always hash with SHA-256 before transmission - **Use HTTPS in production:** Prevents token interception - **Rotate passwords regularly:** Change dvmhost password periodically - **Strong passwords:** Use complex passwords (minimum 16 characters recommended) ### 12.2 Token Management - **Tokens are session-based:** Bound to client IP/hostname - **Token invalidation:** Tokens are invalidated on: - Re-authentication - Explicit invalidation - Server restart - **Token format:** 64-bit unsigned integer (not cryptographically secure by itself) ### 12.3 Network Security - **Use HTTPS:** Enable SSL/TLS for production deployments - **Firewall rules:** Restrict REST API access to trusted networks - **Rate limiting:** Consider implementing rate limiting for brute-force protection - **Audit logging:** Enable debug logging to track API access ### 12.4 Operational Security - **Mode changes:** Use caution when changing modes during active traffic - **Kill command:** Restricted to authorized administrators - **Supervisory mode:** Only enable on authoritative/master hosts - **Raw TSBK:** Advanced feature requiring protocol knowledge --- ## 13. Examples ### 13.1 Complete Authentication and Status Check ```bash #!/bin/bash # Configuration DVMHOST="dvmhost.example.com" PORT="9990" PASSWORD="your_password_here" # Step 1: Generate password hash echo "Generating password hash..." HASH=$(echo -n "$PASSWORD" | sha256sum | cut -d' ' -f1) # Step 2: Authenticate echo "Authenticating..." AUTH_RESPONSE=$(curl -s -X PUT "http://${DVMHOST}:${PORT}/auth" \ -H "Content-Type: application/json" \ -d "{\"auth\":\"$HASH\"}") TOKEN=$(echo "$AUTH_RESPONSE" | jq -r '.token') if [ "$TOKEN" == "null" ] || [ -z "$TOKEN" ]; then echo "Authentication failed!" echo "$AUTH_RESPONSE" exit 1 fi echo "Authenticated successfully. Token: $TOKEN" # Step 3: Get version echo -e "\nGetting version..." curl -s -X GET "http://${DVMHOST}:${PORT}/version" \ -H "X-DVM-Auth-Token: $TOKEN" | jq # Step 4: Get status echo -e "\nGetting status..." curl -s -X GET "http://${DVMHOST}:${PORT}/status" \ -H "X-DVM-Auth-Token: $TOKEN" | jq # Step 5: Get voice channels echo -e "\nGetting voice channels..." curl -s -X GET "http://${DVMHOST}:${PORT}/voice-ch" \ -H "X-DVM-Auth-Token: $TOKEN" | jq ``` ### 13.2 Switch to Fixed P25 Mode ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" # Set fixed P25 mode curl -X PUT "http://${DVMHOST}:${PORT}/mdm/mode" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "mode": "p25" }' | jq # Verify mode change curl -X GET "http://${DVMHOST}:${PORT}/status" \ -H "X-DVM-Auth-Token: $TOKEN" | jq '.state, .fixedMode' ``` ### 13.3 Enable DMR Debug Logging ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" # Enable DMR debug and verbose logging curl -X GET "http://${DVMHOST}:${PORT}/dmr/debug/1/1" \ -H "X-DVM-Auth-Token: $TOKEN" | jq # Query current debug state curl -X GET "http://${DVMHOST}:${PORT}/dmr/debug" \ -H "X-DVM-Auth-Token: $TOKEN" | jq ``` ### 13.4 Send DMR Radio Check ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" # Send radio check to radio 123456 on slot 1 curl -X PUT "http://${DVMHOST}:${PORT}/dmr/rid" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "command": "check", "dstId": 123456, "slot": 1 }' | jq ``` ### 13.5 Grant P25 Voice Channel ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" # Grant voice channel 1 for TG 100 to radio 123456 curl -X PUT "http://${DVMHOST}:${PORT}/grant-tg" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "state": 5, "dstId": 100, "srcId": 123456, "grp": true, "voiceChNo": 1, "emergency": false }' | jq ``` ### 13.6 P25 Dynamic Regroup ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" # Regroup radio 123456 to talkgroup 5000 curl -X PUT "http://${DVMHOST}:${PORT}/p25/rid" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "command": "dyn-regrp", "dstId": 123456, "tgId": 5000 }' | jq # Cancel the regroup curl -X PUT "http://${DVMHOST}:${PORT}/p25/rid" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "command": "dyn-regrp-cancel", "dstId": 123456 }' | jq ``` ### 13.7 Check Radio ID Authorization ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" RADIO_ID="123456" # Check if radio is whitelisted WHITELIST_RESPONSE=$(curl -s -X GET "http://${DVMHOST}:${PORT}/rid-whitelist/${RADIO_ID}" \ -H "X-DVM-Auth-Token: $TOKEN") IS_WHITELISTED=$(echo "$WHITELIST_RESPONSE" | jq -r '.whitelisted') # Check if radio is blacklisted BLACKLIST_RESPONSE=$(curl -s -X GET "http://${DVMHOST}:${PORT}/rid-blacklist/${RADIO_ID}" \ -H "X-DVM-Auth-Token: $TOKEN") IS_BLACKLISTED=$(echo "$BLACKLIST_RESPONSE" | jq -r '.blacklisted') echo "Radio $RADIO_ID:" echo " Whitelisted: $IS_WHITELISTED" echo " Blacklisted: $IS_BLACKLISTED" if [ "$IS_WHITELISTED" == "true" ] && [ "$IS_BLACKLISTED" == "false" ]; then echo " Status: AUTHORIZED" else echo " Status: NOT AUTHORIZED" fi ``` ### 13.8 Monitor Affiliations Across All Protocols ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" echo "Monitoring affiliations (Ctrl+C to stop)..." echo "================================================" while true; do clear echo "=== DMR Affiliations ===" curl -s -X GET "http://${DVMHOST}:${PORT}/dmr/report-affiliations" \ -H "X-DVM-Auth-Token: $TOKEN" | jq -r '.affiliations[] | "Radio \(.srcId) -> TG \(.grpId) (Slot \(.slot))"' echo -e "\n=== P25 Affiliations ===" curl -s -X GET "http://${DVMHOST}:${PORT}/p25/report-affiliations" \ -H "X-DVM-Auth-Token: $TOKEN" | jq -r '.affiliations[] | "Radio \(.srcId) -> TG \(.grpId)"' echo -e "\n=== NXDN Affiliations ===" curl -s -X GET "http://${DVMHOST}:${PORT}/nxdn/report-affiliations" \ -H "X-DVM-Auth-Token: $TOKEN" | jq -r '.affiliations[] | "Radio \(.srcId) -> TG \(.grpId)"' echo -e "\n[Updated: $(date '+%Y-%m-%d %H:%M:%S')]" sleep 5 done ``` ### 13.9 Toggle Control Channels ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" # Toggle DMR control channel echo "Toggling DMR CC..." curl -X GET "http://${DVMHOST}:${PORT}/dmr/cc-enable" \ -H "X-DVM-Auth-Token: $TOKEN" | jq # Toggle DMR broadcast mode echo -e "\nToggling DMR CC broadcast mode..." curl -X GET "http://${DVMHOST}:${PORT}/dmr/cc-broadcast" \ -H "X-DVM-Auth-Token: $TOKEN" | jq # Fire DMR control channel echo -e "\nFiring DMR control channel..." curl -X GET "http://${DVMHOST}:${PORT}/dmr/beacon" \ -H "X-DVM-Auth-Token: $TOKEN" | jq ``` ### 13.10 P25 Radio Operations ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" RADIO_ID="123456" # Page radio echo "Paging radio ${RADIO_ID}..." curl -X PUT "http://${DVMHOST}:${PORT}/p25/rid" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"command\": \"page\", \"dstId\": ${RADIO_ID} }" | jq # Radio check echo -e "\nSending radio check to ${RADIO_ID}..." curl -X PUT "http://${DVMHOST}:${PORT}/p25/rid" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"command\": \"check\", \"dstId\": ${RADIO_ID} }" | jq # Radio inhibit echo -e "\nInhibiting radio ${RADIO_ID}..." curl -X PUT "http://${DVMHOST}:${PORT}/p25/rid" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"command\": \"inhibit\", \"dstId\": ${RADIO_ID} }" | jq # Wait 5 seconds sleep 5 # Radio uninhibit echo -e "\nUninhibiting radio ${RADIO_ID}..." curl -X PUT "http://${DVMHOST}:${PORT}/p25/rid" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"command\": \"uninhibit\", \"dstId\": ${RADIO_ID} }" | jq ``` ### 13.11 DMR Radio Operations ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" RADIO_ID="123456" SLOT="1" # Radio check echo "Sending DMR radio check to ${RADIO_ID} on slot ${SLOT}..." curl -X PUT "http://${DVMHOST}:${PORT}/dmr/rid" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"command\": \"check\", \"dstId\": ${RADIO_ID}, \"slot\": ${SLOT} }" | jq # Radio inhibit echo -e "\nInhibiting DMR radio ${RADIO_ID} on slot ${SLOT}..." curl -X PUT "http://${DVMHOST}:${PORT}/dmr/rid" \ -H "X-DVM-Auth-Token: $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"command\": \"inhibit\", \"dstId\": ${RADIO_ID}, \"slot\": ${SLOT} }" | jq ``` ### 13.12 Protocol Debug Control ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" # Enable all protocol debugging echo "Enabling debug for all protocols..." curl -X GET "http://${DVMHOST}:${PORT}/dmr/debug/1/1" \ -H "X-DVM-Auth-Token: $TOKEN" | jq curl -X GET "http://${DVMHOST}:${PORT}/p25/debug/1/1" \ -H "X-DVM-Auth-Token: $TOKEN" | jq curl -X GET "http://${DVMHOST}:${PORT}/nxdn/debug/1/1" \ -H "X-DVM-Auth-Token: $TOKEN" | jq # Enable packet dumping echo -e "\nEnabling packet dumping..." curl -X GET "http://${DVMHOST}:${PORT}/dmr/dump-csbk/1" \ -H "X-DVM-Auth-Token: $TOKEN" | jq curl -X GET "http://${DVMHOST}:${PORT}/p25/dump-tsbk/1" \ -H "X-DVM-Auth-Token: $TOKEN" | jq curl -X GET "http://${DVMHOST}:${PORT}/nxdn/dump-rcch/1" \ -H "X-DVM-Auth-Token: $TOKEN" | jq echo -e "\nDebug and packet dumping enabled for all protocols." echo "Check dvmhost logs for detailed output." ``` ### 13.13 Release All Grants and Affiliations ```bash #!/bin/bash TOKEN="your_token_here" DVMHOST="dvmhost.example.com" PORT="9990" # Release all voice channel grants echo "Releasing all voice channel grants..." curl -X GET "http://${DVMHOST}:${PORT}/release-grants" \ -H "X-DVM-Auth-Token: $TOKEN" | jq # Release all affiliations echo -e "\nReleasing all affiliations..." curl -X GET "http://${DVMHOST}:${PORT}/release-affs" \ -H "X-DVM-Auth-Token: $TOKEN" | jq echo -e "\nAll grants and affiliations released." ``` ### 13.14 Python Client Library ```python #!/usr/bin/env python3 import requests import hashlib import json from typing import Optional, Dict, Any class DVMHostClient: def __init__(self, host: str, port: int, password: str, use_https: bool = False): self.base_url = f"{'https' if use_https else 'http'}://{host}:{port}" self.password = password self.token: Optional[str] = None def authenticate(self) -> bool: """Authenticate and get token""" password_hash = hashlib.sha256(self.password.encode()).hexdigest() response = requests.put( f"{self.base_url}/auth", json={"auth": password_hash} ) if response.status_code == 200: data = response.json() self.token = data.get('token') return True else: print(f"Authentication failed: {response.text}") return False def _headers(self) -> Dict[str, str]: """Get headers with auth token""" return { "X-DVM-Auth-Token": self.token, "Content-Type": "application/json" } def get_version(self) -> Dict[str, Any]: """Get dvmhost version""" response = requests.get( f"{self.base_url}/version", headers=self._headers() ) return response.json() def get_status(self) -> Dict[str, Any]: """Get dvmhost status""" response = requests.get( f"{self.base_url}/status", headers=self._headers() ) return response.json() def set_mode(self, mode: str) -> Dict[str, Any]: """Set modem mode""" response = requests.put( f"{self.base_url}/mdm/mode", headers=self._headers(), json={"mode": mode} ) return response.json() def dmr_radio_check(self, dst_id: int, slot: int) -> Dict[str, Any]: """Send DMR radio check""" response = requests.put( f"{self.base_url}/dmr/rid", headers=self._headers(), json={ "command": "check", "dstId": dst_id, "slot": slot } ) return response.json() def p25_radio_check(self, dst_id: int) -> Dict[str, Any]: """Send P25 radio check""" response = requests.put( f"{self.base_url}/p25/rid", headers=self._headers(), json={ "command": "check", "dstId": dst_id } ) return response.json() def get_dmr_affiliations(self) -> Dict[str, Any]: """Get DMR affiliations""" response = requests.get( f"{self.base_url}/dmr/report-affiliations", headers=self._headers() ) return response.json() def get_p25_affiliations(self) -> Dict[str, Any]: """Get P25 affiliations""" response = requests.get( f"{self.base_url}/p25/report-affiliations", headers=self._headers() ) return response.json() def get_nxdn_affiliations(self) -> Dict[str, Any]: """Get NXDN affiliations""" response = requests.get( f"{self.base_url}/nxdn/report-affiliations", headers=self._headers() ) return response.json() def set_dmr_debug(self, debug: bool, verbose: bool) -> Dict[str, Any]: """Set DMR debug state""" debug_val = 1 if debug else 0 verbose_val = 1 if verbose else 0 response = requests.get( f"{self.base_url}/dmr/debug/{debug_val}/{verbose_val}", headers=self._headers() ) return response.json() def set_p25_debug(self, debug: bool, verbose: bool) -> Dict[str, Any]: """Set P25 debug state""" debug_val = 1 if debug else 0 verbose_val = 1 if verbose else 0 response = requests.get( f"{self.base_url}/p25/debug/{debug_val}/{verbose_val}", headers=self._headers() ) return response.json() def set_nxdn_debug(self, debug: bool, verbose: bool) -> Dict[str, Any]: """Set NXDN debug state""" debug_val = 1 if debug else 0 verbose_val = 1 if verbose else 0 response = requests.get( f"{self.base_url}/nxdn/debug/{debug_val}/{verbose_val}", headers=self._headers() ) return response.json() def p25_dynamic_regroup(self, dst_id: int, tg_id: int) -> Dict[str, Any]: """P25 dynamic regroup radio to talkgroup""" response = requests.put( f"{self.base_url}/p25/rid", headers=self._headers(), json={ "command": "dyn-regrp", "dstId": dst_id, "tgId": tg_id } ) return response.json() def p25_dynamic_regroup_cancel(self, dst_id: int) -> Dict[str, Any]: """Cancel P25 dynamic regroup""" response = requests.put( f"{self.base_url}/p25/rid", headers=self._headers(), json={ "command": "dyn-regrp-cancel", "dstId": dst_id } ) return response.json() def dmr_radio_inhibit(self, dst_id: int, slot: int) -> Dict[str, Any]: """Inhibit DMR radio""" response = requests.put( f"{self.base_url}/dmr/rid", headers=self._headers(), json={ "command": "inhibit", "dstId": dst_id, "slot": slot } ) return response.json() def p25_radio_inhibit(self, dst_id: int) -> Dict[str, Any]: """Inhibit P25 radio""" response = requests.put( f"{self.base_url}/p25/rid", headers=self._headers(), json={ "command": "inhibit", "dstId": dst_id } ) return response.json() def release_all_grants(self) -> Dict[str, Any]: """Release all voice channel grants""" response = requests.get( f"{self.base_url}/release-grants", headers=self._headers() ) return response.json() def release_all_affiliations(self) -> Dict[str, Any]: """Release all affiliations""" response = requests.get( f"{self.base_url}/release-affs", headers=self._headers() ) return response.json() def check_rid_whitelist(self, rid: int) -> bool: """Check if RID is whitelisted""" response = requests.get( f"{self.base_url}/rid-whitelist/{rid}", headers=self._headers() ) return response.json().get('whitelisted', False) def check_rid_blacklist(self, rid: int) -> bool: """Check if RID is blacklisted""" response = requests.get( f"{self.base_url}/rid-blacklist/{rid}", headers=self._headers() ) return response.json().get('blacklisted', False) def grant_channel(self, state: int, dst_id: int, src_id: int, voice_ch_no: int, slot: int = 1, grp: bool = True, emergency: bool = False) -> Dict[str, Any]: """Grant voice channel""" data = { "state": state, "dstId": dst_id, "srcId": src_id, "grp": grp, "voiceChNo": voice_ch_no } if state == 4: # DMR data["slot"] = slot elif state == 5: # P25 data["emergency"] = emergency response = requests.put( f"{self.base_url}/grant-tg", headers=self._headers(), json=data ) return response.json() # Example usage if __name__ == "__main__": # Create client client = DVMHostClient("dvmhost.example.com", 9990, "your_password_here") # Authenticate if client.authenticate(): print("Authenticated successfully!") # Get version version = client.get_version() print(f"Version: {version['version']}") # Get status status = client.get_status() print(f"Current state: {status['state']}") print(f"DMR enabled: {status['dmrEnabled']}") print(f"P25 enabled: {status['p25Enabled']}") # Set fixed P25 mode result = client.set_mode("p25") print(f"Mode change: {result}") # Send P25 radio check result = client.p25_radio_check(123456) print(f"Radio check result: {result}") # Get P25 affiliations affs = client.get_p25_affiliations() print(f"P25 Affiliations: {json.dumps(affs, indent=2)}") else: print("Authentication failed!") ``` --- ## Appendix A: Endpoint Summary Table | Method | Endpoint | Description | Auth Required | Protocol | |--------|----------|-------------|---------------|----------| | PUT | /auth | Authenticate and get token | No | N/A | | GET | /version | Get version information | Yes | N/A | | GET | /status | Get host status | Yes | N/A | | GET | /voice-ch | Get voice channel states | Yes | N/A | | PUT | /mdm/mode | Set modem mode | Yes | N/A | | PUT | /mdm/kill | Shutdown host | Yes | N/A | | PUT | /set-supervisor | Set supervisory mode | Yes | N/A | | PUT | /permit-tg | Permit talkgroup | Yes | All | | PUT | /grant-tg | Grant voice channel | Yes | All | | GET | /release-grants | Release all grants | Yes | All | | GET | /release-affs | Release all affiliations | Yes | All | | GET | /rid-whitelist/{rid} | Check RID whitelist | Yes | N/A | | GET | /rid-blacklist/{rid} | Check RID blacklist | Yes | N/A | | GET | /dmr/beacon | Fire DMR beacon | Yes | DMR | | GET | /dmr/debug[/{debug}/{verbose}] | Get/Set DMR debug | Yes | DMR | | GET | /dmr/dump-csbk[/{enable}] | Get/Set CSBK dump | Yes | DMR | | PUT | /dmr/rid | DMR RID operations (7 commands) | Yes | DMR | | GET | /dmr/cc-enable | Toggle DMR CC | Yes | DMR | | GET | /dmr/cc-broadcast | Toggle DMR CC broadcast | Yes | DMR | | GET | /dmr/report-affiliations | Get DMR affiliations | Yes | DMR | | GET | /p25/cc | Fire P25 CC | Yes | P25 | | GET | /p25/debug[/{debug}/{verbose}] | Get/Set P25 debug | Yes | P25 | | GET | /p25/dump-tsbk[/{enable}] | Get/Set TSBK dump | Yes | P25 | | PUT | /p25/rid | P25 RID operations (12 commands) | Yes | P25 | | GET | /p25/cc-enable | Toggle P25 CC | Yes | P25 | | GET | /p25/cc-broadcast | Toggle P25 CC broadcast | Yes | P25 | | PUT | /p25/raw-tsbk | Send raw TSBK packet | Yes | P25 | | GET | /p25/report-affiliations | Get P25 affiliations | Yes | P25 | | GET | /nxdn/cc | Fire NXDN CC | Yes | NXDN | | GET | /nxdn/debug[/{debug}/{verbose}] | Get/Set NXDN debug | Yes | NXDN | | GET | /nxdn/dump-rcch[/{enable}] | Get/Set RCCH dump | Yes | NXDN | | GET | /nxdn/cc-enable | Toggle NXDN CC | Yes | NXDN | | GET | /nxdn/report-affiliations | Get NXDN affiliations | Yes | NXDN | ### Command Summaries **DMR /rid Commands (7 total):** - `page` - Page radio - `check` - Radio check - `inhibit` - Inhibit radio - `uninhibit` - Uninhibit radio - `dmr-setmfid` - Set manufacturer ID **P25 /rid Commands (12 total):** - `p25-setmfid` - Set manufacturer ID - `page` - Page radio - `check` - Radio check - `inhibit` - Inhibit radio - `uninhibit` - Uninhibit radio - `dyn-regrp` - Dynamic regroup - `dyn-regrp-cancel` - Cancel dynamic regroup - `dyn-regrp-lock` - Lock dynamic regroup - `dyn-regrp-unlock` - Unlock dynamic regroup - `group-aff-req` - Group affiliation request - `unit-reg` - Unit registration - `emerg` - Emergency acknowledgment --- ## Appendix B: Configuration File Reference ### REST API Configuration (YAML) ```yaml restApi: # Enable REST API enable: true # Bind address (0.0.0.0 = all interfaces) address: 0.0.0.0 # Port number port: 9990 # SHA-256 hashed password (plaintext - hashed internally) password: "your_secure_password" # SSL/TLS Configuration (optional) ssl: enable: false keyFile: /path/to/private.key certFile: /path/to/certificate.crt # Enable debug logging debug: false ``` --- ## Appendix C: Host State Codes | Code | State | Description | |------|-------|-------------| | 0 | IDLE | Dynamic mode, no active protocol | | 1 | LOCKOUT | Lockout mode, no transmissions | | 2 | ERROR | Error state | | 3 | QUIT | Shutting down | | 4 | DMR | DMR mode active | | 5 | P25 | P25 mode active | | 6 | NXDN | NXDN mode active | --- ## Revision History | Version | Date | Changes | |---------|------|---------| | 1.0 | Dec 3, 2025 | Initial documentation based on source code analysis | --- **End of Document**