Skip to content
Open
6 changes: 5 additions & 1 deletion src/boot/FPPINIT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ int main(int argc, char* argv[]) {
// dup2 replaces that fd before we print anything, so this capture
// still works even though the parent shell redirected us away.
teeOutput(logFile, "fppinit", "Audio", getpid());
} else if (action == "configureBBB" || action == "applyThermal" || action == "resetThermal" || action == "installKiosk" || action == "setupPiRTC") {
} else if (action == "configureBBB" || action == "applyThermal" || action == "resetThermal" || action == "installKiosk" || action == "setupPiRTC" || action == "setupHDMICEC") {
// Same gap as setupNetwork: invoked directly via PHP exec(), output
// otherwise lives only in that request's $output/HTTP response.
teeOutput(logFile, "fppinit", "Config", getpid());
Expand Down Expand Up @@ -283,6 +283,7 @@ int main(int argc, char* argv[]) {
// After DetectCape() above, so a cape shipping DisablePiRTC in its
// defaultSettings is honoured on the boot it is first detected on.
setupPiRTCConfig();
setupHDMICECConfig();
int reboot = getRawSettingInt("rebootFlag", 0);
if (reboot && !needReboot) {
printf("FPP - Clearing reboot flags\n");
Expand Down Expand Up @@ -475,6 +476,9 @@ int main(int argc, char* argv[]) {
} else if (action == "setupPiRTC") {
// The UI's "reboot": 1 on DisablePiRTC prompts the user; don't reboot here.
setupPiRTCConfig(false);
} else if (action == "setupHDMICEC") {
// The UI's "reboot": 1 on DisableHDMICECInit prompts the user; don't reboot here.
setupHDMICECConfig(false);
} else if (action == "setupNetwork") {
PutFileContents(networkSetupMut, "1");
setupNetwork(true);
Expand Down
1 change: 1 addition & 0 deletions src/boot/FPPINIT.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ void startZRAMSwap();
void startDiskSwap();
void setupChannelOutputs();
void setupPiRTCConfig(bool rebootIfChanged = true);
void setupHDMICECConfig(bool rebootIfChanged = true);
void handleRebootActions();

// ---------------------------------------------------------------------------
Expand Down
77 changes: 77 additions & 0 deletions src/boot/FPPINIT_Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,83 @@ void setupPiRTCConfig(bool rebootIfChanged) {
#endif
}

#ifdef PLATFORM_PI
// Suppress the initial CEC Active Source at boot that wakes projectors/TVs
// (e.g. Epson L210SF, issue #2938). When enabled, adds hdmi_ignore_cec_init=1
// to config.txt. Unlike hdmi_ignore_cec=1 this only suppresses the boot
// broadcast — the CEC plugin can still power the display on/off on demand.
//
// Managed block pattern mirrors applyDisablePiRTCBlock above: strip every
// prior copy (including truncated), remember first insertion point, and
// re-insert ahead of the cape variant block to avoid leapfrog reboot loops.
static const std::string HDMI_CEC_BLOCK_BEGIN = "# FPP HDMI CEC - BEGIN (managed by fppinit, do not edit)";
static const std::string HDMI_CEC_BLOCK_END = "# FPP HDMI CEC - END";

// --- BEGIN applyDisableHDMICECBlock ---
static bool applyDisableHDMICECBlock(std::string& content, bool disable) {
const std::string orig = content;
std::string desired;
if (disable) {
desired = HDMI_CEC_BLOCK_BEGIN + "\n[all]\nhdmi_ignore_cec_init=1\n[all]\n" + HDMI_CEC_BLOCK_END + "\n";
}

size_t at = std::string::npos;
size_t begin = content.find(HDMI_CEC_BLOCK_BEGIN);
while (begin != std::string::npos) {
if (at == std::string::npos) {
at = begin;
}
size_t end = content.find(HDMI_CEC_BLOCK_END, begin);
end = (end == std::string::npos) ? content.length()
: end + HDMI_CEC_BLOCK_END.length();
if (end < content.length() && content[end] == '\n') {
++end;
}
content.erase(begin, end - begin);
begin = content.find(HDMI_CEC_BLOCK_BEGIN);
}

if (!desired.empty()) {
if (at == std::string::npos) {
at = content.find(CAPE_VARIANT_BLOCK_BEGIN);
if (at == std::string::npos) {
while (!content.empty() && content.back() == '\n') {
content.pop_back();
}
content += "\n\n";
at = content.length();
} else {
desired += "\n";
}
}
content.insert(at, desired);
}
return content != orig;
}
// --- END applyDisableHDMICECBlock ---
#endif

void setupHDMICECConfig(bool rebootIfChanged) {
#ifdef PLATFORM_PI
std::string content = GetFileContents("/boot/firmware/config.txt");
if (content.empty()) {
return;
}
// Pi 5 has no firmware HDMI path (RP1/KMS), so hdmi_ignore_cec_init is
// ignored. Never write the block on Pi 5; this also cleans up a stale
// block left from when the setting previously defaulted to on.
bool want = !isPi5() && getRawSettingInt("DisableHDMICECInit", 0) != 0;
if (applyDisableHDMICECBlock(content, want)) {
PutFileContents("/boot/firmware/config.txt", content);
printf("FPP - HDMI CEC configuration changed in config.txt\n");
if (rebootIfChanged) {
printf("\n\nRebooting to load new settings.\n\n");
exec("/usr/sbin/reboot");
}
}
#endif
}

void setupChannelOutputs() {
#ifdef PLATFORM_PI
bool hasDPI = false;
Expand Down
5 changes: 5 additions & 0 deletions www/common/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,11 @@ function ApplySetting($setting, $value)
// "reboot": 1, so the UI is already prompting for that.
exec("sudo " . $settings['fppDir'] . "/src/fppinit setupPiRTC", $output);
break;
case 'DisableHDMICECInit':
// fppinit owns the config.txt block. The setting is declared
// "reboot": 1, so the UI already prompts for reboot.
exec("sudo " . $settings['fppDir'] . "/src/fppinit setupHDMICEC", $output);
break;
case 'screensaver':
SetupScreenBlanking($value);
break;
Expand Down
1 change: 1 addition & 0 deletions www/help/settings-system.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<li><b>GPIO 14 Fan Control</b> (checkbox, Raspberry Pi, Advanced) — Uses GPIO 14 PWM to control a cooling fan. Takes effect on reboot; the fan’s temperatures are then set under <b>Fan Temperatures</b> below. A cape that declares its own fan in its device tree does not use this setting and removes it.</li>
<li><b>Fan Temperatures</b> (dynamic section, shown only when a fan thermal zone is present) — Every thermal zone with a fan cooling device bound to it — from the setting above, a cape’s own device tree, or the Pi 5 active cooler — is rendered by <code>PrintFanThermalSettings()</code> as <code>FanTrip_&lt;zone sanitized&gt;_&lt;index&gt;</code> inputs, per zone (e.g. CPU, K16-Pro) with trip points sorted low→high (“Fan On Temperature”, “Fan Speed 2/3…”). Changes are written to sysfs immediately and re-applied at every boot by <code>FPPINIT_Config</code>, since trip points revert to their device tree values on boot. <b>Reset to Defaults</b> POSTs <code>api/settings/fanThermal/reset</code> to restore the hardware values. Stored in °C, displayed in °F when <i>Temperature display units = Fahrenheit</i>.</li>
<li><b>Status Display</b> (dropdown via <code>/dev/i2c-*</code> check) — OLED/LCD on the Pi/BeagleBone I²C bus. Options: Disabled, 128×64 SSD1306 (normal/flipped/h-flipped), 128×64 2-color variants, 128×32 variants, SH1106/SH1107, SSD1327, PCF8574/PCF8574A 16×2/20×4 I²C LCDs, etc. <i>Reboot required.</i></li>
<li><b>Prevent HDMI CEC at Boot</b> (checkbox, Raspberry Pi except Pi 5) — Suppresses the initial CEC Active Source broadcast at boot that can wake connected TVs/projectors (e.g. Epson L210SF after a power outage, issue #2938). When enabled, <code>fppinit</code> adds <code>hdmi_ignore_cec_init=1</code> inside a managed block in <code>/boot/firmware/config.txt</code>. Unlike <code>hdmi_ignore_cec=1</code> it only suppresses the boot broadcast — the CEC plugin can still power the display on/off on demand. Hidden on Pi 5 (KMS/RP1, no firmware HDMI path) and any stale block is removed there. <i>Reboot required.</i></li>
<li><b>FPPD Boot Delay</b> (dropdown) — Wait after boot before starting <code>fppd</code> so slow switches/routers can come up and E1.31 multicast works. Options: 0–10s, 15/20/25/30s, 1/2/3/5 min, <b>Auto</b> (−1, wait until NTP/RTC time is valid or 5 min max). Not shown inside containers.</li>
<li><b>Override UUID</b> (text, Advanced, pii) — Replacement UUID for FPP Connect dedup / stats / services instead of the CPU/serial-derived one. <i>Reboot required.</i></li>
</ul>
Expand Down
18 changes: 18 additions & 0 deletions www/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@
"description": "System Settings",
"settings": [
"GPIOFan",
"DisableHDMICECInit",
"LEDDisplayType",
"bootDelay",
"FPP_UUID"
Expand Down Expand Up @@ -1630,6 +1631,23 @@
],
"type": "checkbox"
},
"DisableHDMICECInit": {
"name": "DisableHDMICECInit",
"description": "Prevent HDMI CEC at Boot",
"gatherStats": true,
"tip": "When enabled, adds hdmi_ignore_cec_init=1 to config.txt to suppress the initial CEC Active Source that wakes projectors/TVs at boot (e.g. after a power outage). CEC control via the CEC plugin still works. Not applicable on Pi 5 (kernel KMS, no firmware HDMI).",
"reboot": 1,
"platforms": [
"Raspberry Pi"
],
"variants": {
"Raspberry Pi": [
"!Pi 5"
]
},
"type": "checkbox",
"default": "0"
},
"GPIOFan": {
"name": "GPIOFan",
"description": "GPIO 14 Fan Control",
Expand Down