You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3914 lines
90 KiB
3914 lines
90 KiB
# 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<uint32_t, uint32_t>` 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**
|