Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions PRIMER.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ Same as the other ShowPilot repos:
| 0.14.24 | **Field renamed: "ShowPilot Server URL".** The Server tab's **Server URL** is now **ShowPilot Server URL**, the name ShowPilot Player and ShowPilot's hosted dashboard use too, so it's clear among other FPP plugins' settings. Its help text now covers both connections (self-hosted address, or the URL a host shows you), and the Show Token help says where to find the token in either case. Log lines and the README use the same name. The setting key (`serverUrl`) is unchanged, so saved settings carry over. Checked: `php -l` on changed PHP and the settings page, `bash -n` on the changed script. |
| 0.14.25 | **Show buttons.** ShowPilot (v0.33.278-master.12+ / Lite 0.5.124+) can put buttons on the viewer page that run something on the show. The heartbeat answer's `showButtons: true` turns on a check every loop tick (≈0.5 s, before the FPP status poll, so it also runs while FPP is idle): `GET /api/plugin/show-buttons` → `{presses: [{id, command, args}], showButtons}`; `showButtons: false` in either answer stops it. Each press runs through FPP's `POST /api/command` with a JSON `{command, args}` body (no URL encoding, so effect names with spaces or `/` work). Allow-list `$SP_SHOW_BUTTON_COMMANDS`: `Effect Start`, `Trigger Command Preset`, `Run Script` only; anything else, or a press with no name, is logged and skipped; args are strings capped at 256 chars. Log lines `[buttons] ...`. Older ShowPilot servers never send the flag, so nothing changes for them. `pluginInfo.json` privacy `other` now says what the buttons run. Checked: `php -l`; the button code run against canned server answers and a fake FPP `/api/command` (on/off switching, allow-list, empty name, FPP error logged); ShowPilot's scenario tests cover the server side. |
| 0.14.26 | **FPP Monitor.** ShowPilot (v0.33.278-master.13+ / Lite 0.5.125+) has an FPP Monitor page. The heartbeat answer's `fppMonitor: true` turns on reporting: when due (`sp_fpp_monitor_tick()`, every loop tick checks the clock), the plugin reads FPP's own `/api/system/status` and `/api/fppd/multiSyncSystems` on 127.0.0.1, plus disk free/total for the media folder (`disk_free_space`), and every 60 s `/api/playlists` and `/api/commandPresets?names=true`, then `POST /api/plugin/fpp-status`. The answer's `nextInSec` (clamped 1–30; 5 normally, 1 while the page is open) sets the next report, and its `commands` are run: `startPlaylist` → `Start Playlist` [name, false, false], `preset` → `Trigger Command Preset`, `stopGracefully` → `Stop Gracefully`, `stopNow` → `Stop Now` (all via `POST /api/command`), `restartFppd` → `GET /api/system/fppd/restart`; anything else is refused. Results `{id, ok, message}` go back in the next report, which is sent right away. Log lines `[fpp-monitor] ...`. Older servers never send the flag. `pluginInfo.json` privacy `sends` gained the status entry (FPP shows the install dialog again). Checked: `php -l`; the reporting code against a real ShowPilot test server and a fake FPP on port 80 (status, MultiSync, lists, disk all on the page; Start Playlist / Trigger Command Preset / Stop Now posted to FPP with the right arguments; restart called; a failing preset came back as failed with FPP's message). |
| 0.14.27 | **FPP Monitor: each MultiSync remote's own status.** ShowPilot (v0.33.278-master.15+) shows status, CPU temperature, CPU, memory and uptime for every FPP in the MultiSync list. While the FPP Monitor is wanted, `sp_fpp_remote_statuses()` asks each MultiSync system (not local, private IPv4 only, mode player/remote/master (not bridge, which is mostly controllers such as WLED), at most 16; no lastSeen filter, because FPP refreshes it only now and then, 20+ min old on a healthy show) for `/api/system/status` every 15 s, all at once with `curl_multi` (1.5 s connect / 2 s total, no redirects); without curl it asks up to 4 one at a time with a 1 s timeout. `sp_trim_remote_status()` keeps only state, sequence, uptime, platform, sensors (label/value/type) and CPU/memory, and the snapshot carries them as `remoteStatus: { "<address>": … }` (a remote that did not answer is `null`, so ShowPilot shows it offline; left out when there are none; older ShowPilot ignores it). `pluginInfo.json` privacy text updated. Suggested by @cathelest. Checked: read through (no PHP here to lint); `pluginInfo.json` parses. |

---

Expand Down
2 changes: 1 addition & 1 deletion pluginInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
},
{
"to": "your ShowPilot server",
"what": "FPP's status every few seconds when ShowPilot asks for it: what's playing and next, FPP's version, host name and IP address, its sensors (such as CPU temperature), CPU, memory and disk use, the MultiSync systems it sees, and its playlist and command preset names",
"what": "FPP's status every few seconds when ShowPilot asks for it: what's playing and next, FPP's version, host name and IP address, its sensors (such as CPU temperature), CPU, memory and disk use, the MultiSync systems it sees with each FPP remote's own status (what it's playing, CPU temperature and other sensors, CPU, memory and uptime, which the plugin asks each remote for on your show network), and its playlist and command preset names",
"why": "for ShowPilot's FPP Monitor page, which also lets you start a playlist, run a command preset, stop the show or restart FPPD from ShowPilot",
"alwaysOn": false
}
Expand Down
114 changes: 112 additions & 2 deletions showpilot_listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ function sp_update_notice($resp) {
// ShowPilot's FPP Monitor page shows this FPP's status and offers a few
// controls. ShowPilot never connects to FPP; while the heartbeat says
// fppMonitor, this plugin sends a snapshot of FPP's own status calls:
// POST /api/plugin/fpp-status { status, multisync, disk, now, playlists?, presets?, results? }
// POST /api/plugin/fpp-status { status, multisync, disk, now, playlists?, presets?, results?, remoteStatus? }
// and runs the controls in the answer ({commands: [{id, action, name?}],
// nextInSec}). ShowPilot asks for a report every 5 s, or every second while
// someone has the page open. Only the five actions below are ever run;
Expand Down Expand Up @@ -355,6 +355,113 @@ function sp_fpp_get_json($path) {
return is_array($j) ? $j : null;
}

// MultiSync remotes' own status (v0.14.27+, ShowPilot v0.33.278-master.15+).
// Every 15 s while the FPP Monitor is wanted, ask each FPP this one sees on
// the show network for its /api/system/status, all at once (curl_multi) with
// a 2 s limit, so the listener loop never waits long. Only private IPv4
// addresses of FPP systems (player/remote), at most 16, one request per
// address. A remote that doesn't answer is sent as null (ShowPilot shows it
// offline). Each answer is cut down to what ShowPilot shows: state, sequence, CPU,
// memory, sensors (temperatures), uptime and platform.
$fppRemoteStatus = array();
$fppRemoteStatusAt = 0;

function sp_is_private_ipv4($ip) {
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) return false;
return !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE) && strpos($ip, '127.') !== 0;
}

function sp_trim_remote_status($j) {
if (!is_array($j)) return null;
$adv = (isset($j['advancedView']) && is_array($j['advancedView'])) ? $j['advancedView'] : array();
$util = (isset($adv['Utilization']) && is_array($adv['Utilization'])) ? $adv['Utilization'] : array();
$sensors = array();
if (isset($j['sensors']) && is_array($j['sensors'])) {
foreach (array_slice($j['sensors'], 0, 30) as $s) {
if (!is_array($s)) continue;
$sensors[] = array(
'label' => isset($s['label']) ? substr((string)$s['label'], 0, 60) : '',
'value' => isset($s['value']) && is_numeric($s['value']) ? $s['value'] + 0 : null,
'valueType' => isset($s['valueType']) ? substr((string)$s['valueType'], 0, 30) : '',
);
}
}
$pick = function ($a, $k, $max) { return (isset($a[$k]) && is_scalar($a[$k])) ? substr((string)$a[$k], 0, $max) : ''; };
return array(
'status_name' => $pick($j, 'status_name', 40),
'current_sequence' => $pick($j, 'current_sequence', 200),
'uptimeStr' => $pick($j, 'uptimeStr', 60),
'platform' => $pick($j, 'platform', 60),
'sensors' => $sensors,
'advancedView' => array(
'Platform' => $pick($adv, 'Platform', 60),
'Utilization' => array(
'CPU' => isset($util['CPU']) && is_numeric($util['CPU']) ? $util['CPU'] + 0 : null,
'Memory' => isset($util['Memory']) && is_numeric($util['Memory']) ? $util['Memory'] + 0 : null,
'Uptime' => $pick($util, 'Uptime', 60),
),
),
);
}

function sp_fpp_remote_statuses($multisync, $now) {
global $fppRemoteStatus, $fppRemoteStatusAt;
if (time() - $fppRemoteStatusAt < 15) return $fppRemoteStatus;
$fppRemoteStatusAt = time();
$ips = array();
$systems = (is_array($multisync) && isset($multisync['systems']) && is_array($multisync['systems'])) ? $multisync['systems'] : array();
foreach ($systems as $s) {
if (!is_array($s) || !empty($s['local'])) continue;
$ip = isset($s['address']) ? (string)$s['address'] : '';
if (!sp_is_private_ipv4($ip) || isset($ips[$ip])) continue;
// FPP systems only (controllers don't answer /api/system/status).
$mode = isset($s['fppModeString']) ? strtolower((string)$s['fppModeString']) : '';
// Not 'bridge': that's mostly controllers such as WLED.
if (!in_array($mode, array('player', 'remote', 'master'), true)) continue;
// No lastSeen filter: FPP only refreshes it now and then (20+ min old
// on a healthy show), so a remote that answers is simply online.
$ips[$ip] = true;
if (count($ips) >= 16) break;
}
$out = array();
if (!count($ips)) { $fppRemoteStatus = $out; return $out; }
if (function_exists('curl_multi_init')) {
$mh = curl_multi_init();
$hs = array();
foreach (array_keys($ips) as $ip) {
$ch = curl_init('http://' . $ip . '/api/system/status');
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT_MS => 1500, CURLOPT_TIMEOUT_MS => 2000,
));
curl_multi_add_handle($mh, $ch);
$hs[$ip] = $ch;
}
$running = 0;
do {
curl_multi_exec($mh, $running);
if ($running) curl_multi_select($mh, 0.2);
} while ($running > 0);
foreach ($hs as $ip => $ch) {
$body = curl_multi_getcontent($ch);
$out[$ip] = (curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200 && is_string($body) && strlen($body) < 500000)
? sp_trim_remote_status(json_decode($body, true)) : null;
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
} else {
// No curl: one at a time, short timeouts, fewer remotes.
$ctx = stream_context_create(array('http' => array('timeout' => 1, 'ignore_errors' => true, 'follow_location' => 0)));
foreach (array_slice(array_keys($ips), 0, 4) as $ip) {
$body = @file_get_contents('http://' . $ip . '/api/system/status', false, $ctx);
$out[$ip] = $body === false ? null : sp_trim_remote_status(json_decode($body, true));
}
}
$fppRemoteStatus = $out;
return $out;
}

function sp_fpp_monitor_run($c) {
$action = (isset($c->action) && is_string($c->action)) ? $c->action : '';
$name = (isset($c->name) && is_string($c->name)) ? substr($c->name, 0, 200) : '';
Expand Down Expand Up @@ -393,12 +500,15 @@ function sp_fpp_monitor_tick() {

$status = sp_fpp_get_json('/api/system/status');
if ($status === null) return; // fppd not answering; try again later
$multisync = sp_fpp_get_json('/api/fppd/multiSyncSystems');
$body = array(
'source' => 'fpp',
'now' => time(),
'status' => $status,
'multisync' => sp_fpp_get_json('/api/fppd/multiSyncSystems'),
'multisync' => $multisync,
);
$remoteStatus = sp_fpp_remote_statuses($multisync, time());
if (count($remoteStatus)) $body['remoteStatus'] = $remoteStatus;
$dir = isset($settings['mediaDirectory']) ? $settings['mediaDirectory'] : '/home/fpp/media';
$free = @disk_free_space($dir);
$total = @disk_total_space($dir);
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
// "0.10.0" in other files and update by hand — by design, no other file
// has it.
// ============================================================
$PLUGIN_VERSION = "0.14.26";
$PLUGIN_VERSION = "0.14.27";