TIMMYG Studios

API Reference

Complete API reference for Tommy's Radio exports, functions, and events.

Server ExportsClient ExportsEvents

Server Exports

Channel Information

getSpeakersInChannel(frequency)

Get all users connected to transmit on a specific channel.

Parameters:

  • frequency (string/number) - Channel frequency (e.g., "154.755")

Returns: table - Array of server IDs actively speaking on the channel

Example Usage
local speakers = exports['radio']:getSpeakersInChannel("154.755")
for _, serverId in ipairs(speakers) do
    local name = exports['radio']:getPlayerName(serverId)
    print(name .. " can transmit on dispatch")
end

getListenersInChannel(frequency)

Get all users listening to a channel without transmit capability.

Parameters:

  • frequency (string/number) - Channel frequency

Returns: table - Array of server IDs listening to the channel

Example Usage
local listeners = exports['radio']:getListenersInChannel("154.755")
print("Channel has " .. #listeners .. " listeners")

getAllUsersInChannel(frequency)

Get all users on a channel regardless of role (speakers + listeners combined).

Parameters:

  • frequency (string/number) - Channel frequency

Returns: table - Array of server IDs on the channel

Example Usage
local allUsers = exports['radio']:getAllUsersInChannel("154.755")
print("Total users on channel: " .. #allUsers)

getActiveTalkersInChannel(frequency)

Get users actively talking right now on the channel (PTT pressed).

Parameters:

  • frequency (string/number) - Channel frequency

Returns: table - Array of server IDs currently transmitting

Example Usage
local talkers = exports['radio']:getActiveTalkersInChannel("154.755")
if #talkers > 0 then
    print("Someone is transmitting on dispatch!")
end

getActiveChannels()

Get list of all channels with active users.

Returns: table - Array of active channel frequencies

Example Usage
local activeChannels = exports['radio']:getActiveChannels()
for _, frequency in ipairs(activeChannels) do
    print("Channel " .. frequency .. " is active")
end

getChannelInfo(channel)

Get detailed information about a specific channel.

Parameters:

  • channel (string/number) - Channel frequency

Returns: table - Channel object with speakers, listeners, activeTalkers arrays, or nil

Example Usage
local channelInfo = exports['radio']:getChannelInfo("154.755")
if channelInfo then
    print("Speakers: " .. #channelInfo.speakers)
    print("Listeners: " .. #channelInfo.listeners)
    print("Active Talkers: " .. #channelInfo.activeTalkers)
end

getAllChannels()

Get complete list of channels from config with zones, names, types, etc.

Returns: table - Array of all configured channels with full details

Example Usage
local channels = exports['radio']:getAllChannels()
for _, channel in ipairs(channels) do
    print(channel.name .. " - " .. channel.frequency .. " (" .. channel.zone .. ")")
end

Alert & Signal Management

getChannelAlert(frequency)

Get active alert on a channel (e.g., "Signal 100").

Parameters:

  • frequency (string/number) - Channel frequency

Returns: table - Alert object {name, color, tone, isPersistent} or nil if no alert

Example Usage
local alert = exports['radio']:getChannelAlert("154.755")
if alert then
    print("Alert active: " .. alert.name)
end

setChannelSignal(frequency, enabled)

Activate or clear an alert on a channel.

Parameters:

  • frequency (string/number) - Channel frequency
  • enabled (boolean) - Alert state

Returns: boolean - true on success

Example Usage
-- Activate Signal 100 on dispatch
exports['radio']:setChannelSignal("154.755", true)

-- Clear signal after 60 seconds
Wait(60000)
exports['radio']:setChannelSignal("154.755", false)

getChannelPanic(frequency)

Get all users with panic activated on a channel.

Parameters:

  • frequency (string/number) - Channel frequency

Returns: table - Table of server IDs with active panic buttons

Example Usage
local panicUsers = exports['radio']:getChannelPanic("154.755")
for serverId, _ in pairs(panicUsers) do
    print("User " .. serverId .. " has panic active!")
end

setChannelPanic(frequency, serverId, enabled)

Set or clear panic button for a specific user on a channel.

Parameters:

  • frequency (string/number) - Channel frequency
  • serverId (number) - Player's server ID
  • enabled (boolean) - Panic state

Returns: boolean - true on success

Example Usage
-- Activate panic for officer
exports['radio']:setChannelPanic("154.755", 1, true)

-- Clear panic
exports['radio']:setChannelPanic("154.755", 1, false)

getUserPanicState(serverId, frequency)

Check if a specific user has panic active on a channel.

Parameters:

  • serverId (number) - Player's server ID
  • frequency (string/number) - Channel frequency

Returns: boolean - Panic state

Example Usage
if exports['radio']:getUserPanicState(1, "154.755") then
    print("Officer has emergency activated!")
end

User Management

setUserChannel(serverId, frequency)

Move a user to a specific channel (handles trunked channels automatically).

Parameters:

  • serverId (number) - Player's server ID
  • frequency (string/number) - Target channel frequency

Returns: boolean - true on success

Example Usage
-- Move officer to tactical channel
local success = exports['radio']:setUserChannel(1, "155.475")
if success then
    print("Officer moved to tactical channel")
end

disconnectUser(serverId)

Disconnect a user from the radio system.

Parameters:

  • serverId (number) - Player's server ID

Returns: boolean - true on success

Example Usage
-- Remove user from radio (e.g., when going off-duty)
exports['radio']:disconnectUser(1)

isUserTalking(serverId, frequency)

Check if a specific user is actively transmitting on a channel.

Parameters:

  • serverId (number) - Player's server ID
  • frequency (string/number) - Channel frequency

Returns: boolean - Transmit state

Example Usage
if exports['radio']:isUserTalking(1, "154.755") then
    print("User 1 is transmitting on dispatch")
end

User Information

getPlayerName(serverId)

Get the display name of a player.

Parameters:

  • serverId (number) - Player's server ID

Returns: string - Player name

Example Usage
local name = exports['radio']:getPlayerName(1)
print("Player name: " .. name)

getUserNacId(serverId)

Get a user's Network Access Code ID.

Parameters:

  • serverId (number) - Player's server ID

Returns: string - NAC ID or nil

Example Usage
local nacId = exports['radio']:getUserNacId(1)
if nacId then
    print("NAC ID: " .. nacId)
end

getUserInfo(serverId)

Get both name and NAC ID for a user.

Parameters:

  • serverId (number) - Player's server ID

Returns: table - User info {name, nacId}

Example Usage
local userInfo = exports['radio']:getUserInfo(1)
print(userInfo.name .. " - NAC: " .. (userInfo.nacId or "N/A"))

hasRadioAccess(serverId)

Check if a user has radio system access.

Parameters:

  • serverId (number) - Player's server ID

Returns: boolean - Access state

Example Usage
if exports['radio']:hasRadioAccess(source) then
    -- Grant radio permissions
end

Audio Control

playToneOnChannel(frequency, tone)

Play a tone to all users on a specific channel.

Parameters:

  • frequency (string/number) - Channel frequency
  • tone (string) - Tone name (e.g., "beep", "chirp")
Example Usage
-- Play tone to entire dispatch channel
exports['radio']:playToneOnChannel("154.755", "alert")

playToneOnSource(serverId, tone)

Play a tone to a specific user only.

Parameters:

  • serverId (number) - Player's server ID
  • tone (string) - Tone name
Example Usage
-- Play confirmation tone to specific officer
exports['radio']:playToneOnSource(1, "beep")

Client Exports

Radio Control

setCurrentChannel(channel)

Set your main speaking channel.

Parameters:

  • channel (string/number) - Channel frequency
Example Usage
-- Switch to dispatch channel
exports['radio']:setCurrentChannel("154.755")

getCurrentFrequency()

Get your current channel frequency.

Returns: string/number - Current frequency, or -1 if not connected

Example Usage
local freq = exports['radio']:getCurrentFrequency()
if freq ~= -1 then
    print("Current frequency: " .. freq)
end

getCurrentChannel()

Get complete information about your current channel.

Returns: table - Channel object with full details

Example Usage
local channel = exports['radio']:getCurrentChannel()
if channel then
    print("On: " .. channel.name .. " (" .. channel.frequency .. ")")
end

openRadio(shouldFocus)

Open the radio interface UI with optional focus control.

Parameters:

  • shouldFocus (boolean, optional) - Whether to focus the radio UI. Defaults to true if not provided.
    • true - Opens and focuses the radio (or focuses if already open)
    • false - Opens without focusing (only if radio is closed)

Returns: void

Example Usage
-- Open and focus radio (default behavior)
exports['radio']:openRadio()

-- Explicitly open and focus
exports['radio']:openRadio(true)

-- Open without focusing
exports['radio']:openRadio(false)

-- Focus an already-open radio
if exports['radio']:isRadioOpen() and not exports['radio']:isRadioFocused() then
    exports['radio']:openRadio(true)
end

closeRadio()

Close the radio interface UI.

Example Usage
exports['radio']:closeRadio()

Listening Channels

addListeningChannel(channel)

Add a channel to your listening list (scan).

Parameters:

  • channel (string/number) - Channel frequency
Example Usage
-- Add tactical channel to scan
exports['radio']:addListeningChannel("155.475")

removeListeningChannel(channel)

Remove a channel from your listening list.

Parameters:

  • channel (string/number) - Channel frequency
Example Usage
exports['radio']:removeListeningChannel("155.475")

getListeningChannels()

Get all channels you're currently scanning.

Returns: table - Array of channel frequencies

Example Usage
local listening = exports['radio']:getListeningChannels()
print("Scanning " .. #listening .. " channels")

Transmission Control

startTransmitting()

Start PTT transmission (same as pressing PTT key).

Example Usage
exports['radio']:startTransmitting()

stopTransmitting()

Stop PTT transmission (same as releasing PTT key).

Example Usage
exports['radio']:stopTransmitting()

isTransmitting()

Check if you're currently transmitting.

Returns: boolean - Transmit state

Example Usage
if exports['radio']:isTransmitting() then
    print("You are transmitting")
end

getActiveTalker()

Get information about who's currently talking on your frequency.

Returns: table - Talker info {serverId, name, frequency} or nil

Example Usage
local talker = exports['radio']:getActiveTalker()
if talker then
    print(talker.name .. " is talking on " .. talker.frequency)
end

setTalking(talking)

Set talking state for UI feedback.

Parameters:

  • talking (boolean) - Talking state
Example Usage
exports['radio']:setTalking(true)

Radio State

isConnected()

Check if connected to a channel.

Returns: boolean - Connection state

Example Usage
if exports['radio']:isConnected() then
    print("Radio connected")
end

isPowerOn()

Check if radio is powered on.

Returns: boolean - Power state

Example Usage
if exports['radio']:isPowerOn() then
    print("Radio is on")
end

isRadioOpen()

Check if radio UI is currently open.

Returns: boolean - UI state

Example Usage
if exports['radio']:isRadioOpen() then
    print("Radio UI is displayed")
end

isRadioFocused()

Check if radio UI is currently focused (has NUI focus).

Returns: boolean - Focus state

Example Usage
if exports['radio']:isRadioFocused() then
    print("Radio UI is focused")
end

-- Check if radio is open but not focused
if exports['radio']:isRadioOpen() and not exports['radio']:isRadioFocused() then
    print("Radio is open but not focused")
end

Audio & Volume

setVolume(volume)

Set radio voice volume.

Parameters:

  • volume (number) - Volume level 0.0 to 1.0
Example Usage
exports['radio']:setVolume(0.75)

getVolume()

Get current radio voice volume.

Returns: number - Volume level 0.0 to 1.0

Example Usage
local volume = exports['radio']:getVolume()
print("Voice volume: " .. (volume * 100) .. "%")

setToneVolume(volume)

Set tone/SFX volume.

Parameters:

  • volume (number) - Volume level 0.0 to 1.0
Example Usage
exports['radio']:setToneVolume(0.35)

getToneVolume()

Get current tone/SFX volume.

Returns: number - Volume level 0.0 to 1.0

Example Usage
local toneVol = exports['radio']:getToneVolume()
print("Tone volume: " .. (toneVol * 100) .. "%")

set3DVolume(volume)

Set 3D audio volume.

Parameters:

  • volume (number) - Volume level 0.0 to 1.0
Example Usage
exports['radio']:set3DVolume(0.5)

get3DVolume()

Get current 3D audio volume.

Returns: number - Volume level 0.0 to 1.0

Example Usage
local vol3D = exports['radio']:get3DVolume()
print("3D audio volume: " .. (vol3D * 100) .. "%")

playTone(tone)

Play a tone locally.

Parameters:

  • tone (string) - Tone name (e.g., "beep", "chirp")
Example Usage
exports['radio']:playTone("beep")

Appearance & Layout

setRadioLayout(layout)

Change radio style/model.

Parameters:

  • layout (string) - Radio model name (e.g., "ATX-8000")
Example Usage
exports['radio']:setRadioLayout("ATX-8000")

getRadioLayout()

Get current radio layout.

Returns: string - Radio model name

Example Usage
local layout = exports['radio']:getRadioLayout()
print("Radio model: " .. layout)

setRadioTheme(theme)

Change radio UI theme.

Parameters:

  • theme (string) - Theme name (e.g., "Dark", "Auto")
Example Usage
exports['radio']:setRadioTheme("Dark")

getRadioTheme()

Get current radio theme.

Returns: string - Theme name

Example Usage
local theme = exports['radio']:getRadioTheme()
print("Theme: " .. theme)

setAnimationId(animId)

Set radio animation.

Parameters:

  • animId (number) - Animation index
Example Usage
exports['radio']:setAnimationId(2)

getAnimationId()

Get current animation ID.

Returns: number - Animation index

Example Usage
local animId = exports['radio']:getAnimationId()
print("Animation ID: " .. animId)

Settings

setEarbudsEnabled(enabled)

Enable/disable earbuds (disables 3D audio).

Parameters:

  • enabled (boolean) - Earbuds state
Example Usage
exports['radio']:setEarbudsEnabled(true)

getEarbudsEnabled()

Get earbuds state.

Returns: boolean - Earbuds enabled

Example Usage
if exports['radio']:getEarbudsEnabled() then
    print("Earbuds mode active")
end

setGPSEnabled(enabled)

Enable/disable GPS tracking.

Parameters:

  • enabled (boolean) - GPS state
Example Usage
exports['radio']:setGPSEnabled(true)

getGPSEnabled()

Get GPS state.

Returns: boolean - GPS enabled

Example Usage
if exports['radio']:getGPSEnabled() then
    print("GPS tracking enabled")
end

Alerts & Panic

triggerAlertOnChannel(frequency, enabled)

Trigger or clear an alert on a channel (sends to server).

Parameters:

  • frequency (string/number) - Channel frequency
  • enabled (boolean) - Alert state
Example Usage
-- Trigger Signal 100
exports['radio']:triggerAlertOnChannel("154.755", true)

panicButton(frequency, enabled)

Activate or clear panic button on a channel.

Parameters:

  • frequency (string/number) - Channel frequency
  • enabled (boolean) - Panic state
Example Usage
-- Activate panic
exports['radio']:panicButton("154.755", true)

User Information

getCurrentName()

Get your own player name.

Returns: string - Your player name

Example Usage
local myName = exports['radio']:getCurrentName()
print("You are: " .. myName)

getCurrentNacId()

Get your own NAC ID.

Returns: string - Your NAC ID or nil

Example Usage
local myNac = exports['radio']:getCurrentNacId()
if myNac then
    print("Your NAC: " .. myNac)
end

getZone()

Get your current zone name.

Returns: string - Zone name

Example Usage
local zone = exports['radio']:getZone()
print("Current zone: " .. zone)

System

getBatteryLevel()

Get radio battery level percentage.

Returns: number - Battery level 0-100

Example Usage
local battery = exports['radio']:getBatteryLevel()
print("Battery: " .. battery .. "%")

getSignalStrength()

Get current signal strength.

Returns: number/boolean - Signal strength value

Example Usage
local signal = exports['radio']:getSignalStrength()
print("Signal: " .. signal)

getConnectionDiagnostics()

Get detailed connection diagnostics for troubleshooting.

Returns: table - Diagnostic information

Example Usage
local diagnostics = exports['radio']:getConnectionDiagnostics()
for key, value in pairs(diagnostics) do
    print(key .. ": " .. tostring(value))
end

Usage Examples

Basic Channel Management

Server-Side Channel Control
-- Get all users on dispatch
local speakers = exports['radio']:getSpeakersInChannel("154.755")
local listeners = exports['radio']:getListenersInChannel("154.755")

print("Dispatch channel has:")
print("- " .. #speakers .. " units with transmit")
print("- " .. #listeners .. " supervisors listening")

-- Move officer to tactical
exports['radio']:setUserChannel(1, "155.475")

-- Check who's talking
local talkers = exports['radio']:getActiveTalkersInChannel("154.755")
if #talkers > 0 then
    for _, serverId in ipairs(talkers) do
        local name = exports['radio']:getPlayerName(serverId)
        print(name .. " is transmitting")
    end
end

Emergency System Integration

Panic Button Handler
-- Server-side emergency response
RegisterNetEvent('radio:handlePanic')
AddEventHandler('radio:handlePanic', function(frequency)
    local serverId = source
    local name = exports['radio']:getPlayerName(serverId)
    local nacId = exports['radio']:getUserNacId(serverId)

    -- Activate panic
    exports['radio']:setChannelPanic(frequency, serverId, true)

    -- Trigger Signal 100 on dispatch
    if frequency == "154.755" then
        exports['radio']:setChannelSignal(frequency, true)
        print("EMERGENCY: " .. name .. " (" .. nacId .. ") activated panic!")

        -- Play alert tone to all dispatch users
        exports['radio']:playToneOnChannel(frequency, "emergency")

        -- Auto-clear after 2 minutes
        SetTimeout(120000, function()
            exports['radio']:setChannelSignal(frequency, false)
            exports['radio']:setChannelPanic(frequency, serverId, false)
        end)
    end
end)

Radio State Monitoring

Client-Side State Checking
-- Check radio status before transmitting
Citizen.CreateThread(function()
    while true do
        Wait(5000)

        if exports['radio']:isConnected() and exports['radio']:isPowerOn() then
            local freq = exports['radio']:getCurrentFrequency()
            local channel = exports['radio']:getCurrentChannel()

            if freq ~= -1 then
                print("Connected to: " .. channel.name)

                -- Check who's talking
                local talker = exports['radio']:getActiveTalker()
                if talker then
                    print(talker.name .. " is speaking")
                end

                -- Monitor battery
                local battery = exports['radio']:getBatteryLevel()
                if battery < 20 then
                    print("WARNING: Low battery (" .. battery .. "%)")
                end
            end
        else
            print("Radio offline")
        end
    end
end)

Multi-Channel Scanning

Client-Side Channel Scanning
-- Setup multi-channel monitoring for supervisors
local function setupSupervisorRadio()
    -- Set primary channel to dispatch
    exports['radio']:setCurrentChannel("154.755")

    -- Add listening channels for all divisions
    exports['radio']:addListeningChannel("155.475") -- Tactical 1
    exports['radio']:addListeningChannel("155.490") -- Tactical 2
    exports['radio']:addListeningChannel("154.920") -- Fire/EMS

    -- Configure volumes
    exports['radio']:setVolume(0.8)
    exports['radio']:setToneVolume(0.3)
    exports['radio']:set3DVolume(0.5)

    print("Supervisor radio configured")
    local listening = exports['radio']:getListeningChannels()
    print("Monitoring " .. #listening .. " channels")
end

Advanced Channel Info

Server-Side Monitoring Dashboard
-- Get comprehensive channel status
local function getChannelStatus()
    local activeChannels = exports['radio']:getActiveChannels()

    for _, frequency in ipairs(activeChannels) do
        local info = exports['radio']:getChannelInfo(frequency)
        local alert = exports['radio']:getChannelAlert(frequency)
        local panicUsers = exports['radio']:getChannelPanic(frequency)

        print("=== Channel " .. frequency .. " ===")
        print("Speakers: " .. #info.speakers)
        print("Listeners: " .. #info.listeners)
        print("Active Talkers: " .. #info.activeTalkers)

        if alert then
            print("ALERT: " .. alert.name)
        end

        local panicCount = 0
        for _ in pairs(panicUsers) do panicCount = panicCount + 1 end
        if panicCount > 0 then
            print("PANIC: " .. panicCount .. " units")
        end
    end
end

Error Handling

Important Notes

  • Always check return values from export functions
  • Frequencies can be passed as strings ("154.755") or numbers (154.755)
  • Volume values are normalized to 0.0-1.0 range (not 0-100)
  • Server exports return nil or false on failure
  • Client exports return -1 or nil when radio is disconnected
  • NAC ID checks should handle nil returns gracefully

Web API Routes

Tommy's Radio includes a REST API and Socket.IO server for external integrations, allowing dispatch panels, third-party tools, and automation systems to interact with the radio system.

Configuration

Set these values in config.lua:

config.lua
Config = {
    serverPort = 7777,                       -- API server port
    authToken = "CHANGE_ME_TO_RANDOM_TEXT",  -- YOU CREATE THIS - any random secure string
    dispatchNacId = "CHANGE_ME",             -- YOU CHOOSE THIS - your dispatch panel password
}

Security Warning

YOU must create these values yourself:

  • authToken: Create any random string for encryption (e.g., "MyServer_Token_2024!")
  • dispatchNacId: Choose your dispatch panel password (e.g., "DISPATCH2024")

The values shown above (CHANGE_ME_TO_RANDOM_TEXT and CHANGE_ME) are placeholders. Never use default values in production. Anyone with these credentials can control your radio system.

Authentication

Step 1: Authenticate and Get Session

Endpoint: POST /radio/dispatch/auth

Request:

{
    "nacId": "141",
    "callsign": "Dispatcher-01"
}

Response (Success):

{
    "success": true,
    "sessionId": "dispatch_1234567890_abc123",
    "authToken": "changeme"
}

Response (Failure):

{
    "error": "Invalid NAC ID"
}

Notes:

  • nacId must match dispatchNacId in your config.lua
  • callsign is your display name in the dispatch panel
  • Sessions expire after 24 hours

Step 2: Make Authenticated Requests

All protected endpoints require these headers:

Authorization: Bearer changeme
x-session-id: dispatch_1234567890_abc123

Example:

curl -X GET http://192.0.2.100:7777/api/status \
  -H "Authorization: Bearer changeme" \
  -H "x-session-id: dispatch_1234567890_abc123"

REST API vs Socket.IO

REST API Only:

  • Use for triggering actions (broadcasts, alerts, tones, channel switches)
  • Does NOT make you visible in the dispatch panel
  • Does NOT receive real-time updates

REST API + Socket.IO:

  • Use for full dispatcher presence and monitoring
  • Appears in the dispatch panel as a connected dispatcher
  • Receives real-time voice data, panic alerts, and user updates
  • Required for voice communication features

Integration Note

Most integrations only need the REST API.

Public Endpoints

Health Check

Endpoint: GET /api/health

Response:

{
    "status": "ok",
    "service": "radio-dispatch",
    "timestamp": "2024-01-01T00:00:00.000Z",
    "version": "2.9"
}

Protected Endpoints

All endpoints below require authentication headers.

Get Configuration

Endpoint: GET /radio/dispatch/config

Response:

{
    "serverPort": 7777,
    "authToken": "changeme",
    "zones": {...},
    "alerts": {...},
    "logLevel": 3,
    "playTransmissionEffects": true,
    "analogTransmissionEffects": true
}

Get System Status

Endpoint: GET /api/status

Response:

{
    "userCount": 5,
    "channelCount": 3,
    "channels": {
        "460.125": {
            "frequency": 460.125,
            "speakers": [1, 2, 3],
            "listeners": [4, 5],
            "activeTalkers": [2],
            "empty": false
        }
    },
    "users": {
        "1": {
            "name": "John Doe",
            "nacId": "PD001",
            "currentChannel": 460.125,
            "listening": [460.125, 460.150],
            "isTalking": false
        }
    },
    "panicStatus": {
        "460.125": {
            "active": true,
            "serverId": 2,
            "playerName": "Officer Smith"
        }
    },
    "activeAlerts": {},
    "version": "2.9"
}

Get Dispatch Status

Endpoint: GET /radio/dispatch/status

Response:

{
    "channels": {
        "460.125": {
            "speakers": [1, 2],
            "listeners": [3],
            "activeTalkers": [1]
        }
    },
    "users": {
        "1": {
            "name": "John Doe",
            "nacId": "PD001"
        }
    },
    "panicStatus": {},
    "activeAlerts": {},
    "serverTimestamp": 1234567890
}

Get Available Tones

Endpoint: GET /radio/dispatch/tones

Response:

{
    "tones": [
        {
            "name": "BEEP",
            "file": "beep.wav"
        },
        {
            "name": "PANIC",
            "file": "panic.wav"
        }
    ]
}

Trigger Broadcast

Endpoint: POST /api/trigger-broadcast

Request:

{
    "message": "All units respond to 123 Main St",
    "frequency": 460.125,
    "type": "Emergency"
}

Parameters:

  • message (required): Broadcast message text
  • frequency (optional): Target frequency. If omitted, broadcasts to all users
  • type (optional): Alert type (e.g., "Emergency", "General")

Response:

{
    "success": true,
    "userCount": 5,
    "users": [1, 2, 3, 4, 5]
}

Play Tone on Channel

Endpoint: POST /api/play-tone

Request:

{
    "frequency": 460.125,
    "tone": "beep"
}

Valid Tones:

  • beep, boop, chirp, panic
  • alert_a, alert_b, alert_c

Response:

{
    "success": true
}

Trigger Alert

Endpoint: POST /radio/dispatch/alert/trigger

Request:

{
    "frequency": 460.125,
    "alertType": "Emergency",
    "alertConfig": {
        "name": "EMERGENCY",
        "color": "#FF0000",
        "isPersistent": true,
        "tone": "PANIC"
    }
}

Response:

{
    "success": true
}

Clear Alert

Endpoint: POST /radio/dispatch/alert/clear

Request:

{
    "frequency": 460.125
}

Response:

{
    "success": true
}

One-Shot Alert

Endpoint: POST /radio/dispatch/alert/oneshot

Triggers a temporary alert that doesn't persist.

Request:

{
    "frequency": 460.125,
    "alertConfig": {
        "name": "TEST",
        "tone": "BEEP"
    }
}

Response:

{
    "success": true
}

Dispatch Broadcast

Endpoint: POST /radio/dispatch/broadcast

Request:

{
    "message": "All units, be advised...",
    "frequency": 460.125,
    "type": "General",
    "tone": "alert_a"
}

Parameters:

  • message (required): Broadcast message
  • frequency (required): Target frequency
  • type (optional): Alert type
  • tone (optional): Tone to play (none, beep, alert_a, etc.)

Response:

{
    "success": true
}

Dispatch Tone

Endpoint: POST /radio/dispatch/tone

Request:

{
    "frequency": 460.125,
    "tone": "BEEP"
}

Response:

{
    "success": true
}

Switch User Channel

Endpoint: POST /radio/dispatch/switchChannel

Move a user to a different radio channel.

Request:

{
    "serverId": 1,
    "frequency": 460.150,
    "oldFrequency": 460.125
}

Parameters:

  • serverId (required): In-game player server ID
  • frequency (required): Target frequency
  • oldFrequency (optional): Previous frequency for logging

Response:

{
    "success": true
}

Send Alert to User

Endpoint: POST /dispatch/user/alert

Send an alert message to a specific user.

Request:

{
    "userId": 1,
    "message": "Respond to 123 Main St",
    "frequency": 460.125
}

Parameters:

  • userId (required): In-game player server ID
  • message (required): Alert message text
  • frequency (optional): Associated frequency

Response:

{
    "success": true
}

Disconnect User

Endpoint: POST /dispatch/user/disconnect

Disconnect a user from the radio system.

Request:

{
    "userId": 1
}

Parameters:

  • userId (required): In-game player server ID

Response:

{
    "success": true
}

Update Dispatch Callsign

Endpoint: POST /dispatch/user/update-callsign

Update the callsign for a dispatch user.

Request:

{
    "callsign": "Dispatcher-02",
    "userId": -5000
}

Parameters:

  • callsign (required): New callsign/display name
  • userId (optional): Dispatch user ID (negative). If omitted, updates the first dispatch user found

Response:

{
    "success": true,
    "callsign": "Dispatcher-02",
    "userId": -5000
}

Socket.IO Connection

To appear as a dispatcher in the dispatch panel, establish a Socket.IO connection.

Authentication

const io = require('socket.io-client');

const dispatchUserId = -(1000 + Math.floor(Math.random() * 10000));

const socket = io('http://YOUR_IP:7777', {
    auth: {
        authToken: 'changeme',
        serverId: dispatchUserId  // Must be negative
    }
});

Events to Emit

Set Session ID:

socket.emit('setDispatchSession', 'YOUR_SESSION_ID');

Set User Info:

socket.emit('updateUserInfo', {
    name: 'API-Bot',
    nacId: 'API'
});

Set Speaker Channel:

socket.emit('setSpeakerChannel', 460.125);

Add Listening Channel:

socket.emit('addListeningChannel', 460.150);

Remove Listening Channel:

socket.emit('removeListeningChannel', 460.150);

Set Talking State:

socket.emit('setTalking', true);  // or false

Heartbeat:

socket.emit('heartbeat', Date.now());

Events to Listen For

Connection:

socket.on('connect', () => {
    console.log('Connected to dispatch');
});

Heartbeat Response:

socket.on('heartbeat_response', (serverTime) => {
    console.log('Server time:', serverTime);
});

Voice Data:

socket.on('voice', (data) => {
    // data: { serverId, channel, voiceData, isTalking }
});

Panic Alert:

socket.on('panicAlert', (data) => {
    // data: { frequency, serverId, playerName }
});

Server Tone:

socket.on('serverTone', (data) => {
    // data: { tone, frequency }
});

Transmission Effects:

socket.on('transmissionEffect', (data) => {
    // data: { serverId, effectType, distance }
});

Example Usage

cURL Examples

Authenticate:

curl -X POST http://192.0.2.100:7777/radio/dispatch/auth \
  -H "Content-Type: application/json" \
  -d '{"nacId": "141", "callsign": "API-Test"}'

Get Status:

curl -X GET http://192.0.2.100:7777/api/status \
  -H "Authorization: Bearer changeme" \
  -H "x-session-id: dispatch_123_abc"

Trigger Broadcast:

curl -X POST http://192.0.2.100:7777/api/trigger-broadcast \
  -H "Authorization: Bearer changeme" \
  -H "x-session-id: dispatch_123_abc" \
  -H "Content-Type: application/json" \
  -d '{"message": "Test alert", "frequency": 460.125, "type": "Test"}'

Node.js Example

const axios = require('axios');
const io = require('socket.io-client');

const API_URL = 'http://192.0.2.100:7777';
const AUTH_TOKEN = 'changeme';
const NAC_ID = '141';

async function main() {
    // Authenticate
    const authResponse = await axios.post(`${API_URL}/radio/dispatch/auth`, {
        nacId: NAC_ID,
        callsign: 'API-Bot'
    });
    
    const { sessionId, authToken } = authResponse.data;
    
    // Setup headers
    const headers = {
        'Authorization': `Bearer ${authToken}`,
        'x-session-id': sessionId,
        'Content-Type': 'application/json'
    };
    
    // Connect Socket.IO
    const dispatchUserId = -(1000 + Math.floor(Math.random() * 10000));
    const socket = io(API_URL, {
        auth: { authToken: AUTH_TOKEN, serverId: dispatchUserId }
    });
    
    socket.on('connect', () => {
        console.log('Connected as dispatcher');
        socket.emit('setDispatchSession', sessionId);
        socket.emit('updateUserInfo', { name: 'API-Bot', nacId: 'API' });
    });
    
    // Get status
    const status = await axios.get(`${API_URL}/api/status`, { headers });
    console.log('Users online:', status.data.userCount);
    
    // Trigger broadcast
    await axios.post(`${API_URL}/api/trigger-broadcast`, {
        message: 'Test broadcast',
        frequency: 460.125,
        type: 'Test'
    }, { headers });
}

main();

Python Example

import requests
import socketio
import random

API_URL = 'http://192.0.2.100:7777'
AUTH_TOKEN = 'changeme'
NAC_ID = '141'

# Authenticate
auth_response = requests.post(f'{API_URL}/radio/dispatch/auth', json={
    'nacId': NAC_ID,
    'callsign': 'API-Bot'
})

session_id = auth_response.json()['sessionId']
auth_token = auth_response.json()['authToken']

# Setup headers
headers = {
    'Authorization': f'Bearer {auth_token}',
    'x-session-id': session_id,
    'Content-Type': 'application/json'
}

# Connect Socket.IO
dispatch_user_id = -(1000 + random.randint(0, 10000))
sio = socketio.Client()

@sio.on('connect')
def on_connect():
    print('Connected as dispatcher')
    sio.emit('setDispatchSession', session_id)
    sio.emit('updateUserInfo', {'name': 'API-Bot', 'nacId': 'API'})

sio.connect(API_URL, auth={'authToken': AUTH_TOKEN, 'serverId': dispatch_user_id})

# Get status
status = requests.get(f'{API_URL}/api/status', headers=headers)
print(f"Users online: {status.json()['userCount']}")

# Trigger broadcast
requests.post(f'{API_URL}/api/trigger-broadcast', headers=headers, json={
    'message': 'Test broadcast',
    'frequency': 460.125,
    'type': 'Test'
})

Error Responses

401 Unauthorized:

{
    "error": "Authentication required"
}

400 Bad Request:

{
    "error": "Message and frequency required"
}

404 Not Found:

{
    "error": "Channel not found"
}

500 Internal Server Error:

{
    "error": "Error message here"
}

API Notes

Important Information

  • Replace 192.0.2.100:7777 with your server's actual IP address and port
  • Replace changeme and 141 with your actual authToken and dispatchNacId values
  • All frequencies should be decimal numbers (e.g., 460.125)
  • Server IDs for dispatchers must be negative (e.g., -5000)
  • Player server IDs are positive integers (e.g., 1, 2, 3)
  • Session IDs expire after 24 hours
  • CORS is enabled for all origins by default
  • The API uses HTTP; consider using a reverse proxy with HTTPS for production

On this page