Files
cdn-star/mmdvmhost/functions.php
T
2026-07-19 18:01:05 +08:00

1481 lines
64 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Data-extraction layer for MMDVMHost mode.
*
* The heart of the dashboard's mode-aware rendering. ~1250 lines of
* procedural helpers consumed by /index.php, /mmdvmhost/repeaterinfo.php,
* /mmdvmhost/lh.php, /mmdvmhost/localtx.php, /api/last_heard.php, and
* the protocol-specific partials.
*
* Big picture, in three layers:
*
* 1. Config readers — slurp `/etc/mmdvmhost`, `/etc/ysfgateway`,
* `/etc/p25gateway`, `/etc/nxdngateway`, `/etc/m17gateway`,
* `/etc/dapnetgateway` into line arrays. {@see getConfigItem()} is
* the workhorse: find the value of a key inside a `[Section]`.
*
* 2. Service / login / mode-status — {@see isProcessRunning()} (in
* tools.php), {@see checkDMRLogin()}, {@see showMode()} which emits
* the coloured mode grid via a giant per-protocol switch.
*
* 3. Log parsers — {@see getMMDVMLog()} and friends shell out to
* `egrep | sed | tail` against today's daily MMDVM/YSF/P25/NXDN/M17/
* DAPNET log under /var/log/pi-star/, with a fallback to yesterday's
* log when today is empty (gmdate-driven, with a 60 s clock-skew
* buffer via `time() - 86340`). {@see getHeardList()} parses the
* MMDVM log into a structured 10-tuple per call (timestamp, mode,
* callsign, id, target, source, duration, loss, BER, RSSI), and
* {@see getLastHeard()} dedupes that into the visible feed used
* by lh.php and the JSON API.
*
* IMPORTANT — many functions in here use **hardcoded substr offsets**
* against log file lines (e.g. `substr($logLine, 27, …)`). Sample log
* lines are documented inline as comments next to each parser. If
* upstream gateway log formats drift those offsets break silently, so
* preserve the example log lines verbatim and validate against a
* running device when modifying any parser.
*
* No setSecurityHeaders() call here — this file is `include_once`'d
* by callers (parent pages set headers; this is library code).
*/
/**
* Read the last $maxMatches lines from $path that match $matchRegex
* and (if non-empty) do NOT match $excludeRegex. Replaces the
* `egrep ... | sed ... | tail -N [| tac]` shell pipelines that
* every gateway-log helper used to fork on every dashboard poll.
*
* The original pipeline read the *whole* day's log file (10-50 MB
* late in a busy day) just to filter and tail it. This reads only
* the last 256 KB of the file via fseek + fread — typically 4-10×
* more raw bytes than are needed to find the last 250 dashboard-
* relevant matches even on noisy DMR hotspots, so the visible
* behaviour matches the old pipeline in every common case while
* cutting the per-poll I/O by ~200×. Zero forks.
*
* Trade-off: an extreme noise burst at the end of a log (e.g. a
* sudden flood of CSBK / overflow / Downlink lines that all get
* filtered out) could in theory leave fewer than $maxMatches in
* the 256 KB tail. The today/yesterday fallback in the callers
* compensates for one common case (just-past-midnight). If a
* really noisy hotspot ever needs more headroom, bump $bufSize.
*
* Pure core PHP. Compatible with PHP 7.0+.
*
* @param string $path absolute path to the log file
* @param string $matchRegex PCRE; lines kept only if they match
* @param string $excludeRegex PCRE; lines dropped if they match.
* Empty string disables the exclude pass.
* @param int $maxMatches max number of matching lines to return
* @param bool $stripFirstField if true, replaces `cut -d" " -f2-`:
* drop everything up to (and including)
* the first space in each surviving line
* @param bool $reverse if true, reverses the result order
* (replaces `| tac` in the DAPNET path)
* @return array<int,string> up to $maxMatches matching lines
*/
function pistar_log_tail_match($path, $matchRegex, $excludeRegex,
$maxMatches, $stripFirstField = false,
$reverse = false)
{
$bufSize = 262144; // 256 KB tail window — see docblock above
$fp = @fopen($path, 'rb');
if ($fp === false) {
return array();
}
if (fseek($fp, 0, SEEK_END) !== 0) {
fclose($fp);
return array();
}
$fileSize = ftell($fp);
if ($fileSize === false || $fileSize === 0) {
fclose($fp);
return array();
}
$readSize = min($bufSize, $fileSize);
$readFrom = $fileSize - $readSize;
if (fseek($fp, $readFrom, SEEK_SET) !== 0) {
fclose($fp);
return array();
}
$buf = fread($fp, $readSize);
fclose($fp);
if ($buf === false || $buf === '') {
return array();
}
// If we started reading mid-file, the first line in $buf is
// almost certainly cut by the chunk boundary. Drop bytes up
// to and including the first newline so we never feed a
// partial line to the regex pass. (When $readFrom == 0 we're
// at the start of the file — the first line is real, keep it.)
if ($readFrom > 0) {
$nl = strpos($buf, "\n");
if ($nl === false) {
return array();
}
$buf = substr($buf, $nl + 1);
}
$matched = array();
$lines = explode("\n", $buf);
foreach ($lines as $line) {
if ($line === '') continue;
if (!preg_match($matchRegex, $line)) continue;
if ($excludeRegex !== '' && preg_match($excludeRegex, $line)) continue;
$matched[] = $line;
}
if (count($matched) > $maxMatches) {
$matched = array_slice($matched, -$maxMatches);
}
if ($stripFirstField) {
foreach ($matched as &$mline) {
$sp = strpos($mline, ' ');
if ($sp !== false) {
$mline = substr($mline, $sp + 1);
}
}
unset($mline);
}
if ($reverse) {
$matched = array_reverse($matched);
}
return $matched;
}
function getMMDVMConfig()
{
// /etc/mmdvmhost is read once per request and cached for the
// remainder. The top-level init at the bottom of this file
// calls us once, and getDSTARLinks() calls us again on D-Star
// hosts — same request, same file, identical result. The
// static cache resets at request shutdown (PHP-FPM does not
// preserve userland state between requests), so this is purely
// an in-request dedup, not a cross-request cache.
static $cached;
if ($cached !== null) {
return $cached;
}
$cached = array();
if ($configs = @fopen(MMDVMINIPATH."/".MMDVMINIFILENAME, 'r')) {
while ($config = fgets($configs)) {
array_push($cached, trim($config, " \t\n\r\0\x0B"));
}
fclose($configs);
}
return $cached;
}
function getDMRHostsLines()
{
// /usr/local/etc/DMR_Hosts.txt is the BrandMeister master list,
// typically 3-6K lines on SD storage. repeaterinfo.php scans it
// twice per request when both DMR and YSF2DMR are enabled (lines
// 243-282 and 379-389) — both consumers iterate the same lines
// looking for an IP→name match. Read once into an in-memory
// line array; foreach() across it is free.
//
// Per-request scope only (PHP-FPM resets userland state between
// requests). file() loads the whole file in one syscall, which
// is cheaper than ~6K fgets() calls and roughly the same memory
// (~400-800 KB for the typical hosts file — fine on the 512 MB+
// Pi-Star supports).
static $cached;
if ($cached !== null) {
return $cached;
}
$cached = @file('/usr/local/etc/DMR_Hosts.txt',
FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($cached === false) {
$cached = array();
}
return $cached;
}
function getTGNameMap()
{
// /usr/local/etc/TGList_CN.json is this operator's own domestic
// talkgroup registry, synced once a day by pistar-cn-tglist-sync
// from https://860.radiowo.com/api/v1/talkgroups. MMDVMHost's log
// only ever prints the numeric ID ("to TG 86990"), never a name,
// so lh.php needs this map to show what that network's own
// dashboard shows for free.
//
// Deliberately CN-only: BrandMeister's global TGList_BM.txt is NOT
// read/merged here — this operator's TG IDs are a private/domestic
// numbering space, and mixing in BM's global names would show the
// wrong name for an ID that means something else on this network.
//
// Per-request scope only (PHP-FPM resets userland state between
// requests); cheap enough (a few dozen entries) to re-parse each
// request.
static $cached;
if ($cached !== null) {
return $cached;
}
$cached = array();
$json = @file_get_contents('/usr/local/etc/TGList_CN.json');
if ($json !== false) {
$rows = json_decode($json, true);
if (is_array($rows)) {
foreach ($rows as $row) {
if (!is_array($row)) { continue; }
if (!isset($row['tg_id'], $row['name'])) { continue; }
if (!is_numeric($row['tg_id'])) { continue; }
$cached[(int)$row['tg_id']] = (string)$row['name'];
}
}
}
return $cached;
}
function getYSFGatewayConfig()
{
// loads /etc/ysfgateway into array for further use
$conf = array();
if ($configs = @fopen(YSFGATEWAYINIPATH."/".YSFGATEWAYINIFILENAME, 'r')) {
while ($config = fgets($configs)) {
array_push($conf, trim ( $config, " \t\n\r\0\x0B"));
}
fclose($configs);
}
return $conf;
}
function getP25GatewayConfig()
{
// loads /etc/p25gateway into array for further use
$conf = array();
if ($configs = @fopen(P25GATEWAYINIPATH."/".P25GATEWAYINIFILENAME, 'r')) {
while ($config = fgets($configs)) {
array_push($conf, trim ( $config, " \t\n\r\0\x0B"));
}
fclose($configs);
}
return $conf;
}
function getNXDNGatewayConfig()
{
// loads /etc/nxdngateway into array for further use
$conf = array();
if ($configs = @fopen('/etc/nxdngateway', 'r')) {
while ($config = fgets($configs)) {
array_push($conf, trim ( $config, " \t\n\r\0\x0B"));
}
fclose($configs);
}
return $conf;
}
function getM17GatewayConfig()
{
// loads /etc/m17gateway into array for further use
$conf = array();
if ($configs = @fopen('/etc/m17gateway', 'r')) {
while ($config = fgets($configs)) {
array_push($conf, trim ( $config, " \t\n\r\0\x0B"));
}
fclose($configs);
}
return $conf;
}
function getDAPNETGatewayConfig()
{
// loads /etc/dapnetgateway into array for further use
$conf = array();
if ($configs = @fopen('/etc/dapnetgateway', 'r')) {
while ($config = fgets($configs)) {
array_push($conf, trim ( $config, " \t\n\r\0\x0B"));
}
fclose($configs);
}
return $conf;
}
function getConfigItem($section, $key, $configs)
{
// retrieves the corresponding config-entry within a [section]
$sectionpos = array_search("[" . $section . "]", $configs) + 1;
$len = count($configs);
while(startsWith($configs[$sectionpos],$key."=") === false && $sectionpos <= ($len) ) {
if (startsWith($configs[$sectionpos],"[")) {
return null;
}
$sectionpos++;
}
return substr($configs[$sectionpos], strlen($key) + 1);
}
function getEnabled ($mode, $mmdvmconfigs)
{
// returns enabled/disabled-State of mode
return getConfigItem($mode, "Enable", $mmdvmconfigs);
}
function checkDMRLogin ($dmrDaemon)
{
if ($dmrDaemon == "MMDVMHost") {
if (file_exists(MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d").".log")) {
$logPath = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d").".log";
$logCheckMMDVMHostDMRLogin = `tail -n 5 $logPath | awk '/master/ && /successfully/ || /master/ && /failed/' | tail -n 1`;
if (strpos($logCheckMMDVMHostDMRLogin, "success")) { return 0; }
elseif (strpos($logCheckMMDVMHostDMRLogin, "fail")) { return 1; }
else { return 0; }
}
}
elseif ($dmrDaemon == "DMRGateway") {
if (file_exists("/var/log/pi-star/DMRGateway-".gmdate("Y-m-d").".log")) {
$logPath = "/var/log/pi-star/DMRGateway-".gmdate("Y-m-d").".log";
$logCheckDMRGatewayDMRLogin = `tail -n 5 $logPath | awk '/master/ && /successfully/ || /master/ && /failed/' | tail -n 1`;
if (strpos($logCheckDMRGatewayDMRLogin, "success")) { return 0; }
elseif (strpos($logCheckDMRGatewayDMRLogin, "fail")) { return 1; }
else { return 0; }
}
}
else {
return 0;
}
}
function showMode($mode, $mmdvmconfigs)
{
// shows if mode is enabled or not.
if (getEnabled($mode, $mmdvmconfigs) == 1) {
if ($mode == "D-Star Network") {
if (isProcessRunning("ircddbgatewayd")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#b00; color:#500; width:50%;\">";
}
}
elseif ($mode == "System Fusion Network") {
if (isProcessRunning("YSFGateway")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#b00; color:#500; width:50%;\">";
}
}
elseif ($mode == "P25 Network") {
if (isProcessRunning("P25Gateway")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#b00; color:#500; width:50%;\">";
}
}
elseif ($mode == "NXDN Network") {
if (isProcessRunning("NXDNGateway")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#b00; color:#500; width:50%;\">";
}
}
elseif ($mode == "M17 Network") {
if (isProcessRunning("M17Gateway")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#b00; color:#500; width:50%;\">";
}
}
elseif ($mode == "POCSAG Network") {
if (isProcessRunning("DAPNETGateway")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#b00; color:#500; width:50%;\">";
}
}
elseif ($mode == "DMR Network") {
if (getConfigItem("DMR Network", "Address", $mmdvmconfigs) == '127.0.0.1') {
if (isProcessRunning("DMRGateway")) {
if (checkDMRLogin("DMRGateway") > 0) { echo "<td style=\"background:#ff9; color:#030; width:50%;\">"; }
else { echo "<td style=\"background:#0b0; color:#030; width:50%;\">"; }
} else {
echo "<td style=\"background:#b00; color:#500; width:50%;\">";
}
}
else {
if (isProcessRunning("MMDVMHost")) {
if (checkDMRLogin("MMDVMHost") > 0) { echo "<td style=\"background:#ff9; color:#030; width:50%;\">"; }
else { echo "<td style=\"background:#0b0; color:#030; width:50%;\">"; }
} else {
echo "<td style=\"background:#b00; color:#500; width:50%;\">";
}
}
}
else {
if ($mode == "D-Star" || $mode == "DMR" || $mode == "System Fusion" || $mode == "P25" || $mode == "NXDN" || $mode == "M17" || $mode == "FM" || $mode == "POCSAG") {
if (isProcessRunning("MMDVMHost")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#b00; color:#500; width:50%;\">";
}
}
}
}
elseif ( ($mode == "YSF XMode") && (getEnabled("System Fusion", $mmdvmconfigs) == 1) ) {
if ( (isProcessRunning("MMDVMHost")) && (isProcessRunning("YSF2DMR") || isProcessRunning("YSF2NXDN") || isProcessRunning("YSF2P25")) ) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#606060; color:#b0b0b0;\" aria-disabled=\"true>\">";
}
}
elseif ( ($mode == "DMR XMode") && (getEnabled("DMR", $mmdvmconfigs) == 1) ) {
if ( (isProcessRunning("MMDVMHost")) && (isProcessRunning("DMR2YSF") || isProcessRunning("DMR2NXDN")) ) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#606060; color:#b0b0b0;\" aria-disabled=\"true>\">";
}
}
elseif ( ($mode == "YSF2DMR Network") && (getEnabled("System Fusion", $mmdvmconfigs) == 1) ) {
if (isProcessRunning("YSF2DMR")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#606060; color:#b0b0b0;\" aria-disabled=\"true>\">";
}
}
elseif ( ($mode == "YSF2NXDN Network") && (getEnabled("System Fusion", $mmdvmconfigs) == 1) ) {
if (isProcessRunning("YSF2NXDN")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#606060; color:#b0b0b0;\" aria-disabled=\"true>\">";
}
}
elseif ( ($mode == "YSF2P25 Network") && (getEnabled("System Fusion", $mmdvmconfigs) == 1) ) {
if (isProcessRunning("YSF2P25")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#606060; color:#b0b0b0;\" aria-disabled=\"true>\">";
}
}
elseif ( ($mode == "DMR2NXDN Network") && (getEnabled("DMR", $mmdvmconfigs) == 1) ) {
if (isProcessRunning("DMR2NXDN")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#606060; color:#b0b0b0;\" aria-disabled=\"true>\">";
}
}
elseif ( ($mode == "DMR2YSF Network") && (getEnabled("DMR", $mmdvmconfigs) == 1) ) {
if (isProcessRunning("DMR2YSF")) {
echo "<td style=\"background:#0b0; color:#030; width:50%;\">";
} else {
echo "<td style=\"background:#606060; color:#b0b0b0;\" aria-disabled=\"true>\">";
}
}
else {
echo "<td style=\"background:#606060; color:#b0b0b0;\" aria-disabled=\"true>\">";
}
$mode = str_replace("System Fusion", "YSF", $mode);
$mode = str_replace("Network", "Net", $mode);
if (strpos($mode, 'YSF2') > -1) { $mode = str_replace(" Net", "", $mode); }
if (strpos($mode, 'DMR2') > -1) { $mode = str_replace(" Net", "", $mode); }
echo $mode."</td>\n";
}
function getMMDVMLog()
{
// Last 250 dashboard-relevant lines from today's MMDVM log,
// falling back to yesterday's tail if today is sparse (just-
// past-midnight case). The egrep|sed|tail shell pipeline that
// used to live here forked three processes per call AND
// streamed the whole day's log through them on every poll —
// see pistar_log_tail_match() for the rationale.
$logToday = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d").".log";
$logYstday = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d", time() - 86340).".log";
$matchR = '/^M.*(from|end|watchdog|lost|slow data)/';
$excR = '/(CSBK|overflow|Downlink)/';
$logLines1 = file_exists($logToday)
? pistar_log_tail_match($logToday, $matchR, $excR, 250)
: array();
$logLines2 = array();
if (sizeof($logLines1) < 250 && file_exists($logYstday)) {
$logLines2 = pistar_log_tail_match($logYstday, $matchR, $excR, 250);
}
// Preserve the original union-by-key + final tail-slice
// semantics: today's matches take the low indices, yesterday's
// fill any higher slots up to 250 total.
$logLines = $logLines1 + $logLines2;
return array_slice($logLines, -250);
}
function getYSFGatewayLog()
{
// Last 1 link-state line from today's YSFGateway log, falling
// back to yesterday if today has none.
$logToday = YSFGATEWAYLOGPATH."/".YSFGATEWAYLOGPREFIX."-".gmdate("Y-m-d").".log";
$logYstday = YSFGATEWAYLOGPATH."/".YSFGATEWAYLOGPREFIX."-".gmdate("Y-m-d", time() - 86340).".log";
$matchR = '/^M.*(onnection to|onnect to|inked|isconnect|Opening YSF network)/';
$excR = '/(Linked to MMDVM|Link successful to MMDVM|\*Link)/';
$logLines = file_exists($logToday)
? pistar_log_tail_match($logToday, $matchR, $excR, 1)
: array();
if (sizeof($logLines) === 0 && file_exists($logYstday)) {
$logLines = pistar_log_tail_match($logYstday, $matchR, $excR, 1);
}
return $logLines;
}
function getP25GatewayLog()
{
// Last 1 link-state line from today's P25Gateway log, falling
// back to yesterday if today has none. The first space-
// delimited field (severity letter) is stripped to match the
// original `cut -d" " -f2-` step.
$logToday = P25GATEWAYLOGPATH."/".P25GATEWAYLOGPREFIX."-".gmdate("Y-m-d").".log";
$logYstday = P25GATEWAYLOGPATH."/".P25GATEWAYLOGPREFIX."-".gmdate("Y-m-d", time() - 86340).".log";
$matchR = '/^M.*(ink|Starting|witched)/';
$logLines = file_exists($logToday)
? pistar_log_tail_match($logToday, $matchR, '', 1, true)
: array();
if (sizeof($logLines) === 0 && file_exists($logYstday)) {
$logLines = pistar_log_tail_match($logYstday, $matchR, '', 1, true);
}
return $logLines;
}
function getNXDNGatewayLog()
{
// Last 1 link-state line from today's NXDNGateway log; see
// getP25GatewayLog for the full rationale (same shape).
$logToday = "/var/log/pi-star/NXDNGateway-".gmdate("Y-m-d").".log";
$logYstday = "/var/log/pi-star/NXDNGateway-".gmdate("Y-m-d", time() - 86340).".log";
$matchR = '/^M.*(ink|Starting|witched)/';
$logLines = file_exists($logToday)
? pistar_log_tail_match($logToday, $matchR, '', 1, true)
: array();
if (sizeof($logLines) === 0 && file_exists($logYstday)) {
$logLines = pistar_log_tail_match($logYstday, $matchR, '', 1, true);
}
return $logLines;
}
function getM17GatewayLog()
{
// Last 1 link-state line from today's M17Gateway log; see
// getP25GatewayLog for the full rationale (same shape).
$logToday = "/var/log/pi-star/M17Gateway-".gmdate("Y-m-d").".log";
$logYstday = "/var/log/pi-star/M17Gateway-".gmdate("Y-m-d", time() - 86340).".log";
$matchR = '/^M.*(ink|Starting|witched)/';
$logLines = file_exists($logToday)
? pistar_log_tail_match($logToday, $matchR, '', 1, true)
: array();
if (sizeof($logLines) === 0 && file_exists($logYstday)) {
$logLines = pistar_log_tail_match($logYstday, $matchR, '', 1, true);
}
return $logLines;
}
function getDAPNETGatewayLog()
{
// Last 20 "Sending message" lines from today's DAPNETGateway
// log (or yesterday's if today is silent), reversed so the
// most recent message appears first. The original pipeline
// ended with `tail -n 20 | tac`; the helper does both.
$logToday = "/var/log/pi-star/DAPNETGateway-".gmdate("Y-m-d").".log";
$logYstday = "/var/log/pi-star/DAPNETGateway-".gmdate("Y-m-d", time() - 86340).".log";
$matchR = '/^M.*(Sending message)/';
$logLines = file_exists($logToday)
? pistar_log_tail_match($logToday, $matchR, '', 20, true, true)
: array();
if (sizeof($logLines) === 0 && file_exists($logYstday)) {
$logLines = pistar_log_tail_match($logYstday, $matchR, '', 20, true, true);
}
return $logLines;
}
// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: DVMEGA HR3.14
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM_HS-ADF7021 20170414 (D-Star/DMR/YSF/P25) (Build: 20:16:25 May 20 2017)
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM 20170206 TCXO (D-Star/DMR/System Fusion/P25/RSSI/CW Id)
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: ZUMspot ADF7021 v1.0.0 20170728 (DStar/DMR/YSF/P25) GitID #c16dd5a
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM_MDO ADF7021 v1.0.1 20170826 (DStar/DMR/YSF/P25) GitID #BD7KLE
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: ZUMspot-v1.0.3 20171226 ADF7021 FW by CA6JAU GitID #bfb82b4
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM_HS_Hat-v1.0.3 20171226 ADF7021 FW by CA6JAU GitID #bfb82b4
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM_HS-v1.0.3 20171226 ADF7021 FW by CA6JAU GitID #bfb82b4
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: MMDVM_HS_Dual_Hat-v1.3.6 20180521 dual ADF7021 FW by CA6JAU GitID #bd6217a
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: D2RG_MMDVM_HS-v1.4.17 20190529 14.7456MHz ADF7021 FW by CA6JAU GitID #cc451c4
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: Nano_hotSPOT-v1.3.3 20180224 ADF7021 FW by CA6JAU GitID #62323e7
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: Nano-Spot-v1.3.3 20180224 ADF7021 FW by CA6JAU GitID #62323e7
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: Nano_DV-v1.4.3 20180716 12.2880MHz ADF7021 FW by CA6JAU GitID #6729d23
// I: 1970-01-01 00:00:00.000 MMDVM protocol version: 1, description: SkyBridge-v1.5.2 20201108 14.7456MHz ADF7021 FW by CA6JAU GitID #89daa20
function getDVModemFirmware()
{
$logMMDVMNow = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d").".log";
$logMMDVMPrevious = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d", time() - 86340).".log";
$logSearchString = "MMDVM protocol version";
$logLine = '';
$modemFirmware = '';
$logLine = exec("grep \"".$logSearchString."\" ".$logMMDVMNow." | tail -1");
if (!$logLine) { $logLine = exec("grep \"".$logSearchString."\" ".$logMMDVMPrevious." | tail -1"); }
if ($logLine) {
if (strpos($logLine, 'DVMEGA')) {
$modemFirmware = substr($logLine, 67, 15);
}
if (strpos($logLine, 'description: MMDVM_HS')) {
$modemFirmware = "MMDVM_HS:".ltrim(substr($logLine, 84, 8), 'v');
}
if (strpos($logLine, 'description: MMDVM ')) {
$modemFirmware = "MMDVM:".substr($logLine, 73, 8);
}
if (strpos($logLine, 'description: ZUMspot ')) {
$modemFirmware = "ZUMspot:".strtok(substr($logLine, 83, 12), ' ');
}
if (strpos($logLine, 'description: MMDVM_MDO ')) {
$modemFirmware = "MMDVM_MDO:".ltrim(strtok(substr($logLine, 85, 12), ' '), 'v');
}
if (strpos($logLine, 'description: ZUMspot-')) {
$modemFirmware = "ZUMspot:".strtok(substr($logLine, 75, 12), ' ');
}
if (strpos($logLine, 'description: MMDVM_HS_Hat-')) {
$modemFirmware = "HS_Hat:".strtok(substr($logLine, 80, 12), ' ');
}
if (strpos($logLine, 'description: MMDVM_HS_Dual_Hat-')) {
$modemFirmware = "HS_Hat:".strtok(substr($logLine, 85, 12), ' ');
}
if (strpos($logLine, 'description: D2RG_MMDVM_HS-')) {
$modemFirmware = "HS_Hat:".strtok(substr($logLine, 81, 12), ' ');
}
if (strpos($logLine, 'description: MMDVM_HS-')) {
$modemFirmware = "MMDVM_HS:".ltrim(strtok(substr($logLine, 76, 12), ' '), 'v');
}
if (strpos($logLine, 'description: Nano_hotSPOT-')) {
$modemFirmware = "MMDVM_HS:".ltrim(strtok(substr($logLine, 80, 12), ' '), 'v');
}
if (strpos($logLine, 'description: Nano-Spot-')) {
$modemFirmware = "NanoSpot:".strtok(substr($logLine, 77, 12), ' ');
}
if (strpos($logLine, 'description: Nano_DV-')) {
$modemFirmware = "NanoDV:".strtok(substr($logLine, 75, 12), ' ');
}
if (strpos($logLine, 'description: OpenGD77 Hotspot')) {
$modemFirmware = "OpenGD77:".strtok(substr($logLine, 83, 12), ' ');
}
if (strpos($logLine, 'description: OpenGD77_HS ')) {
$modemFirmware = "OpenGD77:".strtok(substr($logLine, 79, 12), ' ');
}
if (strpos($logLine, 'description: SkyBridge-')) {
$modemFirmware = "SkyBrg:".strtok(substr($logLine, 77, 12), ' ');
}
}
return $modemFirmware;
}
function getDVModemTCXOFreq()
{
$logMMDVMNow = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d").".log";
$logMMDVMPrevious = MMDVMLOGPATH."/".MMDVMLOGPREFIX."-".gmdate("Y-m-d", time() - 86340).".log";
$logSearchString = "MMDVM protocol version";
$logLine = '';
$modemTCXOFreq = '';
$logLine = exec("grep \"".$logSearchString."\" ".$logMMDVMNow." | tail -1");
if (!$logLine) { $logLine = exec("grep \"".$logSearchString."\" ".$logMMDVMPrevious." | tail -1"); }
if ($logLine) {
if ((strpos($logLine, 'Mhz') !== false) or (strpos($logLine, 'MHz') !== false)) {
$modemTCXOFreq = $logLine;
$modemTCXOFreq = preg_replace('/.*(\d{2}\.\d{3,4}\s{0,1}M[Hh]z).*/', "$1", $modemTCXOFreq);
$modemTCXOFreq = str_replace("MHz"," MHz", $modemTCXOFreq);
}
}
return $modemTCXOFreq;
}
// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
// M: 2000-00-00 00:00:00.000 Mode set to D-Star
// M: 2000-00-00 00:00:00.000 D-Star, received RF header from M1ABC /ABCD to CQCQCQ
// M: 2000-00-00 00:00:00.000 D-Star, received RF end of transmission from M1ABC /ABCD to CQCQCQ , 00.0 seconds, BER: 0.0%, RSSI: -43/-43/-43 dBm
// M: 2000-00-00 00:00:00.000 D-Star, received network header from M1ABC /ABCD to CQCQCQ via REF000 A
// M: 2000-00-00 00:00:00.000 D-Star, received network end of transmission from M1ABC /ABCD to CQCQCQ , 0.0 seconds, 0% packet loss, BER: 0.0%
// M: 2000-00-00 00:00:00.000 D-Star, starting fast data mode
// M: 2000-00-00 00:00:00.000 D-Star, leaving fast data mode
// M: 2000-00-00 00:00:00.000 D-Star, invalid slow data header
// M: 2000-00-00 00:00:00.000 DMR Slot 2, received network voice header from M1ABC to TG 1
// M: 2000-00-00 00:00:00.000 DMR Slot 2, received RF voice header from M1ABC to 5000
// M: 2000-00-00 00:00:00.000 DMR Slot 2, received RF end of voice transmission, 1.8 seconds, BER: 3.9%
// M: 2000-00-00 00:00:00.000 DMR Slot 2, received network end of voice transmission from M1ABC to TG 2, 0.0 seconds, 0% packet loss, BER: 0.0%
// M: 2000-00-00 00:00:00.000 DMR Slot 2, RF voice transmission lost, 1.1 seconds, BER: 6.5%
// M: 2000-00-00 00:00:00.000 DMR Slot 2, received RF CSBK Preamble CSBK (1 to follow) from M1ABC to TG 1
// M: 2000-00-00 00:00:00.000 DMR Slot 2, received network Data Preamble VSBK (11 to follow) from 123456 to TG 123456
// M: 2000-00-00 00:00:00.000 DMR Talker Alias (Data Format 1, Received 24/24 char): 'Hide the bottle from Ont'
// M: 2000-00-00 00:00:00.000 0000: 07 00 20 4F 6E 74 00 00 00 *.. Ont...*
// M: 2000-00-00 00:00:00.000 DMR Slot 2, Embedded Talker Alias Block 3
// M: 2000-00-00 00:00:00.000 DMR Slot 2, received network data header from 123999 to M1ABC, 0 blocks
// M: 2000-00-00 00:00:00.000 DMR Slot 2, ended network data transmission from 123999 to M1ABC
// M: 2000-00-00 00:00:00.000 P25, received RF transmission from M1ABC to TG 10200
// M: 2000-00-00 00:00:00.000 Debug: P25RX: pos/neg/centre/threshold 106 -105 0 106
// M: 2000-00-00 00:00:00.000 Debug: P25RX: sync found in Ldu pos/centre/threshold 3986 9 104
// M: 2000-00-00 00:00:00.000 Debug: P25RX: pos/neg/centre/threshold 267 -222 22 245
// M: 2000-00-00 00:00:00.000 Debug: P25RX: sync found in Ldu pos/centre/threshold 3986 10 112
// M: 2000-00-00 00:00:00.000 P25, received RF end of transmission, 0.4 seconds, BER: 0.0%
// M: 2000-00-00 00:00:00.000 P25, received network transmission from 10999 to TG 10200
// M: 2000-00-00 00:00:00.000 P25, network end of transmission, 1.8 seconds, 0% packet loss
// M: 2000-00-00 00:00:00.000 YSF, received RF data from MW0MWZ to ALL
// M: 2000-00-00 00:00:00.000 YSF, received RF end of transmission, 5.1 seconds, BER: 3.8%
// M: 2000-00-00 00:00:00.000 YSF, received network data from M1ABC to ALL at MB6IBK
// M: 2000-00-00 00:00:00.000 YSF, network watchdog has expired, 5.0 seconds, 0% packet loss, BER: 0.0%
// M: 2000-00-00 00:00:00.000 NXDN, received RF transmission from MW0MWZ to TG 65000
// M: 2000-00-00 00:00:00.000 Debug: NXDNRX: pos/neg/centre/threshold 106 -105 0 106
// M: 2000-00-00 00:00:00.000 Debug: NXDNRX: sync found in Ldu pos/centre/threshold 3986 9 104
// M: 2000-00-00 00:00:00.000 Debug: NXDNRX: pos/neg/centre/threshold 267 -222 22 245
// M: 2000-00-00 00:00:00.000 Debug: NXDNRX: sync found in Ldu pos/centre/threshold 3986 10 112
// M: 2000-00-00 00:00:00.000 NXDN, received RF end of transmission, 0.4 seconds, BER: 0.0%
// M: 2000-00-00 00:00:00.000 NXDN, received network transmission from 10999 to TG 65000
// M: 2000-00-00 00:00:00.000 NXDN, network end of transmission, 1.8 seconds, 0% packet loss
// M: 2000-00-00 00:00:00.000 M17, received RF late entry voice transmission from M1ABC to INFO
// M: 2000-00-00 00:00:00.000 M17, received RF end of transmission from M1ABC to INFO, 2.1 seconds, BER: 0.2%, RSSI: -60/-60/-60 dBm
// M: 2000-00-00 00:00:00.000 M17, received network voice transmission from M1ABC to ECHO
// M: 2000-00-00 00:00:00.000 M17, received network end of transmission from M1ABC to ECHO, 0.0 seconds
// M: 2000-00-00 00:00:00.000 POCSAG, transmitted 1 frame(s) of data from 1 message(s)
// M: 2000-00-00 00:00:00.000 Mode set to Idle
function getHeardList($logLines)
{
//array_multisort($logLines,SORT_DESC);
$heardList = array();
$ts1duration = "";
$ts1loss = "";
$ts1ber = "";
$ts1rssi = "";
$ts2duration = "";
$ts2loss = "";
$ts2ber = "";
$ts2rssi = "";
$dstarduration = "";
$dstarloss = "";
$dstarber = "";
$dstarrssi = "";
$ysfduration = "";
$ysfloss = "";
$ysfber = "";
$ysfrssi = "";
$p25duration = "";
$p25loss = "";
$p25ber = "";
$p25rssi = "";
$nxdnduration = "";
$nxdnloss = "";
$nxdnber = "";
$nxdnrssi = "";
$pocsagduration = "";
foreach ($logLines as $logLine) {
$duration = "";
$loss = "";
$ber = "";
$rssi = "";
//removing invalid lines
if(strpos($logLine,"BS_Dwn_Act")) {
continue;
} else if(strpos($logLine,"invalid access")) {
continue;
} else if(strpos($logLine,"received RF header for wrong repeater")) {
continue;
} else if(strpos($logLine,"unable to decode the network CSBK")) {
continue;
} else if(strpos($logLine,"overflow in the DMR slot RF queue")) {
continue;
} else if(strpos($logLine,"overflow in the M17 RF queue")) {
continue;
} else if(strpos($logLine,"non repeater RF header received")) {
continue;
} else if(strpos($logLine,"Embedded Talker Alias")) {
continue;
} else if(strpos($logLine,"DMR Talker Alias")) {
continue;
} else if(strpos($logLine,", Talker Alias ")) {
continue;
} else if(strpos($logLine,", text Data: ")) {
continue;
} else if(strpos($logLine,"CSBK Preamble")) {
continue;
} else if(strpos($logLine,"Preamble CSBK")) {
continue;
}
if(strpos($logLine, "end of") || strpos($logLine, "watchdog has expired") || strpos($logLine, "invalid slow data header") || strpos($logLine, "ended RF data") || strpos($logLine, "d network data") || strpos($logLine, "RF user has timed out") || strpos($logLine, "transmission lost") || strpos($logLine, "POCSAG")) {
$lineTokens = explode(", ",$logLine);
if (array_key_exists(2,$lineTokens)) {
$duration = strtok($lineTokens[2], " ");
}
if (array_key_exists(3,$lineTokens)) {
$loss = $lineTokens[3];
}
// The change to this code was causing all FCS traffic to always show TOut rather than the timer.
// This version should still show time-out when needed, AND show the time if it exists.
if (strpos($logLine,"RF user has timed out") || strpos($logLine,"watchdog has expired") || strpos($logLine, "invalid slow data header")) {
if (array_key_exists(2,$lineTokens) && strpos($lineTokens[2], "seconds")) {
$duration = strtok($lineTokens[2], " ");
} else {
$duration = "TOut";
}
$ber = "??%";
}
// if RF-Packet with no BER reported (e.g. YSF Wires-X commands) then RSSI is in LOSS position
if (startsWith($loss,"RSSI")) {
$lineTokens[4] = $loss; //move RSSI to the position expected on code below
$loss = 'BER: ??%';
}
// if RF-Packet, no LOSS would be reported, so BER is in LOSS position
if (startsWith($loss,"BER")) {
$ber = substr($loss, 5);
$loss = "0%";
if (array_key_exists(4,$lineTokens) && startsWith($lineTokens[4],"RSSI")) {
$rssi = substr($lineTokens[4], 6);
$dBraw = substr($rssi, strrpos($rssi,'/')+1); //average only
$relint = intval($dBraw) + 93;
$signal = round(($relint/6)+9, 0);
if ($signal < 0) $signal = 0;
if ($signal > 9) $signal = 9;
if ($relint > 0) {
$rssi = "S{$signal}+{$relint}dB ({$dBraw})";
} else {
$rssi = "S{$signal} ({$dBraw})";
}
}
} else {
$loss = strtok($loss, " ");
if (array_key_exists(4,$lineTokens)) {
$ber = substr($lineTokens[4], 5);
}
}
if (strpos($logLine,"ended RF data") || strpos($logLine,"d network data")) {
switch (substr($logLine, 27, strpos($logLine,",") - 27)) {
case "DMR Slot 1":
$ts1duration = "DMR Data";
break;
case "DMR Slot 2":
$ts2duration = "DMR Data";
break;
}
} else {
switch (substr($logLine, 27, strpos($logLine,",") - 27)) {
case "D-Star":
$dstarduration = $duration;
$dstarloss = $loss;
$dstarber = $ber;
$dstarrssi = $rssi;
break;
case "DMR Slot 1":
$ts1duration = $duration;
$ts1loss = $loss;
$ts1ber = $ber;
$ts1rssi = $rssi;
break;
case "DMR Slot 2":
$ts2duration = $duration;
$ts2loss = $loss;
$ts2ber = $ber;
$ts2rssi = $rssi;
break;
case "YSF":
$ysfduration = $duration;
$ysfloss = $loss;
$ysfber = $ber;
$ysfrssi = $rssi;
break;
case "P25":
$p25duration = $duration;
$p25loss = $loss;
$p25ber = $ber;
$p25rssi = $rssi;
break;
case "NXDN":
$nxdnduration = $duration;
$nxdnloss = $loss;
$nxdnber = $ber;
$nxdnrssi = $rssi;
break;
case "M17":
$m17duration = $duration;
$m17loss = $loss;
$m17ber = $ber;
$m17rssi = $rssi;
break;
case "POCSAG":
$pocsagduration = "POCSAG Data";
break;
}
}
}
$timestamp = substr($logLine, 3, 19);
$mode = substr($logLine, 27, strpos($logLine,",") - 27);
$callsign2 = substr($logLine, strpos($logLine,"from") + 5, strpos($logLine,"to") - strpos($logLine,"from") - 6);
$callsign = $callsign2;
if (strpos($callsign2,"/") > 0) {
$callsign = substr($callsign2, 0, strpos($callsign2,"/"));
}
$callsign = trim($callsign);
$id ="";
if ($mode == "D-Star") {
$id = substr($callsign2, strpos($callsign2,"/") + 1);
}
$target = trim(substr($logLine, strpos($logLine, "to") + 3));
// Handle more verbose logging from MMDVMHost
if (strpos($target,",") !== 'false') { $target = explode(",", $target)[0]; }
$source = "RF";
if (strpos($logLine,"network") > 0 || strpos($logLine,"POCSAG") > 0) {
$source = "Net";
}
switch ($mode) {
case "D-Star":
$duration = $dstarduration;
$loss = $dstarloss;
$ber = $dstarber;
$rssi = $dstarrssi;
break;
case "DMR Slot 1":
$duration = $ts1duration;
$loss = $ts1loss;
$ber = $ts1ber;
$rssi = $ts1rssi;
break;
case "DMR Slot 2":
$duration = $ts2duration;
$loss = $ts2loss;
$ber = $ts2ber;
$rssi = $ts2rssi;
break;
case "YSF":
$duration = $ysfduration;
$loss = $ysfloss;
$ber = $ysfber;
$rssi = $ysfrssi;
$target = preg_replace('!\s+!', ' ', $target);
break;
case "P25":
if ($source == "Net" && $target == "TG 10") {$callsign = "PARROT";}
if ($source == "Net" && $callsign == "10999") {$callsign = "MMDVM";}
$duration = $p25duration;
$loss = $p25loss;
$ber = $p25ber;
$rssi = $p25rssi;
break;
case "NXDN":
if ($source == "Net" && $target == "TG 10") {$callsign = "PARROT";}
$duration = $nxdnduration;
$loss = $nxdnloss;
$ber = $nxdnber;
$rssi = $nxdnrssi;
break;
case "M17":
$duration = $m17duration;
$loss = $m17loss;
$ber = $m17ber;
$rssi = $m17rssi;
break;
case "POCSAG":
$callsign = "DAPNET";
$target = "DAPNET User";
$duration = "POCSAG Data";
break;
}
// Callsign or ID should be less than 11 chars long, otherwise it could be errorneous
if ( strlen($callsign) < 11 ) {
array_push($heardList, array($timestamp, $mode, $callsign, $id, $target, $source, $duration, $loss, $ber, $rssi));
$duration = "";
$loss ="";
$ber = "";
$rssi = "";
}
}
return $heardList;
}
function getLastHeard($logLines)
{
//returns last heard list from log
$lastHeard = array();
$heardCalls = array();
$heardList = getHeardList($logLines);
$counter = 0;
foreach ($heardList as $listElem) {
if ( ($listElem[1] == "D-Star") || ($listElem[1] == "YSF") || ($listElem[1] == "P25") || ($listElem[1] == "NXDN") || ($listElem[1] == "M17") || ($listElem[1] == "POCSAG") || (startsWith($listElem[1], "DMR")) ) {
$callUuid = $listElem[2]."#".$listElem[1].$listElem[3].$listElem[5];
if(!(array_search($callUuid, $heardCalls) > -1)) {
array_push($heardCalls, $callUuid);
array_push($lastHeard, $listElem);
$counter++;
}
}
}
return $lastHeard;
}
function getActualMode($metaLastHeard, $mmdvmconfigs)
{
// returns mode of repeater actual working in
$utc_tz = new DateTimeZone('UTC');
$local_tz = new DateTimeZone(date_default_timezone_get ());
$listElem = $metaLastHeard[0];
$timestamp = new DateTime($listElem[0], $utc_tz);
$timestamp->setTimeZone($local_tz);
$mode = $listElem[1];
if (startsWith($mode, "DMR")) {
$mode = "DMR";
}
$now = new DateTime();
$hangtime = getConfigItem("General", "ModeHang", $mmdvmconfigs);
if ($hangtime != "") {
$timestamp->add(new DateInterval('PT' . $hangtime . 'S'));
} else {
$source = $listElem[5];
if ($source == "RF" && $mode === "D-Star") {
$hangtime = getConfigItem("D-Star", "ModeHang", $mmdvmconfigs);
}
else if ($source == "Net" && $mode === "D-Star") {
$hangtime = getConfigItem("D-Star Network", "ModeHang", $mmdvmconfigs);
}
else if ($source == "RF" && $mode === "DMR") {
$hangtime = getConfigItem("DMR", "ModeHang", $mmdvmconfigs);
}
else if ($source == "Net" && $mode === "DMR") {
$hangtime = getConfigItem("DMR Network", "ModeHang", $mmdvmconfigs);
}
else if ($source == "RF" && $mode === "YSF") {
$hangtime = getConfigItem("System Fusion", "ModeHang", $mmdvmconfigs);
}
else if ($source == "Net" && $mode === "YSF") {
$hangtime = getConfigItem("System Fusion Network", "ModeHang", $mmdvmconfigs);
}
else if ($source == "RF" && $mode === "P25") {
$hangtime = getConfigItem("P25", "ModeHang", $mmdvmconfigs);
}
else if ($source == "Net" && $mode === "P25") {
$hangtime = getConfigItem("P25 Network", "ModeHang", $mmdvmconfigs);
}
else if ($source == "RF" && $mode === "NXDN") {
$hangtime = getConfigItem("NXDN", "ModeHang", $mmdvmconfigs);
}
else if ($source == "Net" && $mode === "NXDN") {
$hangtime = getConfigItem("NXDN Network", "ModeHang", $mmdvmconfigs);
}
else if ($source == "RF" && $mode === "M17") {
$hangtime = getConfigItem("M17", "ModeHang", $mmdvmconfigs);
}
else if ($source == "Net" && $mode === "M17") {
$hangtime = getConfigItem("M17 Network", "ModeHang", $mmdvmconfigs);
}
else if ($source == "RF" && $mode === "FM") {
$hangtime = getConfigItem("FM", "ModeHang", $mmdvmconfigs);
}
else if ($source == "Net" && $mode === "FM") {
$hangtime = getConfigItem("FM Network", "ModeHang", $mmdvmconfigs);
}
else if ($source == "Net" && $mode === "POCSAG") {
$hangtime = getConfigItem("POCSAG Network", "ModeHang", $mmdvmconfigs);
}
else {
$hangtime = getConfigItem("General", "RFModeHang", $mmdvmconfigs);
}
$timestamp->add(new DateInterval('PT' . $hangtime . 'S'));
}
if ($listElem[6] != null) { //if terminated, hangtime counts after end of transmission
$seconds = is_numeric($listElem[6]) ? ceil((float)$listElem[6]) : 0;
$timestamp->add(new DateInterval('PT' . $seconds . 'S'));
//$timestamp->add(new DateInterval('PT' . ceil($listElem[6]) . 'S'));
} else { //if not terminated, always return mode
return $mode;
}
if ($now->format('U') > $timestamp->format('U')) {
return "idle";
} else {
return $mode;
}
}
function getDSTARLinks()
{
// Get our current configured callsign / module
$mmdvmconfigs = getMMDVMConfig();
$dstarCallsign = str_pad(getConfigItem("General", "Callsign", $mmdvmconfigs), 7, " ", STR_PAD_RIGHT).getConfigItem("D-Star", "Module", $mmdvmconfigs);
// returns link-states of all D-Star-modules
if (filesize(LINKLOGPATH."/Links.log") == 0) {
return "Not Linked";
}
if ($linkLog = fopen(LINKLOGPATH."/Links.log",'r')) {
while ($linkLine = fgets($linkLog)) {
$linkDate = "&nbsp;";
$protocol = "&nbsp;";
$linkType = "&nbsp;";
$linkSource = "&nbsp;";
$linkDest = "&nbsp;";
$linkDir = "&nbsp;";
// Reflector-Link, sample:
// 2011-09-22 02:15:06: DExtra link - Type: Repeater Rptr: DB0LJ B Refl: XRF023 A Dir: Outgoing
// 2012-04-03 08:40:07: DPlus link - Type: Dongle Rptr: DB0ERK B Refl: REF006 D Dir: Outgoing
// 2012-04-03 08:40:07: DCS link - Type: Repeater Rptr: DB0ERK C Refl: DCS001 C Dir: Outgoing
if(preg_match_all('/^(.{19}).*(D[A-Za-z]*).*Type: ([A-Za-z]*).*Rptr: (.{8}).*Refl: (.{8}).*Dir: (.{8})/',$linkLine,$linx) > 0){
$linkDate = $linx[1][0];
$protocol = $linx[2][0];
$linkType = $linx[3][0];
$linkSource = $linx[4][0];
$linkDest = $linx[5][0];
$linkDir = $linx[6][0];
if ($linkSource != $dstarCallsign) {
$linkDate = "&nbsp;";
$protocol = "&nbsp;";
$linkType = "&nbsp;";
$linkSource = "&nbsp;";
$linkDest = "&nbsp;";
$linkDir = "&nbsp;";
}
}
// CCS-Link, sample:
// 2013-03-30 23:21:53: CCS link - Rptr: PE1AGO C Remote: PE1KZU Dir: Incoming
if(preg_match_all('/^(.{19}).*(CC[A-Za-z]*).*Rptr: (.{8}).*Remote: (.{8}).*Dir: (.{8})/',$linkLine,$linx) > 0){
$linkDate = $linx[1][0];
$protocol = $linx[2][0];
$linkType = $linx[2][0];
$linkSource = $linx[3][0];
$linkDest = $linx[4][0];
$linkDir = $linx[5][0];
}
// Dongle-Link, sample:
// 2011-09-24 07:26:59: DPlus link - Type: Dongle User: DC1PIA Dir: Incoming
// 2012-03-14 21:32:18: DPlus link - Type: Dongle User: DC1PIA Dir: Incoming
if(preg_match_all('/^(.{19}).*(D[A-Za-z]*).*Type: ([A-Za-z]*).*User: (.{6,8}).*Dir: (.*)$/',$linkLine,$linx) > 0){
$linkDate = $linx[1][0];
$protocol = $linx[2][0];
$linkType = $linx[3][0];
$linkSource = "&nbsp;";
$linkDest = $linx[4][0];
$linkDir = $linx[5][0];
}
if (strtolower(substr($linkDir, 0, 2)) == "in") { $linkDir = "In"; }
if (strtolower(substr($linkDir, 0, 3)) == "out") { $linkDir = "Out"; }
$out = $linkDest." ".$protocol."/".$linkDir;
}
}
fclose($linkLog);
return $out;
}
function getActualLink($logLines, $mode)
{
// returns actual link state of specific mode
//M: 2016-05-02 07:04:10.504 D-Star link status set to "Verlinkt zu DCS002 S"
//M: 2016-04-03 16:16:18.638 DMR Slot 2, received network voice header from 4000 to 2625094
//M: 2016-04-03 19:30:03.099 DMR Slot 2, received network voice header from 4020 to 2625094
//M: 2017-09-03 08:10:42.862 DMR Slot 2, received network data header from M6JQD to TG 9, 5 blocks
switch ($mode) {
case "D-Star":
if (isProcessRunning(IRCDDBGATEWAY)) {
return getDSTARLinks();
} else {
return "No D-Star Network";
}
break;
case "DMR Slot 1":
foreach ($logLines as $logLine) {
if(strpos($logLine,"unable to decode the network CSBK")) {
continue;
} else if(substr($logLine, 27, strpos($logLine,",") - 27) == "DMR Slot 1") {
$to = "";
if (strpos($logLine,"to")) {
$to = trim(substr($logLine, strpos($logLine,"to") + 3));
}
if ($to !== "") {
if (substr($to, 0, 3) !== 'TG ') {
continue;
}
if ($to === "TG 4000") {
return "No TG";
}
if (strpos($to, ',') !== false) {
$to = substr($to, 0, strpos($to, ','));
}
return $to;
}
}
}
return "No TG";
break;
case "DMR Slot 2":
foreach ($logLines as $logLine) {
if(strpos($logLine,"unable to decode the network CSBK")) {
continue;
} else if(substr($logLine, 27, strpos($logLine,",") - 27) == "DMR Slot 2") {
$to = "";
if (strpos($logLine,"to")) {
$to = trim(substr($logLine, strpos($logLine,"to") + 3));
}
if ($to !== "") {
if (substr($to, 0, 3) !== 'TG ') {
continue;
}
if ($to === "TG 4000") {
return "No TG";
}
if (strpos($to, ',') !== false) {
$to = substr($to, 0, strpos($to, ','));
}
return $to;
}
}
}
return "No TG";
break;
case "YSF":
// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
// M: 0000-00-00 00:00:00.000 Connect to 62829 has been requested by M1ABC
// M: 0000-00-00 00:00:00.000 Automatic connection to 62829
// New YSFGateway Format
// M: 0000-00-00 00:00:00.000 Opening YSF network connection
// M: 0000-00-00 00:00:00.000 Automatic (re-)connection to 16710 - "GB SOUTH WEST "
// M: 0000-00-00 00:00:00.000 Automatic (re-)connection to FCS00290
// M: 0000-00-00 00:00:00.000 Linked to GB SOUTH WEST
// M: 0000-00-00 00:00:00.000 Linked to FCS002-90
// M: 0000-00-00 00:00:00.000 Disconnect via DTMF has been requested by M1ABC
// M: 0000-00-00 00:00:00.000 Connect to 00003 - "YSF2NXDN " has been requested by M1ABC
// M: 0000-00-00 00:00:00.000 Link has failed, polls lost
if (isProcessRunning("YSFGateway")) {
$to = "";
foreach($logLines as $logLine) {
if ( (strpos($logLine,"Linked to")) && (!strpos($logLine,"Linked to MMDVM")) ) {
$to = trim(substr($logLine, 37, 16));
if (substr($to, 0, 3) === "FCS") { $to = str_replace(' ', '', str_replace('-', '', $to)); }
}
if (strpos($logLine,"Automatic (re-)connection to")) {
if (strpos($logLine,"Automatic (re-)connection to FCS")) {
$to = substr($logLine, 56, 8);
}
else {
$to = substr($logLine, 56, 5);
}
}
if (strpos($logLine,"Connect to")) {
$to = substr($logLine, 38, 5);
}
if (strpos($logLine,"Automatic connection to")) {
$to = substr($logLine, 51, 5);
}
if (strpos($logLine,"Disconnect via DTMF")) {
$to = "Not Linked";
}
if (strpos($logLine,"Opening YSF network connection")) {
$to = "Not Linked";
}
if (strpos($logLine,"Link has failed")) {
$to = "Not Linked";
}
if (strpos($logLine,"DISCONNECT Reply")) {
$to = "Not Linked";
}
if ($to !== "") {
return $to;
}
}
return "Not Linked";
} else {
return "Service Not Started";
}
break;
case "NXDN":
// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
// 2000-01-01 00:00:00.000 Linked at startup to reflector 10100
// 2000-01-01 00:00:00.000 Unlinked from reflector 10100 by M1ABC
// 2000-01-01 00:00:00.000 Linked to reflector 10100 by M1ABC
// 2000-01-01 00:00:00.000 No response from 10200, unlinking
// 2000-01-01 00:00:00.000 Switched to reflector 10100 by remote command
// 2000-01-01 00:00:00.000 Unlinking from 10100 due to inactivity
// 2000-01-01 00:00:00.000 Statically linked to reflector 10100
if (isProcessRunning("NXDNGateway")) {
foreach($logLines as $logLine) {
$to = "";
if (strpos($logLine,"Linked to")) {
$to = preg_replace('/[^0-9]/', '', substr($logLine, 44, 5));
$to = preg_replace('/[^0-9]/', '', $to);
return "TG ".$to;
}
if (strpos($logLine,"Linked at start")) {
$to = preg_replace('/[^0-9]/', '', substr($logLine, 55, 5));
$to = preg_replace('/[^0-9]/', '', $to);
return "TG ".$to;
}
if (strpos($logLine,"Statically linked to reflector")) {
$to = preg_replace('/[^0-9]/', '', substr($logLine, 55, 5));
$to = preg_replace('/[^0-9]/', '', $to);
return "TG ".$to;
}
if (strpos($logLine,"Switched to reflector")) {
$to = preg_replace('/[^0-9]/', '', substr($logLine, 46, 5));
$to = preg_replace('/[^0-9]/', '', $to);
return "TG ".$to;
}
if (strpos($logLine,"Starting NXDNGateway")) {
return "Not Linked";
}
if (strpos($logLine,"unlinking")) {
return "Not Linked";
}
if (strpos($logLine,"Unlinked from")) {
return "Not Linked";
}
if (strpos($logLine,"Unlinking from")) {
return "Not Linked";
}
}
return "Not Linked";
} else {
return "Service Not Started";
}
break;
case "M17":
// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
if (isProcessRunning("M17Gateway")) {
foreach($logLines as $logLine) {
if(preg_match_all('/Linked .* reflector (M17-.{3} [A-Z])/',$logLine,$linx) > 0){
return $linx[1][0];
}
if (strpos($logLine,"Starting M17Gateway")) {
return "Not Linked";
}
if (strpos($logLine,"unlinking")) {
return "Not Linked";
}
if (strpos($logLine,"Unlinking")) {
return "Not Linked";
}
if (strpos($logLine,"Unlinked")) {
return "Not Linked";
}
}
return "Not Linked";
} else {
return "Service Not Started";
}
break;
case "P25":
// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
// 2000-01-01 00:00:00.000 Linked at startup to reflector 10100
// 2000-01-01 00:00:00.000 Unlinked from reflector 10100 by M1ABC
// 2000-01-01 00:00:00.000 Linked to reflector 10100 by M1ABC
// 2000-01-01 00:00:00.000 No response from 10100, unlinking
// 2000-01-01 00:00:00.000 Switched to reflector 10100 due to RF activity from 12345
// 2000-01-01 00:00:00.000 Unlinking from reflector 10100 by 12345
// 2000-01-01 00:00:00.000 Switched to reflector 10100 by remote command
if (isProcessRunning("P25Gateway")) {
foreach($logLines as $logLine) {
$to = "";
if (strpos($logLine,"Linked to")) {
$to = preg_replace('/[^0-9]/', '', substr($logLine, 44, 5));
$to = preg_replace('/[^0-9]/', '', $to);
return "TG ".$to;
}
if (strpos($logLine,"Linked at startup to")) {
$to = preg_replace('/[^0-9]/', '', substr($logLine, 55, 5));
$to = preg_replace('/[^0-9]/', '', $to);
return "TG ".$to;
}
if (strpos($logLine,"Statically linked to reflector")) {
$to = preg_replace('/[^0-9]/', '', substr($logLine, 55, 5));
$to = preg_replace('/[^0-9]/', '', $to);
return "TG ".$to;
}
if (strpos($logLine,"Switched to reflector")) {
$to = preg_replace('/[^0-9]/', '', substr($logLine, 46, 5));
$to = preg_replace('/[^0-9]/', '', $to);
return "TG ".$to;
}
if (strpos($logLine,"Starting P25Gateway")) {
return "Not Linked";
}
if (strpos($logLine,"unlinking")) {
return "Not Linked";
}
if (strpos($logLine,"Unlinking")) {
return "Not Linked";
}
if (strpos($logLine,"Unlinked")) {
return "Not Linked";
}
}
return "Not Linked";
} else {
return "Service Not Started";
}
break;
}
return "Service Not Started";
}
function getActualReflector($logLines, $mode)
{
// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901
// M: 2016-05-02 07:04:10.504 D-Star link status set to "Verlinkt zu DCS002 S"
// M: 2016-04-03 16:16:18.638 DMR Slot 2, received network voice header from 4000 to 2625094
// M: 2016-04-03 19:30:03.099 DMR Slot 2, received network voice header from 4020 to 2625094
foreach ($logLines as $logLine) {
if (substr($logLine, 27, strpos($logLine,",") - 27) == $mode) {
$from = substr($logLine, strpos($logLine,"from") + 5, strpos($logLine,"to") - strpos($logLine,"from") - 6);
if (strlen($from) == 4 && startsWith($from,"4")) {
if ($from == "4000") {
return "No Ref";
} else {
return "Ref ".$from;
}
}
}
}
return "No Ref";
}
//Some basic inits
$mmdvmconfigs = getMMDVMConfig();
if (!in_array($_SERVER["PHP_SELF"],array('/mmdvmhost/bm_links.php','/mmdvmhost/bm_manager.php'),true)) {
$logLinesMMDVM = getMMDVMLog();
$reverseLogLinesMMDVM = $logLinesMMDVM;
array_multisort($reverseLogLinesMMDVM,SORT_DESC);
$lastHeard = getLastHeard($reverseLogLinesMMDVM);
// Only need these in repeaterinfo.php
if (strpos($_SERVER["PHP_SELF"], 'repeaterinfo.php') !== false || strpos($_SERVER["PHP_SELF"], 'index.php') !== false) {
$logLinesYSFGateway = getYSFGatewayLog();
$reverseLogLinesYSFGateway = $logLinesYSFGateway;
array_multisort($reverseLogLinesYSFGateway,SORT_DESC);
$logLinesP25Gateway = getP25GatewayLog();
$logLinesNXDNGateway = getNXDNGatewayLog();
$logLinesM17Gateway = getM17GatewayLog();
}
// Only need these in index.php
if (strpos($_SERVER["PHP_SELF"], 'index.php') !== false || strpos($_SERVER["PHP_SELF"], 'pages.php') !== false) {
$logLinesDAPNETGateway = getDAPNETGatewayLog();
}
}