From 0e7bcd6ca4673a0ff53617f7592958d81cb3ed31 Mon Sep 17 00:00:00 2001 From: Jory Pratt Date: Sat, 12 Apr 2025 21:16:10 -0500 Subject: [PATCH] Port Supermon2 node info display to Supermon 7.4+, rewritenthe bash script to python, update commands used in latest asterisk. Change config format for node_info.ini. Signed-off-by: Jory Pratt --- usr/local/sbin/ast_node_status_update.py | 233 +++++++++++++++++++++++ usr/local/sbin/node_info.ini | 10 + var/www/html/supermon/link.php | 78 ++++---- var/www/html/supermon/server.php | 88 +++++++-- 4 files changed, 357 insertions(+), 52 deletions(-) create mode 100755 usr/local/sbin/ast_node_status_update.py create mode 100644 usr/local/sbin/node_info.ini diff --git a/usr/local/sbin/ast_node_status_update.py b/usr/local/sbin/ast_node_status_update.py new file mode 100755 index 0000000..2446cf1 --- /dev/null +++ b/usr/local/sbin/ast_node_status_update.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 + +""" +ast_node_status_update.py + +This script updates various AllstarLink node variables on the local server +using the `rpt set variable` Asterisk command. It gathers information +such as system uptime, CPU load, CPU temperature, weather conditions, +and log disk usage. It also displays any active AutoSky/SkywarnPlus +weather alerts (if either is installed and configured), with the alert +itself being a clickable link if available. + +Configuration: + The script reads its primary configuration from the 'node_info.ini' file + located in the same directory as this script. This file contains settings + for the Allstar nodes to update, weather information, and the preferred + temperature unit. + +Usage: + 1. Ensure the script has execute permissions: `chmod +x ast_node_status_update.py` + 2. Create a 'node_info.ini' file in the same directory with your configuration. + 3. Run the script: `./ast_node_status_update.py` + +Configuration File (node_info.ini) Example: +[general] +NODE = node_number(s) +WX_CODE = zip/airport +WX_LOCATION = City, State +TEMP_UNIT = F + +[autosky] +MASTER_ENABLE = yes +ALERT_INI = /usr/local/bin/AUTOSKY/AutoSky.ini +WARNINGS_FILE = /var/www/html/AUTOSKY/warnings.txt +""" + +import os +import subprocess +import re +import configparser + +def run_command(command): + """Executes a shell command and returns its output.""" + try: + process = subprocess.run(command, shell=True, capture_output=True, text=True, check=True) + return process.stdout.strip() + except subprocess.CalledProcessError as e: + print(f"Error running command '{command}': {e}") + return None + except FileNotFoundError: + print(f"Command not found: {command}") + return None + +def get_uptime(): + """Gets the system uptime.""" + uptime_output = run_command("uptime -p") + if uptime_output: + return f"Up {uptime_output.replace('up ', '')}" + return None + +def get_cpu_load(): + """Gets the CPU load average.""" + uptime_output = run_command("uptime") + if uptime_output: + load_match = re.search(r"load average: (.+)", uptime_output) + if load_match: + return f'"Load Average: {load_match.group(1)}"' + return None + +def get_cpu_temperature(temp_unit): + """Gets the CPU temperature from /sys/class/thermal/thermal_zone0/temp.""" + temp_c = None + + if os.path.exists("/sys/class/thermal/thermal_zone0/temp") and os.access("/sys/class/thermal/thermal_zone0/temp", os.R_OK): + temp_raw = run_command("cat /sys/class/thermal/thermal_zone0/temp") + if temp_raw and temp_raw.isdigit(): + temp_c = int(temp_raw) / 1000 + + if temp_c is not None: + temp_unit_upper = temp_unit.upper() + if temp_unit_upper == "F": + temp_val = (temp_c * 9 / 5) + 32 + unit_str = "F" + elif temp_unit_upper == "C": + temp_val = temp_c + unit_str = "C" + else: + return '"Temp Unit Invalid in config"' + + temp_int = int(temp_val) + temp_display = f"{temp_int} {unit_str}" + + if unit_str == "C": + if temp_int <= 50: + return f'"{temp_display}"' + elif temp_int <= 60: + return f'"{temp_display}"' + else: + return f'"{temp_display}"' + elif unit_str == "F": + if temp_int <= 140: + return f'"{temp_display}"' + elif temp_int <= 158: + return f'"{temp_display}"' + else: + return f'"{temp_display}"' + else: + return '"CPU Temp N/A"' + +def get_weather(wx_code, wx_location): + """Gets weather information using external scripts if configured.""" + if not wx_code or not wx_location: + return '" "' + elif os.access("/usr/local/sbin/weather.pl", os.X_OK): + wx_raw = run_command(f"/usr/local/sbin/weather.pl \"{wx_code}\" v") + if wx_raw: + return f'"{wx_location}   ({wx_raw})"' + elif os.access("/usr/local/sbin/weather.sh", os.X_OK): + wx_raw = run_command(f"/usr/local/sbin/weather.sh \"{wx_code}\" v") + if wx_raw: + return f'"{wx_location}   ({wx_raw})"' + return '" "' + +def get_log_usage(): + """Gets the disk usage of the /var/log partition.""" + logsz_output = run_command("df -h /var/log") + if logsz_output: + lines = logsz_output.strip().split('\n') + if len(lines) > 1: + parts = lines[1].split() + if len(parts) >= 5: + used = parts[2] + percent = parts[4] + available = parts[3] + return f'"Logs - {used} {percent} used, {available} remains"' + return '"Logs - N/A"' + +def get_autosky_alerts(alert_ini, warnings_file, master_enable): + """Retrieves active weather alerts from AutoSky/SkywarnPlus, if configured.""" + github_link = 'SkywarnPlus' + enabled_text = f'{github_link} Enabled' + disabled_text = f'{github_link} Disabled' + + if master_enable.lower() != "yes": + return f'"{disabled_text}"' + + if warnings_file and not os.path.exists(warnings_file): + print(f"Warning: warnings_file '{warnings_file}' not found. Create it as root: touch {warnings_file}") + return '" "' + + if os.path.exists(warnings_file): + alert_url_command = f"grep '^OFILE=' \"{alert_ini}\" | sed 's/OFILE=//' | sed 's/&y=0/&y=1/' | sed 's/\"//g'" + alert_url = run_command(alert_url_command) + alert_url_link = f"" if alert_url else "" + alert_url_link_end = "" if alert_url else "" + alert_content = "" + try: + with open(warnings_file, 'r') as f: + alert_content = f.read().strip() + except FileNotFoundError: + return f'"{enabled_text}
No Alerts"' + + if not alert_content: + return f'"{enabled_text}
No Alerts"' + else: + alert_content_cleaned = alert_content.replace("[", "").replace("]", "") + return f'"{enabled_text}
{alert_url_link}{alert_content_cleaned}{alert_url_link_end}"' + else: + return '" "' + +def update_node_variables(node, cpu_up, cpu_load, cpu_temp_dsp, wx, logs, alert): + """Updates the Allstar node variables using the `rpt set variable` command.""" + check_node_command = f"grep -q '[[:blank:]]*\\[{node}\\]' /etc/asterisk/rpt.conf" + if run_command(check_node_command) is not None: + command = [ + "/usr/sbin/asterisk", + "-rx", + f"rpt set variable {node} cpu_up=\"{cpu_up}\" cpu_load={cpu_load} cpu_temp={cpu_temp_dsp} WX={wx} LOGS={logs}" + ] + result = subprocess.run(command, capture_output=True, text=True, check=False) + if result.returncode != 0: + print(f"Error setting variables for node {node}: {result.stderr}") + else: + print(f"Updated Variables Node {node} using rpt set variable") + + command_alert = [ + "/usr/sbin/asterisk", + "-rx", + f"rpt set variable {node} ALERT={alert}" + ] + result_alert = subprocess.run(command_alert, capture_output=True, text=True, check=False) + if result_alert.returncode != 0: + print(f"Error setting ALERT for node {node}: {result_alert.stderr}") + else: + print(f"Updated ALERT Node {node} using rpt set variable") + else: + print(f"Invalid Node {node} this server") + +if __name__ == "__main__": + script_dir = os.path.dirname(os.path.realpath(__file__)) + config_file = os.path.join(script_dir, "node_info.ini") + config = configparser.ConfigParser() + + if not os.path.exists(config_file): + print(f"Error: Configuration file '{config_file}' not found.") + exit(1) + + config.read(config_file) + + nodes = config.get("general", "NODE", fallback="").split() + wx_code = config.get("general", "WX_CODE", fallback="") + wx_location = config.get("general", "WX_LOCATION", fallback="") + temp_unit = config.get("general", "TEMP_UNIT", fallback="F") + + master_enable = config.get("autosky", "MASTER_ENABLE", fallback="no") + alert_ini = config.get("autosky", "ALERT_INI", fallback="/usr/local/bin/AUTOSKY/AutoSky.ini") + warnings_file = config.get("autosky", "WARNINGS_FILE", fallback="/var/www/html/AUTOSKY/warnings.txt") + + cpu_up = get_uptime() + cpu_load = get_cpu_load() + cpu_temp_dsp = get_cpu_temperature(temp_unit) + wx = get_weather(wx_code, wx_location) + logs = get_log_usage() + alert = get_autosky_alerts(alert_ini, warnings_file, master_enable) + + if nodes: + for node in nodes: + if node.strip(): + update_node_variables(node.strip(), cpu_up, cpu_load, cpu_temp_dsp, wx, logs, alert) + else: + print("No nodes specified in the configuration file.") + + exit(0) diff --git a/usr/local/sbin/node_info.ini b/usr/local/sbin/node_info.ini new file mode 100644 index 0000000..a7724e4 --- /dev/null +++ b/usr/local/sbin/node_info.ini @@ -0,0 +1,10 @@ +[general] +NODE = node_number(s) +WX_CODE = zip/airport +WX_LOCATION = City, State +TEMP_UNIT = F + +[autosky] +MASTER_ENABLE = yes +ALERT_INI = /usr/local/bin/AUTOSKY/AutoSky.ini +WARNINGS_FILE = /var/www/html/AUTOSKY/warnings.txt diff --git a/var/www/html/supermon/link.php b/var/www/html/supermon/link.php index 58ad706..7c45985 100644 --- a/var/www/html/supermon/link.php +++ b/var/www/html/supermon/link.php @@ -136,19 +136,49 @@ $(document).ready(function() { tx_keyed = 1; } - - if (cos_keyed == 0) { - if (tx_keyed == 0) - tablehtml += '' + localNode + 'Idle'; - else - tablehtml += '' + localNode + 'PTT-Keyed'; - } - else { - if (tx_keyed == 0) - tablehtml += '' + localNode + 'COS-Detected'; - else - tablehtml += '' + localNode + 'COS-Detected and PTT-Keyed (Full-Duplex)'; - } + + // WA3DSP - 12/2020 + var cpu_temp=tabledata[localNode].remote_nodes[row].cpu_temp; + + if (!cpu_temp) { + + if (cos_keyed == 0) { + if (tx_keyed == 0) + tablehtml += '' + localNode + 'Idle'; + else + tablehtml += '' + localNode + 'PTT-Keyed'; + } else { + if (tx_keyed == 0) + tablehtml += '' + localNode + 'COS-Detected'; + else + tablehtml += '' + localNode + 'COS-Detected and PTT-Keyed (Full-Duplex)'; + } + + } else { + + if (cos_keyed == 0) { + if (tx_keyed == 0) + + tablehtml += '' + localNode + 'Idle
'+tabledata[localNode].remote_nodes[row].ALERT + '
' + tabledata[localNode].remote_nodes[row].WX + '

CPU='+tabledata[localNode].remote_nodes[row].cpu_temp + ' - ' + tabledata[localNode].remote_nodes[row].cpu_up + '
' + tabledata[localNode].remote_nodes[row].cpu_load + '
' + tabledata[localNode].remote_nodes[row].LOGS + ''; + else + +tablehtml += '' + localNode + 'PTT-KEYED
'+tabledata[localNode].remote_nodes[row].ALERT + '
' + tabledata[localNode].remote_nodes[row].WX + '

CPU='+tabledata[localNode].remote_nodes[row].cpu_temp + ' - ' + tabledata[localNode].remote_nodes[row].cpu_up + '
' + tabledata[localNode].remote_nodes[row].cpu_load + '
' + tabledata[localNode].remote_nodes[row].LOGS + ''; + + + } else { + if (tx_keyed == 0) + +tablehtml += '' + localNode + 'COS-DETECTED
'+tabledata[localNode].remote_nodes[row].ALERT + '
' + tabledata[localNode].remote_nodes[row].WX + '

CPU='+tabledata[localNode].remote_nodes[row].cpu_temp + ' - ' + tabledata[localNode].remote_nodes[row].cpu_up + '
' + tabledata[localNode].remote_nodes[row].cpu_load + '
' + tabledata[localNode].remote_nodes[row].LOGS + ''; + + + else + +tablehtml += '' + localNode + 'COS-Detected and PTT-Keyed (Full Duplex)
'+tabledata[localNode].remote_nodes[row].ALERT + '
' + tabledata[localNode].remote_nodes[row].WX + '

CPU='+tabledata[localNode].remote_nodes[row].cpu_temp + ' - ' + tabledata[localNode].remote_nodes[row].cpu_up + '
' + tabledata[localNode].remote_nodes[row].cpu_load + '
' + tabledata[localNode].remote_nodes[row].LOGS + ''; + + } +} + +// END WA3DSP - 12/2020 for (row in tabledata[localNode].remote_nodes) { @@ -405,7 +435,7 @@ if ($myip == $mylanip) { } } #endif (count($nodes) > 0) print "
"; -print "[ $myday - $uptime ]"; +print "[ $myday ]"; ?> @@ -430,12 +460,6 @@ if (isset($DVM_URL) && isset($DVM_URL_NAME)) { } // END KN2R -// ADDED KN2R - CPU temp indicator for RPi only - irrelevant for PC Cloud -if (file_exists("/sys/class/thermal/thermal_zone0/temp")) { - $CPUTemp = exec("/usr/local/sbin/supermon/get_temp"); - print "   [ $CPUTemp]"; -} - // ADDED KN2R - 11/7/2018 - Core dump file indicator if (isset($SHOW_COREDUMPS) && ($SHOW_COREDUMPS == 'yes')) { $Cores = `ls /var/lib/systemd/coredump |wc -w`; @@ -465,20 +489,6 @@ print "

"; // END KN2R -// Added WA3DSP Weather conditions -if (isset($LOCALZIP)) { - $WX = exec("/usr/local/sbin/supermon/weather.sh $LOCALZIP v"); - print "

[ Weather conditions for $LOCATION - $LOCALZIP: "; - print " $WX  ]"; -} - -if (file_exists("/var/www/html/AUTOSKY/warnings.txt")) { - if (filesize("/var/www/html/AUTOSKY/warnings.txt")) { - $warnings = file_get_contents ("/var/www/html/AUTOSKY/warnings.txt"); - print "
$warnings
"; - } -} - print "

"; #print '
'; print_r($nodes); print '
'; diff --git a/var/www/html/supermon/server.php b/var/www/html/supermon/server.php index 3f6b5d5..4b6769b 100644 --- a/var/www/html/supermon/server.php +++ b/var/www/html/supermon/server.php @@ -148,27 +148,34 @@ while(TRUE) { $current[$node]['remote_nodes'][$i]['mode']=$arr['mode']; $current[$node]['remote_nodes'][$i]['elapsed'] = ' '; + // WA3DSP + // Change to return "Never" so test can be made in link.php -// WA3DSP -// Change to return "Never" so test can be made in link.php + if ( $arr['last_keyed'] == "Never" ) { + $current[$node]['remote_nodes'][$i]['last_keyed'] = 'Never'; + } else { + $current[$node]['remote_nodes'][$i]['last_keyed'] = ' '; + } - if ( $arr['last_keyed'] == "Never" ) { - $current[$node]['remote_nodes'][$i]['last_keyed'] = 'Never'; - } else { - $current[$node]['remote_nodes'][$i]['last_keyed'] = ' '; - } + // WA3DSP 3/2018 + // Change to add local RXKEYED COS display + $current[$node]['remote_nodes'][$i]['cos_keyed']=$arr['cos_keyed']; + $current[$node]['remote_nodes'][$i]['info']=$arr['info']; + $current[$node]['remote_nodes'][$i]['node']=$arr['node']; + // END -// WA3DSP 3/2018 -// Change to add local RXKEYED COS display -$current[$node]['remote_nodes'][$i]['cos_keyed']=$arr['cos_keyed']; -$current[$node]['remote_nodes'][$i]['info']=$arr['info']; -$current[$node]['remote_nodes'][$i]['node']=$arr['node']; -// END + // ADDED KN2R 9/2018 + // Change to add local TXKEYED PTT display + $current[$node]['remote_nodes'][$i]['tx_keyed']=$arr['tx_keyed']; + // END KN2R -// ADDED KN2R 9/2018 -// Change to add local TXKEYED PTT display -$current[$node]['remote_nodes'][$i]['tx_keyed']=$arr['tx_keyed']; -// END KN2R + // WA3DSP 12/2020 1/3/2021 + $current[$node]['remote_nodes'][$i]['cpu_temp']=$arr['cpu_temp']; + $current[$node]['remote_nodes'][$i]['cpu_up']=$arr['cpu_up']; + $current[$node]['remote_nodes'][$i]['cpu_load']=$arr['cpu_load']; + $current[$node]['remote_nodes'][$i]['ALERT']=$arr['ALERT']; + $current[$node]['remote_nodes'][$i]['WX']=$arr['WX']; + $current[$node]['remote_nodes'][$i]['LOGS']=$arr['LOGS']; $i++; } @@ -325,9 +332,36 @@ function parseNode($fp, $rptStatus, $sawStatus) { // ADDED KN2R 9/2018 if (preg_match('/Var: RPT_TXKEYED=./', $line, $matches)) { $txKeyed=substr($matches[0], strpos($matches[0], "=") + 1); - } + } // END KN2R +// WA3DSP 12/2020 1/3/2021 + if (preg_match('/Var: cpu_temp=.*/', $line, $matches)) { + $cputemp=substr($matches[0], strpos($matches[0], "=") + 1); + } + + if (preg_match('/Var: cpu_up=.*/', $line, $matches)) { + $cpuup=substr($matches[0], strpos($matches[0], "=") + 1); + } + + if (preg_match('/Var: cpu_load=.*/', $line, $matches)) { + $cpuload=substr($matches[0], strpos($matches[0], "=") + 1); + } + + if (preg_match('/Var: ALERT=.*/', $line, $matches)) { + $ALERT=substr($matches[0], strpos($matches[0], "=") + 1); + } + + if (preg_match('/Var: WX=.*/', $line, $matches)) { + $WX=substr($matches[0], strpos($matches[0], "=") + 1); + } + + if (preg_match('/Var: LOGS=.*/', $line, $matches)) { + $LOGS=substr($matches[0], strpos($matches[0], "=") + 1); + } + +// END WA3DSP + } #print "
Conns: \n"; print_r($conns); print "
"; @@ -364,6 +398,14 @@ function parseNode($fp, $rptStatus, $sawStatus) { $curNodes[$n]['direction'] = $node[3]; $curNodes[$n]['elapsed'] = $node[4]; $curNodes[$n]['link'] = @$node[5]; + + // WA3DSP 12/2020 1/3/2021 + $curNodes[$n]['cpu_temp'] = $cputemp; + $curNodes[$n]['cpu_up'] = $cpuup; + $curNodes[$n]['cpu_load'] = $cpuload; + $curNodes[$n]['ALERT'] = $ALERT; + $curNodes[$n]['WX'] = $WX; + $curNodes[$n]['LOGS'] = $LOGS; ## Fix for table display bug of IRLP nodes - Paul Aidukas, KN2R ;) # @@ -425,6 +467,16 @@ if ($txKeyed === "1") { } // END KN2R +// WA3DSP 12/2020 1/3/2021 +$curNodes[1]['cpu_temp'] = $cputemp; +$curNodes[1]['cpu_up'] = $cpuup; +$curNodes[1]['cpu_load'] = $cpuload; +$curNodes[1]['ALERT'] = $ALERT; +$curNodes[1]['WX'] = $WX; +$curNodes[1]['LOGS'] = $LOGS; +$curNodes[1]['REGISTRATIONS'] = $REGISTRATIONS; +// END WA3DSP + return $curNodes; } ?> -- 2.49.0