Hotspot Setup & Configuration

What is the role of the dvmhost?

  • Host software that connects to the DVM modems (both air interface for repeater and hotspot or P25 DFSI for commerical P25 hardware) and is the primary data processing application for digital modes. See configuration to configure and calibrate.

Prerequisites

  1. An Internet connect Raspberry Pi running Raspbian 12 (Bookworm) with a MMDVM hat flashed with the latest firmware from the DVMProject.
    1. This guide will assume it is a Raspberry Pi Model 3B+
    2. This guide will assume that there are no iptable rules or other firewalls currently installed within Raspbian 12.
  2. The dvmhost binary from the latest dvmhost release for your CPU architecture.
    1. At the time of writing, R04H31 2025-05-25 was the latest release.
      1. dvmhost-2025-05-25-arm.tar.gz

Connect to the Raspberry Pi & Download The dvmhost Release For Your CPU Architecture

  1. Download the archive from the DVMPRoject GitHub
ssh user@raspberrypi
cd /opt/
sudo wget https://github.com/DVMProject/dvmhost/releases/download/2025-05-25/dvmhost-2025-05-25-arm.tar.gz
  1. Extract & remove the archive
sudo tar zxvf dvmhost-2025-05-25-arm.tar.gz

# Optional step
sudo rm /opt/dvmhost-2025-05-25-arm.tar.gz

Organize the working directory

cd /opt/dvm/
sudo mkdir /opt/dvm/examples/
sudo mv /opt/dvm/*.example.* /opt/dvm/examples/

Copy the following example configurations as live versions.

sudo mkdir /opt/dvm/rules/
sudo cp /opt/dvm/examples/rid_acl.example.dat /opt/dvm/rules/rid_acl.dat
sudo cp /opt/dvm/examples/talkgroup_rules.example.yml /opt/dvm/rules/talkgroup_rules.yml
sudo cp /opt/dvm/examples/peer_list.example.dat /opt/dvm/rules/peer_list.dat

Review & Populate the iden_table.dat

The example iden_table.dat includes the UHF R2 range, if you are using frequencies that fall into this range, there is no additional calculation needed and you will use 2 as the channelId later in config.yml.

If you need to calculate a frequency pair, perform the following steps:

  1. Navigate to: https://dvmproject.io/iden-calc-web/
  2. Enter the appropriate values into each of the following fields:
    1. Downlink Frequency (MHz)
      1. This field represents the frequency on which the hotspot will receive transmissions.
    2. Base Frequency (MHz)
      1. This field defines a starting or reference frequency for a specific block or range of channels within the P25 system's frequency plan. It is used in a formula, along with the Channel ID and Spacing, to calculate the specific frequency for a given channel.
    3. Spacing (kHz)
      1. This field indicates the separation between the center frequencies of adjacent channels, often called the channel step or channel raster. It is typically measured in kilohertz (kHz) (e.g., 12.5 kHz, 6.25 kHz for P25). This value is crucial for calculating the exact frequency of a channel based on its ID and the base frequency.
    4. Offset (MHz)
      1. This field most commonly refers to the standard frequency difference between the uplink and downlink frequencies for a repeater channel pair in a specific band (e.g., +5 MHz or -5 MHz). It is measured in megahertz (MHz) and ensures that radios transmit on one frequency and receive on another, allowing for simultaneous transmission and reception through a repeater.
    5. Channel ID (dec)
      1. This field is a numerical identifier for a specific radio channel within the P25 system. This decimal value is typically used in a formula with the Base Frequency and Spacing to determine the precise operational frequency (often the downlink) for that channel.
    6. Uplink Frequency (MHz)
      1. Calculated automatically.

Copy the base configuration file to the working directory.

sudo cp /opt/dvm/examples/config.example.yml /opt/dvm/config.yml

Define the settings for P25 communication in config.yml

sudo nano /opt/dvm/config.yml

These dotted paths below represent nodes within the YAML syntax of fne-config.yml

  1. network.address: [ADDRESS-OF-FNE]
  2. network.id: [6-DIGIT-ID-OF-HOTSPOT]
  3. network.password: [PASSWORD-FROM-FNE-CONFIG.YML-ON-FNE]
  4. system.identity: MYHOTSPOT
  5. protocols.dmr.enable: false
  6. protocols.nxdn.enable: false
  7. system.modem.protocol.type: uart
  8. system.modem.protocol.mode: air
  9. system.modem.protocol.uart.port: /dev/ttyAMA0
  10. system.modem.protocol.uart.speed: 115200
  11. system.config.channelId: 2
    1. If you defined your own iden_table.dat use your associated channelId
  12. system.config.channelNo: [VALUE-CALCULATED-FROM-IDEN-CALC-WEB]
  13. system.config.voiceChNo.channelId: 2
    1. If you defined your own iden_table.dat use your associated channelId
  14. system.config.voiceChNo.channelNo: [VALUE-CALCULATED-FROM-IDEN-CALC-WEB]
  15. systtem.config.iden_table.file: /opt/dvm/rules/iden_table.dat
  16. system.config.radio_acl.file: /opt/dvm/rules/radio_acl.dat
  17. system.config.talkgroup_id.file: /opt/dvm/rules/talkgroup_rules.yml

Optional: Enable ACLs based on radio ID or talkgroup ID

sudo nano /opt/dvm/config.yml
  1. Radio ID
    1. system.config.radio_id.acl: true
  2. Talkgroup ID
    1. system.config.talkgroup_id.acl: true

Verify settings in the TUI

Note: You can use your mouse in the TUI to click through the menu's and visually confirm the settings set in config.yml

sudo /opt/dvm/bin/dvmhost -c /opt/dvm/config.yml --setup
Connect to the modem
  1. Press F8
    1. The modem should initialize and connect successfully.
    2. This is where alignment would take place if necessary. Alignment is generally needed if the Hotspot does not activate when receiving a transmission from a subscriber radio. At this time this guide will not cover offsets or alignments on the hotspot.
  2. Press F2
    1. This will save your current settings and "flatten" your config.yml file removing any comments.
  3. Press F3
    1. This will take you back to the console.

Example: Finalized Config.yml

daemon: true
log:
    activityFilePath: .
    disableNonAuthoritiveLogging: false
    displayLevel: 1
    fileLevel: 1
    filePath: /opt/dvm/log
    fileRoot: DVM
    useSyslog: false
network:
    address: [REDACTED]
    allowActivityTransfer: true
    allowDiagnosticTransfer: true
    allowStatusTransfer: true
    debug: false
    enable: true
    encrypted: false
    id: 9039148
    jitter: 360
    password: [REDACTED]
    port: 62031
    presharedKey: [REDACTED]
    restAddress: 127.0.0.1
    restDebug: false
    restEnable: false
    restPassword: PASSWORD
    restPort: 9990
    restSsl: false
    restSslCertificate: web.crt
    restSslKey: web.key
    rpcAddress: 127.0.0.1
    rpcDebug: false
    rpcPassword: "ULTRA-VERY-SECURE-DEFAULT"
    rpcPort: 9890
    saveLookups: false
    slot1: true
    slot2: true
    updateLookups: false
protocols:
    dmr:
        backOff: 1
        beacons:
            duration: 3
            enable: false
            interval: 60
        callHang: 5
        control:
            dedicated: false
            disableGrantSourceIdCheck: false
            enable: false
            slot: 1
        convNetGrantDemand: false
        debug: false
        disableNetworkGrant: false
        disableUnitRegTimeout: false
        dumpCsbkData: false
        dumpDataPacket: false
        dumpTAData: true
        embeddedLCOnly: false
        enable: false
        frameLossThreshold: 2
        ignoreAffiliationCheck: false
        nRandWait: 8
        queueSize: 31
        repeatDataPacket: true
        silenceThreshold: 21
        txHang: 8
        verbose: true
        verifyReg: false
    nxdn:
        callHang: 5
        control:
            broadcast: true
            dedicated: false
            disableGrantSourceIdCheck: false
            duration: 1
            enable: false
            interval: 300
        debug: false
        disableUnitRegTimeout: false
        dumpRcchData: false
        enable: false
        frameLossThreshold: 4
        ignoreAffiliationCheck: false
        queueSize: 31
        silenceThreshold: 14
        verbose: true
        verifyAff: false
        verifyReg: false
    p25:
        allowExplicitSourceId: true
        callHang: 5
        control:
            ackRequests: true
            broadcast: true
            dedicated: false
            disableAdjSiteBroadcast: false
            disableGrantSourceIdCheck: false
            disableTSDUMBF: false
            duration: 1
            enable: false
            enableTimeDateAnn: false
            interval: 300
            notifyActiveTG: false
            redundantGrantTransmit: false
            redundantImmediate: true
        controlOnly: false
        convNetGrantDemand: false
        debug: false
        demandUnitRegForRefusedAff: true
        disableNetworkGrant: false
        disableNetworkHDU: false
        disableUnitRegTimeout: false
        dumpDataPacket: false
        dumpTsbkData: false
        enable: true
        frameLossThreshold: 6
        ignoreAffiliationCheck: false
        inhibitUnauthorized: false
        legacyGroupGrnt: true
        legacyGroupReg: false
        noMessageAck: true
        noStatusAck: false
        queueSize: 12
        repeatDataPacket: true
        requireLLAForReg: false
        silenceThreshold: 124
        sndcpSupport: false
        tduPreambleCount: 6
        unitToUnitAvailCheck: false
        verbose: true
        verifyAff: false
        verifyReg: false
system:
    activeTickDelay: 5
    config:
        announcementGroup: FFFE
        authoritative: true
        channelId: 2
        channelNo: 8c0
        colorCode: 1
        controlCh:
            notifyEnable: true
            presence: 120
            rpcAddress: 127.0.0.1
            rpcPassword: "ULTRA-VERY-SECURE-DEFAULT"
            rpcPort: 0
        dmrNetId: 1
        nac: 293
        netId: BB800
        pSuperGroup: FFFE
        ran: 1
        rfssId: 1
        secure:
            key: 000102030405060708090A0B0C0D0E0F
        siteId: 1
        supervisor: false
        sysId: 001
        voiceChNo:
            - channelId: 2
              channelNo: 8c0
              rpcAddress: 127.0.0.1
              rpcPassword: "ULTRA-VERY-SECURE-DEFAULT"
              rpcPort: 9890
    cwId:
        callsign: ABCD123
        enable: true
        time: 15
    disableWatchdogOverflow: false
    duplex: true
    fixedMode: false
    iden_table:
        file: /opt/dvm/rules/iden_table.dat
        time: 30
    identity: W25HS02
    idleTickDelay: 5
    info:
        height: 1
        latitude: "-83.689428"
        location: "Repeater Site, Antarctica"
        longitude: "-39.194973"
        power: 10
    localTimeOffset: 0
    modeHang: 10
    modem:
        cosLockout: false
        dcBlocker: true
        debug: false
        dfsi:
            callTimeout: 200
            dfsiTIAMode: false
            diu: true
            fsc: false
            fscHeartbeat: 5
            initiator: false
            jitter: 200
            rtrt: true
        disableOFlowReset: false
        dmrFifoLength: 560
        dmrRxDelay: 7
        dumpModemStatus: false
        fdmaPreamble: 80
        hotspot:
            adfGainMode: 2
            afcEnable: true
            afcKI: 13
            afcKP: 5
            afcRange: 1
            dmrDiscBWAdj: 0
            dmrPostBWAdj: 0
            nxdnDiscBWAdj: 0
            nxdnPostBWAdj: 0
            p25DiscBWAdj: 0
            p25PostBWAdj: 0
            rxTuning: 0
            txTuning: 0
        ignoreModemConfigArea: false
        nxdnFifoLength: 538
        nxdnFifoLenth: 538
        p25CorrCount: 8
        p25FifoLength: 522
        packetPlayoutTime: 10
        protocol:
            mode: air
            type: uart
            uart:
                port: /dev/ttyAMA0
                speed: 115200
        pttInvert: false
        repeater:
            dmrSymLvl1Adj: 0
            dmrSymLvl3Adj: 0
            nxdnSymLvl1Adj: 0
            nxdnSymLvl3Adj: 0
            p25SymLvl1Adj: 0
            p25SymLvl3Adj: 0
        rssiMappingFile: RSSI.dat
        rxDCOffset: 0
        rxInvert: false
        rxLevel: 50
        softpot:
            rssiCoarse: 127
            rssiFine: 127
            rxCoarse: 127
            rxFine: 127
            txCoarse: 127
            txFine: 127
        trace: false
        txDCOffset: 0
        txInvert: false
        txLevel: 50
    radio_id:
        acl: false
        file: /opt/dvm/rules/rid_acl.dat
        time: 2
    rfTalkgroupHang: 10
    simplexSameFrequency: false
    talkgroup_id:
        acl: false
        file: /opt/dvm/rules/talkgroup_rules.yml
        time: 2
    timeout: 180

Example: Finalized iden_table.dat

#
# This file sets the valid bandplan identity table.
# (It is recommended to use the included iden_channel_calc.py Python script
# to generate entries for this file.)
#
# ChId,Base Freq,Spacing (khz),Input Offset (mhz),Bandwidth (khz),<newline>
#0,851006250,6.25,-45.000,12.5,
#1,762006250,6.25,30.000,12.5,
#15,935001250,6.25,-39.00000,12.5,
2,450000000,6.25,5.000,12.5,
#3,146000000,6.25,1.000,12.5,

Start the dvmhost process as a daemon.

/opt/dvm/start-dvm.sh /opt/dvm/config.yml

If the start is successful, you should see an entry as denoted in Log Entry Examples subsection, Peer Connecting.

Configure dvmhost to start as a system service.

sudo nano /etc/systemd/system/dvmhost.service
dvmhost.service Example
[Unit]
Description=dvmhost
After=network.target

[Service]
ExecStart=/opt/dvm/bin/dvmhost -c /opt/dvm/config.yml -f
User=root
Type=forking
Restart=on-abnormal
TimeoutSec=infinity

[Install]
WantedBy=multi-user.target
Enable and start the dvmhost service
sudo systemctl daemon-reload
sudo systemctl enable dvmhost.service
sudo systemctl start dvmhost.service