Skip to content

Instantly share code, notes, and snippets.

@wd5gnr
Created February 11, 2025 15:34
Show Gist options
  • Select an option

  • Save wd5gnr/62bce94b7097414d094d1e81bf46cfe2 to your computer and use it in GitHub Desktop.

Select an option

Save wd5gnr/62bce94b7097414d094d1e81bf46cfe2 to your computer and use it in GitHub Desktop.

Revisions

  1. wd5gnr created this gist Feb 11, 2025.
    26 changes: 26 additions & 0 deletions Notes.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,26 @@
    You need to do the following:

    1. Copy pistar-remote.wd5gnr to /usr/local/sbin/pistar-remote.wd5gnr AND /usr/local/sbin/pistar-remote.wd5gnr.backup (important!)
    1. Copy the files (remote-user-hook-example0, bm_tg.py, gnrcheck) for /usr/local/bin to your /usr/local/bin directory
    1. Edit (don't copy) your /etc/pistar-remote to look like the one below
    1. Copy and Edit /home/pi-star/user-hook-data to fit your needs
    1. Make all the files except user-hook-data executable (e.g., chmod +x gnrcheck)

    # Commands

    Assuming you set the prefix to 77:

    * 7700002 - Deletes static talkgroup #2 (slot 1; as defined in user-hook-data
    * 7710002 - Adds static talkgroup #2 (slot 1)
    * 7720003 - Disables DMR network #3
    * 7730003 - Enables DMR network #3

    # Reinstalling


    Every time WPSD updates, this script will end. Run gnrcheck to fix things. It will tell you if you need to fix it and then will show you the instructions
    to fix it. However, if you run `gnrcheck fix` it will try to fix things for you as long as the .wd5gnr and the .wd5gnr.backup files match.

    # TODO

    Right now, this only works for DMR but it is easy to add the other modes which I will be doing shortly.
    31 changes: 31 additions & 0 deletions gnrcheck
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,31 @@
    # This wouldn't exist if/when this feature is pulled into mainstream
    #!/bin/bash
    DIR=/usr/local/sbin
    FBASE=pistar-remote
    echo Checking gnrcmd setup
    if diff "$DIR/$FBASE" "$DIR/$FBASE.wd5gnr" >/dev/null
    then
    echo GNR is present
    exit 0
    fi
    echo GNR not present
    if diff "$DIR/$FBASE" "$DIR/$FBASE.original" >/dev/null
    then
    echo No changes detected on original
    if ! diff "$DIR/$FBASE.wd5gnr" "$DIR/$FBASE.wd5gnr.backup" >/dev/null
    then
    echo Warning: Backup is not the same!
    echo Resolve this before relinking
    exit 1
    fi
    if [ "$1" == "fix" ]
    then
    echo Fixing
    ln -sf "$DIR/$FBASE.wd5gnr" "$DIR/$FBASE"
    sudo systemctl restart pistar-remote
    exit 0
    fi
    echo issue: ln -sf \"$DIR/$FBASE.wd5gnr\" \"$DIR/$FBASE\"
    echo then: systemctl restart pistar-remote
    fi
    exit 1
    56 changes: 56 additions & 0 deletions pistar-remote
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,56 @@
    # NOTE: You only need to change what's in [enable] to suit your purpose
    [banner]
    # Pi-Star Remote config file
    # This config file is desiged for the Pi-Star Keeper remote control
    # The remote control system is designed to give repeater keepers an
    # RF KillSwitch for their repeaters.

    [enable]
    # Is the Pi-Star Remote Enabled? (true|false)
    enabled=true
    local_prefix=77
    local_script=/usr/local/bin/remote-user-hook-example0
    scan_rate=15 # scan_rate is how many seconds between looking for commands (default was 30)

    [keeper]
    # Keepers Information
    callsign=WD5GNR


    [d-star]
    # UR fields
    #svckill=SVCKILL
    #svcrestart=SVCRSTRT
    #reboot=REBOOTPI
    #shutdown=SHUTDOWN
    #8Ball=8BALL

    [dmr]
    # TG commands
    svckill=7999999
    svcrestart=7999998
    reboot=7999997
    shutdown=7999996
    gnrcmd=77

    [ysf]
    # ROOM commands
    #svckill=99999
    #svcrestart=99998
    #reboot=99997
    #shutdown=99996

    [p25]
    # P25 Talkgroups are limited to 1->65535
    #svckill=65531
    #svcrestart=65532
    #reboot=65533
    #shutdown=65534

    [m17]
    # M17 destination fields
    #svckill=SVCKILL
    #svcrestart=SVCRSTRT
    #reboot=REBOOTPI
    #shutdown=SHUTDOWN

    515 changes: 515 additions & 0 deletions pistar-remote.wd5gnr
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,515 @@
    #!/usr/bin/python

    ###########################################################################
    # #
    # WPSD RF Remote Control #
    # #
    # Refactored for Py3 - W0CHP #
    # #
    ###########################################################################
    # WD5GNR version
    import datetime
    import time
    import linecache
    import os
    import subprocess
    import configparser
    import random
    import re

    # Read the config;
    config = configparser.RawConfigParser()
    config.read('/etc/pistar-remote')

    # Read the MMDVMHost config;
    mmdvmConfig = configparser.RawConfigParser()
    mmdvmConfig.read('/etc/mmdvmhost')

    # Read the YSFGateway config;
    ysfGatewayConfig = configparser.RawConfigParser()
    ysfGatewayConfig.read('/etc/ysfgateway')

    # If not enabled, die;
    isEnabled = config.get('enable', 'enabled')
    if (isEnabled != 'true'):
    quit()

    # Substitute variables from config
    mmdvmLogPath = mmdvmConfig.get('Log', 'FilePath')
    mmdvmFileRoot = mmdvmConfig.get('Log', 'FileRoot')
    ysfGatewayLogPath = ysfGatewayConfig.get('Log', 'FilePath')
    ysfGatewayFileRoot = ysfGatewayConfig.get('Log', 'FileRoot')
    keeperCall = config.get('keeper', 'callsign')
    local_prefix=config.get('enable','local_prefix',fallback='999999999999')
    local_script=config.get('enable','local_script',fallback='')
    scan_rate=config.getint('enable','scan_rate',fallback=30)

    # DMR Control Options
    if config.has_option('dmr', 'svckill'):
    dmrstop = config.get('dmr', 'svckill')
    else:
    dmrstop = str(999999999999)
    if config.has_option('dmr', 'svcrestart'):
    dmrrestart = config.get('dmr', 'svcrestart')
    else:
    dmrrestart = str(999999999999)
    if config.has_option('dmr', 'reboot'):
    dmrreboot = config.get('dmr', 'reboot')
    else:
    dmrreboot = str(999999999999)
    if config.has_option('dmr', 'shutdown'):
    dmrshutdown = config.get('dmr', 'shutdown')
    else:
    dmrshutdown = str(999999999999)
    if config.has_option('dmr', 'hostfiles'):
    dmrhostfiles = config.get('dmr', 'hostfiles')
    else:
    dmrhostfiles = str(999999999999)
    if config.has_option('dmr', 'reconnect'):
    dmrreconnect = config.get('dmr', 'reconnect')
    else:
    dmrreconnect = str(999999999999)

    # D-Star Control Options
    if config.has_option('d-star', 'svckill'):
    dstarstop = config.get('d-star', 'svckill')
    else:
    dstarstop = str(999999999999)
    if config.has_option('d-star', 'svcrestart'):
    dstarrestart = config.get('d-star', 'svcrestart')
    else:
    dstarrestart = str(999999999999)
    if config.has_option('d-star', 'reboot'):
    dstarreboot = config.get('d-star', 'reboot')
    else:
    dstarreboot = str(999999999999)
    if config.has_option('d-star', 'shutdown'):
    dstarshutdown = config.get('d-star', 'shutdown')
    else:
    dstarshutdown = str(999999999999)
    if config.has_option('d-star', 'hostfiles'):
    dstarhostfiles = config.get('d-star', 'hostfiles')
    else:
    dstarhostfiles = str(999999999999)
    if config.has_option('d-star', 'getip'):
    dstargetip = config.get('d-star', 'getip')
    else:
    dstargetip = str(999999999999)
    if config.has_option('d-star', 'wifissid'):
    dstargetwifissid = config.get('d-star', 'wifissid')
    else:
    dstargetwifissid = str(999999999999)
    if config.has_option('d-star', 'wifirssi'):
    dstargetwifirssi = config.get('d-star', 'wifirssi')
    else:
    dstargetwifirssi = str(999999999999)
    if config.has_option('d-star', '8Ball'):
    dstar8ball = config.get('d-star', '8Ball')
    else:
    dstar8ball = str(999999999999)
    dstarmodule = mmdvmConfig.get('General', 'Callsign').ljust(
    7) + mmdvmConfig.get('D-Star', 'Module')

    # YSF Control Options
    if config.has_option('ysf', 'svckill'):
    ysfstop = config.get('ysf', 'svckill')
    else:
    ysfstop = str(999999999999)
    if config.has_option('ysf', 'svcrestart'):
    ysfrestart = config.get('ysf', 'svcrestart')
    else:
    ysfrestart = str(999999999999)
    if config.has_option('ysf', 'reboot'):
    ysfreboot = config.get('ysf', 'reboot')
    else:
    ysfreboot = str(999999999999)
    if config.has_option('ysf', 'shutdown'):
    ysfshutdown = config.get('ysf', 'shutdown')
    else:
    ysfshutdown = str(999999999999)
    if config.has_option('ysf', 'hostfiles'):
    ysfhostfiles = config.get('ysf', 'hostfiles')
    else:
    ysfhostfiles = str(999999999999)

    # P25 Control Options
    if config.has_option('p25', 'svckill'):
    p25stop = config.get('p25', 'svckill')
    else:
    p25stop = str(999999999999)
    if config.has_option('p25', 'svcrestart'):
    p25restart = config.get('p25', 'svcrestart')
    else:
    p25restart = str(999999999999)
    if config.has_option('p25', 'reboot'):
    p25reboot = config.get('p25', 'reboot')
    else:
    p25reboot = str(999999999999)
    if config.has_option('p25', 'shutdown'):
    p25shutdown = config.get('p25', 'shutdown')
    else:
    p25shutdown = str(999999999999)
    if config.has_option('p25', 'hostfiles'):
    p25hostfiles = config.get('p25', 'hostfiles')
    else:
    p25hostfiles = str(999999999999)

    # M17 Control Options
    if config.has_option('m17', 'svckill'):
    m17stop = config.get('m17', 'svckill')
    else:
    m17stop = str(999999999999)
    if config.has_option('m17', 'svcrestart'):
    m17restart = config.get('m17', 'svcrestart')
    else:
    m17restart = str(999999999999)
    if config.has_option('m17', 'reboot'):
    m17reboot = config.get('m17', 'reboot')
    else:
    m17reboot = str(999999999999)
    if config.has_option('m17', 'shutdown'):
    m17shutdown = config.get('m17', 'shutdown')
    else:
    m17shutdown = str(999999999999)
    if config.has_option('m17', 'hostfiles'):
    m17hostfiles = config.get('m17', 'hostfiles')
    else:
    m17hostfiles = str(999999999999)

    # 8-Ball answers
    magic8ball = [
    'It is certain',
    'It is decidedly so',
    'Without a doubt',
    'Yes definitely',
    'You may rely on it',
    'As I see it, yes',
    'Most likely',
    'Outlook good',
    'Yes',
    'Signs point to yes',
    'Reply hazy try agn',
    'Ask again later',
    'Tell you later',
    'Cannot predict now',
    'Concentrate, ask agn',
    'Dont count on it',
    'My reply is no',
    'My sources say no',
    'Outlook not so good',
    'Very doubtful'
    ]

    # Some Variables that are important later
    txtTransmitOldBin = '/usr/local/bin/texttransmit'
    txtTransmitNewBin = '/usr/local/bin/texttransmitd'
    if os.path.isfile(txtTransmitOldBin):
    txtTransmitBin = '/usr/local/bin/texttransmit'
    if os.path.isfile(txtTransmitNewBin):
    txtTransmitBin = '/usr/local/bin/texttransmitd'

    # Now run the loop
    while True:
    # Check that the process is running, if its not there is no point in trying to stop it.
    checkproc = subprocess.Popen(
    'pgrep' + ' MMDVMHost', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    if checkproc.stdout.readlines():

    # This is the main loop that keeps waiting, we dont want to hammer the logs too often, every 30 secs should be enough.
    utcnow = datetime.datetime.utcnow()
    datenow = utcnow.strftime('%Y-%m-%d')
    dateminus60sec = datetime.datetime.utcnow() - datetime.timedelta(seconds=scan_rate)
    logstampnow = utcnow.strftime('%Y-%m-%d %H:%M:%S')
    logstampnowminus60sec = dateminus60sec.strftime('%Y-%m-%d %H:%M:%S')
    currentLog = mmdvmLogPath + '/' + mmdvmFileRoot + '-' + datenow + '.log'
    currentLogYSF = ysfGatewayLogPath + '/' + \
    ysfGatewayFileRoot + '-' + datenow + '.log'

    loglist = []
    # Open the MMDVMHost Log
    if os.path.isfile(currentLog):
    logfile = open(currentLog, 'r')
    loglist = logfile.readlines()
    logfile.close()

    # Parse the log lines
    for line in loglist:
    # We only care about logs in the last 60 secs
    if line[3:22] >= logstampnowminus60sec and line[3:22] <= logstampnow:
    dmrlocalfound= re.search('received RF voice header from ' + keeperCall + ' to ' + local_prefix + '([0-9])([0-9][0-9][0-9][0-9])',line)
    if dmrlocalfound:
    localverb=dmrlocalfound.group(1)
    localid=dmrlocalfound.group(2)
    os.system(f'{local_script} dmr "{localverb}" "{localid}"')

    # DMR Stop MMDVMHost
    # M: 2017-07-03 09:39:38.208 DMR Slot 2, received RF voice header from M1ABC to 123456
    if str('received RF voice header from ' + keeperCall + ' to ' + dmrstop) in line:
    # Kill the Services
    os.system(r'systemctl stop mmdvmhost.timer')
    os.system(r'systemctl stop pistar-watchdog.timer')
    os.system(r'systemctl stop mmdvmhost.service')
    os.system(r'systemctl stop pistar-watchdog.service')

    # DMR Restart MMDVMHost
    # M: 2017-07-03 09:39:38.208 DMR Slot 2, received RF voice header from M1ABC to 123456
    if str('received RF voice header from ' + keeperCall + ' to ' + dmrrestart) in line:
    # Restart the Services
    os.system(r'systemctl restart mmdvmhost.service')
    os.system(r'systemctl restart dmrgateway.service')
    time.sleep(30)

    # DMR Restart the OS
    # M: 2017-07-03 09:39:38.208 DMR Slot 2, received RF voice header from M1ABC to 123456
    if str('received RF voice header from ' + keeperCall + ' to ' + dmrreboot) in line:
    # Restart the OS
    os.system(r'shutdown -r now')

    # DMR Shutdown the OS
    # M: 2017-07-03 09:39:38.208 DMR Slot 2, received RF voice header from M1ABC to 123456
    if str('received RF voice header from ' + keeperCall + ' to ' + dmrshutdown) in line:
    # Shutdown the OS
    os.system(r'shutdown -h now')

    # DMR Update Hostfiles
    # M: 2017-07-03 09:39:38.208 DMR Slot 2, received RF voice header from M1ABC to 123456
    if str('received RF voice header from ' + keeperCall + ' to ' + dmrhostfiles) in line:
    # Update Host files
    os.system(r'/usr/local/sbin/wpsd-hostfile-update')

    # DMR Reconnect WIFI
    # M: 2017-07-03 09:39:38.208 DMR Slot 2, received RF voice header from M1ABC to 123456
    if str('received RF voice header from ' + keeperCall + ' to ' + dmrreconnect) in line:
    # trigger reconnect
    os.system(
    r'logger -t "[$$]" "Pi-Star --> Wifi Reconnect initiated <--"')
    os.system(r'wpa_cli reconfigure wlan0')
    os.system(r'ifdown wlan0 && sleep 3')
    os.system(r'ifup wlan0')
    os.system(r'wpa_cli scan')

    # P25 Stop MMDVMHost
    # M: 2017-08-08 08:02:43.352 P25, received RF transmission from M1ABC to TG 123456
    if str('P25, received RF transmission from ' + keeperCall + ' to TG ' + p25stop) in line:
    # Kill the Services
    os.system(r'systemctl stop mmdvmhost.timer')
    os.system(r'systemctl stop pistar-watchdog.timer')
    os.system(r'systemctl stop mmdvmhost.service')
    os.system(r'systemctl stop pistar-watchdog.service')

    # P25 Restart MMDVMHost
    # M: 2017-08-08 08:02:43.352 P25, received RF transmission from M1ABC to TG 123456
    if str('P25, received RF transmission from ' + keeperCall + ' to TG ' + p25restart) in line:
    # Restart the Services
    os.system(r'systemctl restart mmdvmhost.service')
    os.system(r'systemctl restart dmrgateway.service')
    time.sleep(30)

    # P25 Restart the OS
    # M: 2017-08-08 08:02:43.352 P25, received RF transmission from M1ABC to TG 123456
    if str('P25, received RF transmission from ' + keeperCall + ' to TG ' + p25reboot) in line:
    # Restart the OS
    os.system(r'shutdown -r now')

    # P25 Shutdown the OS
    # M: 2017-08-08 08:02:43.352 P25, received RF transmission from M1ABC to TG 123456
    if str('P25, received RF transmission from ' + keeperCall + ' to TG ' + p25shutdown) in line:
    # Shutdown the OS
    os.system(r'shutdown -h now')

    # P25 Update Hostfiles
    # M: 2017-08-08 08:02:43.352 P25, received RF transmission from M1ABC to TG 123456
    if str('P25, received RF transmission from ' + keeperCall + ' to TG ' + p25hostfiles) in line:
    # Update Host Files
    os.system(r'/usr/local/sbin/wpsd-hostfile-update')

    # D-Star Stop MMDVMHost
    # M: 2017-07-03 11:38:57.349 D-Star, received RF header from M1ABC /1234 to COMMAND1
    if str('D-Star, received RF header from ' + keeperCall + ' ') and str(dstarstop) in line:
    # Kill the Services
    os.system(
    txtTransmitBin + ' -text "Shutting down at keeper request" "' + dstarmodule + '"')
    time.sleep(5)
    os.system(r'systemctl stop mmdvmhost.timer')
    os.system(r'systemctl stop pistar-watchdog.timer')
    os.system(r'systemctl stop mmdvmhost.service')
    os.system(r'systemctl stop pistar-watchdog.service')

    # D-Star Restart MMDVMHost
    # M: 2017-07-03 11:38:57.349 D-Star, received RF header from M1ABC /1234 to COMMAND1
    if str('D-Star, received RF header from ' + keeperCall + ' ') and str(dstarrestart) in line:
    # Restart the Services
    os.system(
    txtTransmitBin + ' -text "Restarting services at keeper request" "' + dstarmodule + '"')
    time.sleep(5)
    os.system(r'systemctl restart ircddbgateway.service')
    os.system(r'systemctl restart mmdvmhost.service')
    time.sleep(30)

    # D-Star Reboot the OS
    # M: 2017-07-03 11:38:57.349 D-Star, received RF header from M1ABC /1234 to COMMAND1
    if str('D-Star, received RF header from ' + keeperCall + ' ') and str(dstarreboot) in line:
    # Kill the Services
    os.system(
    txtTransmitBin + ' -text "Rebooting OS at keeper request" "' + dstarmodule + '"')
    time.sleep(5)
    os.system(r'shutdown -r now')

    # D-Star Shutdown the OS
    # M: 2017-07-03 11:38:57.349 D-Star, received RF header from M1ABC /1234 to COMMAND1
    if str('D-Star, received RF header from ' + keeperCall + ' ') and str(dstarshutdown) in line:
    # Shutdown
    os.system(
    txtTransmitBin + ' -text "Shutting down OS at keeper request" "' + dstarmodule + '"')
    time.sleep(5)
    os.system(r'shutdown -h now')

    # D-Star Hostfiles Update
    # M: 2017-07-03 11:38:57.349 D-Star, received RF header from M1ABC /1234 to COMMAND1
    if str('D-Star, received RF header from ' + keeperCall + ' ') and str(dstarhostfiles) in line:
    # Update Host files
    os.system(
    txtTransmitBin + ' -text "Updating Hostfiles at keeper request" "' + dstarmodule + '"')
    time.sleep(5)
    os.system(r'/usr/local/sbin/wpsd-hostfile-update')

    # D-Star Get the current IP
    # M: 2017-07-03 11:38:57.349 D-Star, received RF header from M1ABC /1234 to COMMAND1
    if str('D-Star, received RF header from ' + keeperCall + ' ') and str(dstargetip) in line:
    # Get the IP
    myIP = os.popen(
    '/bin/hostname -I | awk \'{print $1}\'').read()
    os.system(txtTransmitBin + ' -text "IP: ' +
    myIP + '" "' + dstarmodule + '"')

    # D-Star Get the wifi SSID
    # M: 2017-07-03 11:38:57.349 D-Star, received RF header from M1ABC /1234 to COMMAND1
    if str('D-Star, received RF header from ' + keeperCall + ' ') and str(dstargetwifissid) in line:
    # Get the SSID
    if os.path.islink('/sys/class/net/wlan0'):
    ssid = os.popen(
    '/sbin/iwconfig wlan0 | grep ESSID | awk -F ":" \'{print $2}\' | sed \'s/"//g\'').read()
    os.system(txtTransmitBin + ' -text "SSID: ' +
    ssid + '" "' + dstarmodule + '"')
    else:
    os.system(
    txtTransmitBin + ' -text "WiFi: Not connected" "' + dstarmodule + '"')

    # D-Star Get the wifi RSSI
    # M: 2017-07-03 11:38:57.349 D-Star, received RF header from M1ABC /1234 to COMMAND1
    if str('D-Star, received RF header from ' + keeperCall + ' ') and str(dstargetwifirssi) in line:
    # Get the SSID
    if os.path.islink('/sys/class/net/wlan0'):
    rssi = os.popen(
    '/sbin/iwconfig wlan0 | grep -i "signal level" | awk -F "=" \'{print $3}\'').read()
    os.system(txtTransmitBin + ' -text "RSSI: ' +
    rssi + '" "' + dstarmodule + '"')
    else:
    os.system(
    txtTransmitBin + ' -text "WiFi: Not connected" "' + dstarmodule + '"')

    # D-Star 8Ball
    # M: 2017-07-03 11:38:57.349 D-Star, received RF header from M1ABC /1234 to COMMAND1
    if str('D-Star, received RF header from ' + keeperCall + ' ') and str(dstar8ball) in line:
    # Ask the 8Ball
    magic8ballanswer = random.choice(magic8ball)
    os.system(txtTransmitBin + ' -text "' +
    magic8ballanswer + '" "' + dstarmodule + '"')

    # YSF
    # M: 2017-07-13 19:50:43.464 YSF, received RF header from M1ABC to ALL
    if str('YSF, received RF header from ' + keeperCall + ' ') in line:
    # YSF Log Checking here
    logfileysf = open(currentLogYSF, 'r')
    loglistysf = logfileysf.readlines()
    logfileysf.close()

    checkproc = subprocess.Popen(
    'pgrep' + ' YSFGateway', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    if checkproc.stdout.readlines():

    # Parse the log lines
    for lineysf in loglistysf:

    # We only care about logs in the last 60 secs
    if lineysf[3:22] >= logstampnowminus60sec and lineysf[3:22] <= logstampnow:

    # YSF Stop MMDVMHost
    # M: 2017-07-13 19:24:59.704 Trying to find non existent reflector with an id of 12345
    if (str('Trying to find non existent') and str(ysfstop) in lineysf) or (str('Received Connect to') and str(ysfstop) in lineysf):
    # Kill the Services
    os.system(
    r'systemctl stop mmdvmhost.timer')
    os.system(
    r'systemctl stop pistar-watchdog.timer')
    os.system(
    r'systemctl stop mmdvmhost.service')
    os.system(
    r'systemctl stop pistar-watchdog.service')

    # YSF Restart MMDVMHost
    # M: 2017-07-13 19:24:59.704 Trying to find non existent reflector with an id of 12345
    if (str('Trying to find non existent') and str(ysfrestart) in lineysf) or (str('Received Connect to') and str(ysfrestart) in lineysf):
    # Restart the Services
    os.system(
    r'systemctl restart ysfgateway.service')
    os.system(
    r'systemctl restart mmdvmhost.service')
    time.sleep(30)

    # YSF Reboot the OS
    # M: 2017-07-13 19:24:59.704 Trying to find non existent reflector with an id of 12345
    if (str('Trying to find non existent') and str(ysfreboot) in lineysf) or (str('Received Connect to') and str(ysfreboot) in lineysf):
    # Reboot the OS
    os.system(r'shutdown -r now')

    # YSF Shutdown the OS
    # M: 2017-07-13 19:24:59.704 Trying to find non existent reflector with an id of 12345
    if (str('Trying to find non existent') and str(ysfshutdown) in lineysf) or (str('Received Connect to') and str(ysfshutdown) in lineysf):
    # Shutdown the OS
    os.system(r'shutdown -h now')

    # YSF Update the Hostfiles
    # M: 2017-07-13 19:24:59.704 Trying to find non existent reflector with an id of 12345
    if (str('Trying to find non existent') and str(ysfhostfiles) in lineysf) or (str('Received Connect to') and str(ysfhostfiles) in lineysf):
    # Update the host files
    os.system(
    r'/usr/local/sbin/wpsd-hostfile-update')

    # M17 Stop MMDVMHost
    # M: 2025-01-25 15:51:20.147 M17, received RF voice transmission from N1ADJ to SVCKILL
    if str('M17, received RF voice transmission from ' + keeperCall + ' to ' + m17stop) in line:
    # Kill the Services
    os.system(r'systemctl stop mmdvmhost.timer')
    os.system(r'systemctl stop pistar-watchdog.timer')
    os.system(r'systemctl stop mmdvmhost.service')
    os.system(r'systemctl stop pistar-watchdog.service')

    # M17 Restart MMDVMHost
    # M: 2025-01-25 15:51:20.147 M17, received RF voice transmission from N1ADJ to SVCKILL
    if str('M17, received RF voice transmission from ' + keeperCall + ' to ' + m17restart) in line:
    # Restart the Services
    os.system(r'systemctl restart mmdvmhost.service')
    os.system(r'systemctl restart m17gateway.service')
    time.sleep(30)

    # M17 Restart the OS
    # M: 2025-01-25 15:51:20.147 M17, received RF voice transmission from N1ADJ to SVCKILL
    if str('M17, received RF voice transmission from ' + keeperCall + ' to ' + m17reboot) in line:
    # Restart the OS
    os.system(r'shutdown -r now')

    # M17 Shutdown the OS
    # M: 2025-01-25 15:51:20.147 M17, received RF voice transmission from N1ADJ to SVCKILL
    if str('M17, received RF voice transmission from ' + keeperCall + ' to ' + m17shutdown) in line:
    # Shutdown the OS
    os.system(r'shutdown -h now')

    # M17 Update Hostfiles
    # M: 2025-01-25 15:51:20.147 M17, received RF voice transmission from N1ADJ to SVCKILL
    if str('M17, received RF voice transmission from ' + keeperCall + ' to ' + m17hostfiles) in line:
    # Update Host Files
    os.system(r'/usr/local/sbin/wpsd-hostfile-update')

    # This is the 30 second sleep before the next pass.
    time.sleep(scan_rate)
    59 changes: 59 additions & 0 deletions remote-user-hook-example0
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,59 @@
    #!/bin/bash
    # example remote user hook de WD5GNR
    echo Loading user data
    . /home/pi-star/user-hook-data

    # Here is an example of the user-hook-data file
    #
    #
    #DMRID=XXXXXXXX
    #TG0001=3001
    #TG0002=312681
    #TG9999=93


    # ignore everything but dmr
    if [ "$1" != "dmr" ]
    then
    exit 0
    fi

    # we can pull your BMAPI key
    bmkey=$(grep apikey= /etc/bmapi.key | cut -d = -f 2)

    # read the verb (0-9) and the noun/argument (4 digits)
    VERB="$2"
    ARG="TG$3"
    N=$(echo $3 | cut -c4 )
    TG="${!ARG}"

    echo $VERB " " $ARG

    if [ "$VERB" == "0" ]
    then
    python /usr/local/bin/bm_tg.py rmtg "$TG" 1 "$DMRID" "$bmkey"
    echo Removed $TG
    exit 0
    fi
    if [ "$VERB" == "1" ]
    then
    python /usr/local/bin/bm_tg.py addtg "$TG" 1 "$DMRID" "$bmkey"
    echo Added $TG
    exit 0
    fi

    if [ "$VERB" == 2 ]
    then
    RemoteCommand 7643 disable net$N
    exit 0
    fi

    if [ "$VERB" == 3 ]
    then
    RemoteCommand 7643 enable net$N
    exit 0
    fi


    exit 1

    55 changes: 55 additions & 0 deletions tg_bg.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,55 @@
    #!/usr/bin/env python3
    # de WD5GNR
    # This script reads a verb (command), noun (tg #), your ESSID, and your BM API Key
    # It will add a static TG on timeslot 1 (verb=1 or verb=addtg)
    # It will remove a static TG on timeslot 1 (verb=0 or verb=rmtg)
    # exit code 0=ok, 1=failed, 2=bad verb, 3=wrong number of arguments
    import sys
    import requests

    def manage_talkgroup(verb, noun, slot, hotspot_id, bmkey):
    base_url = f'https://api.brandmeister.network/v2/device/{hotspot_id}/talkgroup'
    headers = {'Authorization': f'Bearer {bmkey}'}

    # add talkgrou
    if verb == '1' or verb == 'addtg':
    # Add static talkgroup to timeslot 1
    payload = {'group': int(noun), 'slot': int(slot)}
    response = requests.post(base_url, json=payload, headers=headers)
    if response.status_code == 200:
    print(f'Successfully added talkgroup {noun} to timeslot {slot}.')
    return 0
    else:
    print(f'Failed to add talkgroup {noun}. Status code: {response.status_code}')
    return 1
    elif verb == '0' or verb == 'rmtg':
    # Remove static talkgroup
    delete_url = f'{base_url}/{slot}/{noun}'
    response = requests.delete(delete_url, headers=headers)
    if response.status_code == 200:
    print(f'Successfully removed talkgroup {noun}.')
    return 0
    else:
    print(f'Failed to remove talkgroup {noun}. Status code: {response.status_code}')
    return 1
    else:
    print('Invalid verb. Use 0 to add or 1 to remove a talkgroup.')
    return 2

    if __name__ == '__main__':
    if len(sys.argv) != 6:
    print('Usage: bm_tg <verb> <noun> <slot> <node> <key>')
    print('verb: 0 to add a talkgroup, 1 to remove a talkgroup')
    print('noun: ID of the talkgroup')
    print('slot: The slot (if needed)')
    print('node: ID of the repeater')
    print('key: BM API Key')
    exit(3)
    else:
    verb = sys.argv[1]
    noun = sys.argv[2]
    slot = sys.argv[3]
    id = sys.argv[4]
    apikey = sys.argv[5]
    rc=manage_talkgroup(verb, noun, slot, id, apikey)
    exit(rc)
    14 changes: 14 additions & 0 deletions user-hook-data
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,14 @@
    # Sample user-hook-data file for remote-user-hook-example0
    # We need your DMR ID
    DMRID=123456701 # hard to pull from /etc/dmrgateway
    # List of 4 digit TGs to map to real TGs
    TG0001=3001
    TG0002=312681
    TG9999=93

    # order doesn't matter, but you do need 4 digits (e.g., don't do TG2=...)
    # If your user prefix is 77 then
    # 7710002 will activate 312681 as a static group
    # 7700002 will deactivate 312681
    # 7730001 will activate DMR network #1 (as in WPSD admin dashboard)
    # 7720001 will deactivate DMR network #1