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
- Overview
- Authentication
- System Endpoints
- Modem Control
- Trunking and Supervisory Control
- Radio ID Lookup
- DMR Protocol Endpoints
- P25 Protocol Endpoints
- NXDN Protocol Endpoints
- Response Formats
- Error Handling
- Security Considerations
- 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
keyFileandcertFileparameters - Uses OpenSSL when
ENABLE_SSLis 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
- Client sends password hash to
/authendpoint - Server validates password and returns authentication token
- Client includes token in
X-DVM-Auth-Tokenheader for subsequent requests - 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:
{
"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):
{
"status": 200,
"token": "12345678901234567890"
}
Response (Failure):
{
"status": 400,
"message": "invalid password"
}
Error Conditions:
400 Bad Request: Invalid password, malformed auth string, or invalid characters401 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):
# 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:
{
"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:
{
"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 enabledp25Enabled: P25 protocol enablednxdnEnabled: NXDN protocol enabledstate: Current host state (0=IDLE, 1=LOCKOUT, 2=ERROR, 4=DMR, 5=P25, 6=NXDN)isTxCW: Currently transmitting CW IDfixedMode: Host is in fixed mode (true) or dynamic mode (false)dmrTSCCEnable: DMR TSCC (Tier III Control Channel) data enableddmrCC: DMR control channel mode activep25CtrlEnable: P25 control channel data enabledp25CC: P25 control channel mode activenxdnCtrlEnable: NXDN control channel data enablednxdnCC: NXDN control channel mode activetx: Modem currently transmittingchannelId: Current RF channel IDchannelNo: Current RF channel numberlastDstId: Last destination ID (talkgroup)lastSrcId: Last source ID (radio ID)peerId: Peer ID from network configurationsysId: System IDsiteId: Site IDp25RfssId: P25 RFSS IDp25NetId: P25 Network ID (WACN)p25NAC: P25 Network Access Code
Voice Channels Array (vcChannels[]):
channelNo: Voice channel numberchannelId: Voice channel IDtx: Channel currently transmittinglastDstId: Last destination ID on this channellastSrcId: Last source ID on this channel
Modem Object (modem):
portType: Port type (uart, tcp, udp, null)modemPort: Serial port pathportSpeed: 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 adjustmenttxDCOffset: Transmit DC offset adjustmentdmrSymLevel3Adj: 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 countdmrRxDelay: DMR receive delayp25CorrCount: P25 correlation countrxFrequency: 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 connectedprotoVer: 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:
{
"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 numberaddress: Network address for voice channelport: 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:
{
"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):
{
"status": 200,
"message": "Dynamic mode",
"mode": 0
}
Response Fields:
status: HTTP status code (200)message: Mode description"Dynamic mode"foridleandlockoutmodes"Fixed mode"fordmr,p25, andnxdnmodes
mode: Numeric mode value (0=IDLE, 1=LOCKOUT, 4=DMR, 5=P25, 6=NXDN)
Error Responses:
Missing or Invalid Mode:
{
"status": 400,
"message": "password was not a valid string"
}
Note: Implementation bug - error message incorrectly says "password" instead of "mode"
Invalid Mode Value:
{
"status": 400,
"message": "invalid mode"
}
Protocol Not Enabled:
{
"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
idleandlockoutsetfixedModeto 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:
{
"force": false
}
Request Fields:
force(required, boolean): Shutdown modefalse: Graceful shutdown (allows cleanup, wait for transmissions to complete)true: Forced shutdown (immediate termination)
Response (Success):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
Missing or Invalid Force Parameter:
{
"status": 400,
"message": "force was not a valid boolean"
}
Implementation Behavior:
- Sets global
g_killedflag totruefor graceful shutdown - If
force=true, also setsHOST_STATE_QUITfor 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
forceparameter, 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:
{
"state": 4,
"enable": true
}
Request Fields:
state(required, integer): Protocol state value4= DMR5= P256= NXDN
enable(required, boolean): Enable (true) or disable (false) supervisory mode
Response (Success):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
Host Not Authoritative:
{
"status": 400,
"message": "Host is not authoritative, cannot set supervisory state"
}
Invalid State Parameter:
{
"status": 400,
"message": "state was not a valid integer"
}
Invalid Enable Parameter:
{
"status": 400,
"message": "enable was not a boolean"
}
Invalid State Value:
{
"status": 400,
"message": "invalid mode"
}
Protocol Not Enabled:
{
"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: truein 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):
{
"state": 4,
"dstId": 1,
"slot": 1
}
Request Body (P25):
{
"state": 5,
"dstId": 1,
"dataPermit": false
}
Request Body (NXDN):
{
"state": 6,
"dstId": 1
}
Request Fields:
state(required, integer): Protocol state value4= DMR5= P256= NXDN
dstId(required, integer): Destination talkgroup ID to permitslot(required for DMR, integer): TDMA slot number1= Slot 12= Slot 2
dataPermit(optional for P25, boolean): Enable data permissions (default:false)
Response (Success):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
Host Is Authoritative:
{
"status": 400,
"message": "Host is authoritative, cannot permit TG"
}
Invalid State Parameter:
{
"status": 400,
"message": "state was not a valid integer"
}
Invalid Destination ID:
{
"status": 400,
"message": "destination ID was not a valid integer"
}
Invalid Slot (DMR only):
{
"status": 400,
"message": "slot was not a valid integer"
}
Illegal DMR Slot Value:
{
"status": 400,
"message": "illegal DMR slot"
}
Returned when slot is 0 or greater than 2
Invalid State Value:
{
"status": 400,
"message": "invalid mode"
}
Protocol Not Enabled:
{
"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: falsein 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
dataPermitflag 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):
{
"state": 4,
"dstId": 1,
"srcId": 123456,
"slot": 1,
"unitToUnit": false
}
Request Body (P25):
{
"state": 5,
"dstId": 1,
"srcId": 123456,
"unitToUnit": false
}
Request Body (NXDN):
{
"state": 6,
"dstId": 1,
"srcId": 123456,
"unitToUnit": false
}
Request Fields:
state(required, integer): Protocol state value4= DMR5= P256= 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 astrueto grant function - inverted logic)true= Unit-to-unit call (passed asfalseto grant function)
Response (Success):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
Host Is Authoritative Control Channel:
{
"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:
{
"status": 400,
"message": "state was not a valid integer"
}
Invalid Destination ID:
{
"status": 400,
"message": "destination ID was not a valid integer"
}
Illegal Destination TGID:
{
"status": 400,
"message": "destination ID is an illegal TGID"
}
Returned when dstId is 0
Invalid Source ID:
{
"status": 400,
"message": "source ID was not a valid integer"
}
Illegal Source ID:
{
"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):
{
"status": 400,
"message": "slot was not a valid integer"
}
Illegal DMR Slot Value:
{
"status": 400,
"message": "illegal DMR slot"
}
Returned when slot is 0 or greater than 2
Invalid State Value:
{
"status": 400,
"message": "invalid mode"
}
Protocol Not Enabled:
{
"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
unitToUnitparameter has inverted logic: the value is negated before being passed to the grant functionunitToUnit: falseresults in group call (truepassed to grant function)unitToUnit: trueresults in unit-to-unit call (falsepassed to grant function)
- DMR requires valid slot specification (1 or 2)
- Both
srcIdanddstIdmust 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):
{
"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
0withtrueflag - 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):
{
"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
0withtrueflag - 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-grantswhich 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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
Invalid Arguments:
{
"status": 400,
"message": "invalid API call arguments"
}
RID Zero Not Allowed:
{
"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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
Invalid Arguments:
{
"status": 400,
"message": "invalid API call arguments"
}
RID Zero Not Allowed:
{
"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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
DMR Not Enabled:
{
"status": 503,
"message": "DMR mode is not enabled"
}
Beacons Not Enabled:
{
"status": 503,
"message": "DMR beacons are not enabled"
}
Implementation Behavior:
- Sets global flag
g_fireDMRBeacon = trueto trigger beacon transmission - Requires DMR mode enabled in configuration
- Requires DMR beacons enabled (
dmr.beacons.enable: truein 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):
{
"status": 200,
"debug": true,
"verbose": false
}
Response Fields:
status: HTTP status code (200)debug(boolean): Current debug logging stateverbose(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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
DMR Not Enabled:
{
"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
debugenables standard debug logging for DMR operationsverboseenables 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):
{
"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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
DMR Not Enabled:
{
"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:
{
"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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
DMR Not Enabled:
{
"status": 503,
"message": "DMR mode is not enabled"
}
Invalid Command:
{
"status": 400,
"message": "command was not valid"
}
Invalid Destination ID Type:
{
"status": 400,
"message": "destination ID was not valid"
}
Zero Destination ID:
{
"status": 400,
"message": "destination ID was not valid"
}
Invalid Slot:
{
"status": 400,
"message": "slot was not valid"
}
Invalid Slot Number:
{
"status": 400,
"message": "invalid DMR slot number (slot == 0 or slot > 3)"
}
Unknown Command:
{
"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": CallswriteRF_Call_Alrt(slot, WUID_ALL, dstId)- sends call alert"check": CallswriteRF_Ext_Func(slot, CHECK, WUID_ALL, dstId)- sends radio check request"inhibit": CallswriteRF_Ext_Func(slot, INHIBIT, WUID_STUNI, dstId)- uses STUN Individual addressing"uninhibit": CallswriteRF_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/uninhibituse STUNI (stun individual) addressing modepageandcheckuse 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):
{
"status": 200,
"message": "DMR CC is enabled"
}
or
{
"status": 200,
"message": "DMR CC is disabled"
}
Response Fields:
status: HTTP status code (200)message: Current state after toggle
Error Responses:
DMR Not Enabled:
{
"status": 503,
"message": "DMR mode is not enabled"
}
TSCC Data Not Enabled:
{
"status": 400,
"message": "DMR control data is not enabled!"
}
P25 Enabled (Conflict):
{
"status": 400,
"message": "Can't enable DMR control channel while P25 is enabled!"
}
NXDN Enabled (Conflict):
{
"status": 400,
"message": "Can't enable DMR control channel while NXDN is enabled!"
}
Implementation Behavior:
- Toggles
m_host->m_dmrCtrlChannelflag (true ↔ false) - Requires
m_host->m_dmrTSCCDatato 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):
{
"status": 200,
"message": "DMR CC broadcast is enabled"
}
or
{
"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:
{
"status": 503,
"message": "DMR mode is not enabled"
}
Implementation Behavior:
- Toggles
m_host->m_dmrTSCCDataflag (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):
{
"status": 200,
"affiliations": [
{
"srcId": 123456,
"grpId": 1
},
{
"srcId": 234567,
"grpId": 2
}
]
}
Response Fields:
status: HTTP status code (200)affiliations(array): List of current affiliationssrcId(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 isgrpId(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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
P25 Not Enabled:
{
"status": 503,
"message": "P25 mode is not enabled"
}
P25 Control Data Not Enabled:
{
"status": 503,
"message": "P25 control data is not enabled"
}
Implementation Behavior:
- Sets global flag
g_fireP25Control = trueto trigger control channel transmission - Requires P25 mode enabled in configuration
- Requires P25 control data enabled (
p25.control.enable: truein 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):
{
"status": 200,
"debug": true,
"verbose": false
}
Response Fields:
status: HTTP status code (200)debug(boolean): Current debug logging stateverbose(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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
P25 Not Enabled:
{
"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
debugenables standard debug logging for P25 operationsverboseenables 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):
{
"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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
P25 Not Enabled:
{
"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):
{
"command": "check",
"dstId": 123456
}
Request Body (Set MFID):
{
"command": "p25-setmfid",
"mfId": 144
}
Request Body (Dynamic Regroup):
{
"command": "dyn-regrp",
"dstId": 123456,
"tgId": 5000
}
Request Body (Emergency Alarm):
{
"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"
- Not required for:
mfId(required forp25-setmfid, integer): Manufacturer ID (uint8_t)tgId(required fordyn-regrp, integer): Target talkgroup IDsrcId(required foremerg, integer): Source radio ID (must not be 0)
Response (Success):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
P25 Not Enabled:
{
"status": 503,
"message": "P25 mode is not enabled"
}
Invalid Command:
{
"status": 400,
"message": "command was not valid"
}
Invalid Destination ID:
{
"status": 400,
"message": "destination ID was not valid"
}
Invalid MFID:
{
"status": 400,
"message": "MFID was not valid"
}
Invalid Talkgroup ID:
{
"status": 400,
"message": "talkgroup ID was not valid"
}
Invalid Source ID:
{
"status": 400,
"message": "source ID was not valid"
}
Unknown Command:
{
"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": Callscontrol()->setLastMFId(mfId)- no RF transmission"page": CallswriteRF_TSDU_Call_Alrt(WUID_FNE, dstId)"check": CallswriteRF_TSDU_Ext_Func(CHECK, WUID_FNE, dstId)"inhibit": CallswriteRF_TSDU_Ext_Func(INHIBIT, WUID_FNE, dstId)"uninhibit": CallswriteRF_TSDU_Ext_Func(UNINHIBIT, WUID_FNE, dstId)"dyn-regrp": CallswriteRF_TSDU_Ext_Func(DYN_REGRP_REQ, tgId, dstId)"dyn-regrp-cancel": CallswriteRF_TSDU_Ext_Func(DYN_REGRP_CANCEL, 0, dstId)"dyn-regrp-lock": CallswriteRF_TSDU_Ext_Func(DYN_REGRP_LOCK, 0, dstId)"dyn-regrp-unlock": CallswriteRF_TSDU_Ext_Func(DYN_REGRP_UNLOCK, 0, dstId)"group-aff-req": CallswriteRF_TSDU_Grp_Aff_Q(dstId)- GAQ message"unit-reg": CallswriteRF_TSDU_U_Reg_Cmd(dstId)- U_REG message"emerg": CallswriteRF_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):
{
"status": 200,
"message": "P25 CC is enabled"
}
or
{
"status": 200,
"message": "P25 CC is disabled"
}
Response Fields:
status: HTTP status code (200)message: Current state after toggle
Error Responses:
P25 Not Enabled:
{
"status": 503,
"message": "P25 mode is not enabled"
}
P25 Control Data Not Enabled:
{
"status": 400,
"message": "P25 control data is not enabled!"
}
DMR Enabled (Conflict):
{
"status": 400,
"message": "Can't enable P25 control channel while DMR is enabled!"
}
NXDN Enabled (Conflict):
{
"status": 400,
"message": "Can't enable P25 control channel while NXDN is enabled!"
}
Implementation Behavior:
- Toggles
m_host->m_p25CtrlChannelflag (true ↔ false) - Sets
m_host->m_p25CtrlBroadcast = truewhen enabling - Sets
g_fireP25Control = trueto trigger control burst - Calls
m_p25->setCCHalted(false)to resume control channel - Requires
m_host->m_p25CCDatato 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):
{
"status": 200,
"message": "P25 CC broadcast is enabled"
}
or
{
"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:
{
"status": 503,
"message": "P25 mode is not enabled"
}
P25 Control Data Not Enabled:
{
"status": 400,
"message": "P25 control data is not enabled!"
}
Implementation Behavior:
- Toggles
m_host->m_p25CtrlBroadcastflag (true ↔ false) - If disabling broadcast:
- Sets
g_fireP25Control = falseto stop control bursts - Calls
m_p25->setCCHalted(true)to halt control channel
- Sets
- If enabling broadcast:
- Sets
g_fireP25Control = trueto start control bursts - Calls
m_p25->setCCHalted(false)to resume control channel
- Sets
- 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:
{
"tsbk": "00112233445566778899AABB"
}
Request Fields:
tsbk(required, string): Raw TSBK data as hexadecimal string (must be exactly 24 characters / 12 bytes)
Response (Success):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
P25 Not Enabled:
{
"status": 503,
"message": "P25 mode is not enabled"
}
Invalid TSBK Field:
{
"status": 400,
"message": "tsbk was not valid"
}
Invalid TSBK Length:
{
"status": 400,
"message": "TSBK must be 24 characters in length"
}
Invalid TSBK Characters:
{
"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):
{
"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 objectssrcId(uint32): Radio ID (subscriber unit)grpId(uint32): Talkgroup ID the radio is affiliated to
Empty Affiliations Response:
{
"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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
NXDN Not Enabled:
{
"status": 503,
"message": "NXDN mode is not enabled"
}
NXDN Control Data Not Configured:
{
"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 = trueto 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):
{
"status": 200,
"debug": true,
"verbose": false
}
Response Fields:
status: HTTP status code (200)debug(boolean): Current debug logging stateverbose(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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
NXDN Not Enabled:
{
"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
debugenables standard debug logging for NXDN operationsverboseenables 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):
{
"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):
{
"status": 200,
"message": "OK"
}
Response Fields:
status: HTTP status code (200)message: Always"OK"
Error Responses:
NXDN Not Enabled:
{
"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):
{
"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:
{
"status": 503,
"message": "NXDN mode is not enabled"
}
NXDN Control Data Not Configured:
{
"status": 400,
"message": "NXDN control data is not enabled!"
}
DMR Protocol Conflict:
{
"status": 400,
"message": "Can't enable NXDN control channel while DMR is enabled!"
}
P25 Protocol Conflict:
{
"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_nxdnCtrlChannelflag (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):
{
"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 objectssrcId(uint32): Radio ID (subscriber unit)grpId(uint32): Talkgroup ID the radio is affiliated to
Empty Affiliations Response:
{
"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:
{
"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:
{
"status": 200,
"message": "OK",
"value": true
}
Multiple Values Response:
{
"status": 200,
"message": "OK",
"debug": true,
"verbose": false
}
Array Response:
{
"status": 200,
"message": "OK",
"affiliations": [
{"srcId": 123456, "grpId": 1, "slot": 1}
]
}
Complex Object Response:
{
"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:
{
"status": 400,
"message": "descriptive error message"
}
HTTP Status Codes:
200 OK: Request successful400 Bad Request: Invalid request format or parameters401 Unauthorized: Missing or invalid authentication token404 Not Found: Endpoint does not exist405 Method Not Allowed: Wrong HTTP method for endpoint500 Internal Server Error: Server-side error503 Service Unavailable: Requested service/protocol not enabled
10.4 Protocol-Specific Responses
DMR Responses:
- Include
slotfield (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:
{
"status": 200,
"message": "DMR CC is enabled"
}
or
{
"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:
{
"status": 401,
"message": "no authentication token"
}
Invalid Token:
{
"status": 401,
"message": "invalid authentication token"
}
Illegal Token:
{
"status": 401,
"message": "illegal authentication token"
}
Authentication Failed (Wrong Password):
{
"status": 401,
"message": "authentication failed"
}
11.2 Validation Errors
Invalid JSON:
{
"status": 400,
"message": "JSON parse error: unexpected character"
}
Invalid Content-Type:
{
"status": 400,
"message": "invalid content-type (must be application/json)"
}
Missing Required Field:
{
"status": 400,
"message": "field 'dstId' is required"
}
Invalid Field Value:
{
"status": 400,
"message": "dstId was not valid"
}
Invalid Command:
{
"status": 400,
"message": "unknown command specified"
}
Invalid Hex String (P25 raw-tsbk):
{
"status": 400,
"message": "TSBK must be 24 characters in length"
}
or
{
"status": 400,
"message": "TSBK contains invalid characters"
}
11.3 Service Errors
Protocol Not Enabled:
{
"status": 503,
"message": "DMR mode is not enabled"
}
{
"status": 503,
"message": "P25 mode is not enabled"
}
{
"status": 503,
"message": "NXDN mode is not enabled"
}
Feature Not Configured:
{
"status": 503,
"message": "DMR beacons are not enabled"
}
{
"status": 503,
"message": "DMR control data is not enabled"
}
{
"status": 503,
"message": "P25 control data is not enabled"
}
{
"status": 503,
"message": "NXDN control data is not enabled"
}
Unauthorized Operation:
{
"status": 400,
"message": "Host is not authoritative, cannot set supervisory state"
}
Protocol Conflicts:
{
"status": 400,
"message": "Can't enable DMR control channel while P25 is enabled!"
}
{
"status": 400,
"message": "Can't enable P25 control channel while DMR is enabled!"
}
{
"status": 400,
"message": "Can't enable NXDN control channel while DMR is enabled!"
}
11.4 Parameter-Specific Errors
Invalid Slot (DMR):
{
"status": 400,
"message": "slot is invalid, must be 1 or 2"
}
Invalid Source ID:
{
"status": 400,
"message": "srcId was not valid"
}
Invalid Talkgroup ID:
{
"status": 400,
"message": "tgId was not valid"
}
Invalid Voice Channel:
{
"status": 400,
"message": "voiceChNo was not valid"
}
Invalid Mode:
{
"status": 400,
"message": "mode is invalid"
}
11.5 Error Handling Best Practices
- Always check HTTP status code first - 200 means success, anything else is an error
- Parse error messages - The
messagefield contains human-readable error details - Handle 503 errors gracefully - Service unavailable often means protocol not enabled in config
- Retry on 401 - May need to re-authenticate if token expired
- Log errors - Keep error responses for debugging and audit trails
- Validate input before sending - Many errors can be prevented with client-side validation
- 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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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
#!/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 radiocheck- Radio checkinhibit- Inhibit radiouninhibit- Uninhibit radiodmr-setmfid- Set manufacturer ID
P25 /rid Commands (12 total):
p25-setmfid- Set manufacturer IDpage- Page radiocheck- Radio checkinhibit- Inhibit radiouninhibit- Uninhibit radiodyn-regrp- Dynamic regroupdyn-regrp-cancel- Cancel dynamic regroupdyn-regrp-lock- Lock dynamic regroupdyn-regrp-unlock- Unlock dynamic regroupgroup-aff-req- Group affiliation requestunit-reg- Unit registrationemerg- Emergency acknowledgment
Appendix B: Configuration File Reference
REST API Configuration (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