Let's be honest: everyone wants their MagicMirror to be smart and responsive – but who wants to waste energy or keep fiddling with unreliable presence sensors? That’s where the journey of this module began.
Previously, you had two choices:
- MMM-Pir (by bugsounet/Coernel82): Fancy, feature-rich, but a bit “heavy” and no longer maintained.
- MMM-MQTTScreenOnOff (by olexs): Simple, reliable, but missing those “extra” features and visual feedback.
Why not get the best of both worlds? That’s what MMM-PresenceScreenControl aims to do!
Big thanks to the original creators/maintainers/keepers:
Without their work, this project wouldn’t exist. If you like what you see here, consider checking out their original modules too!
Each module had its strengths:
- MMM-Pir: cool timer bar, auto-dimming, advanced time windows, but pretty complex and tough to maintain.
- MMM-MQTTScreenOnOff: clean, robust, MQTT-friendly, but no visual feedback or “smart” features.
By combining them, you get:
- Support for both PIR sensors (for fast, local detection) and MQTT (for remote or radar sensors).
- A slick timer bar, auto-dimming, and flexible “ignore” or “always on” schedules.
- Simple configuration, easy installation, and a codebase that won’t break your brain.
We trimmed the fat:
- No more camera or relay support (if you need that, check the original modules).
- No obfuscated code or install-time magic – everything is here, readable, and ready to tweak.
- Screen ON/OFF is now just a command: You decide how your screen turns on or off – works for X11, Wayland, Pi, or any system.
- Cron windows are clear and reliable: Want the mirror always on for breakfast? You got it. Want to ignore sensor triggers at night? No problem.
- Touch/click control: Click anywhere on the screen to turn on the display (if off) and reset the presence timer.
- Presence detection via PIR sensor (GPIO), MQTT events, or both
- Auto-dimming and configurable timers for natural, intuitive behavior
- Flexible “ignore” and “always on” scheduling with cron-style time windows
- Visual timer bar lets you (and your users) see what’s happening
- Customizable screen ON/OFF commands – works on almost any system
- Touch/click control for screen on and timer reset
- No more bloat – just the essentials for a happy, smart MagicMirror
- All the important features from both modules – and none of the old headaches.
- Works with fast PIRs and “slow” (radar, mmwave, etc.) sensors via MQTT.
- Clean, maintainable code. No more guessing or reverse engineering.
- Easy to update, easy to debug.
- Only “bar” (progress bar) visualization is available – sorry, no circle or semicircle.
- No support for cameras, relays, or other exotic hardware.
- You provide your own screen ON/OFF commands for your system (see below for many examples!).
- If you enable both PIR and MQTT, presence is triggered by either (logical “OR”).
Navigate to your MagicMirror's modules directory and clone the repository:
cd ~/MagicMirror/modules
git clone https://github.com/rkorell/MMM-PresenceScreenControl.git
Install the required dependencies by navigating into the module's directory and running:
cd MMM-PresenceScreenControl
npm install
cd ~/MagicMirror/modules/MMM-PresenceScreenControl
rm -rf node_modules package-lock.json
git pull
npm installImportant:
MMM-PresenceScreenControl no longer requires electron-rebuild.
PIR GPIO events are read via the gpiod CLI tool gpiomon (no native Node.js GPIO module).
If gpiomon is missing, install it with:
sudo apt install gpiodIf gpiomon is unavailable on your system, the module falls back automatically to Python/gpiozero (MotionSensor.py).
Plug MMM-PresenceScreenControl into your MagicMirror config.js like any other module.
All configuration is done via module parameters.
{
module: "MMM-PresenceScreenControl",
position: "bottom_bar",
config: {
mode: "PIR_MQTT",
pirGPIO: 4,
mqttServer: "mqtt://localhost:1883",
mqttTopic: "sensor/presence",
mqttPayloadOccupancyField: "presence",
mqttUser: "",
mqttPassword: "",
onCommand: "DISPLAY=:0 xrandr --output HDMI-1 --mode 1920x1200 --rotate left",
offCommand: "DISPLAY=:0 xrandr --output HDMI-1 --off",
counterTimeout: 120,
startupGracePeriod: 0,
autoDimmer: true,
autoDimmerTimeout: 60,
cronIgnoreWindows: [
{ from: "23:00", to: "05:00", days: [1,2,3,4,5] },
{ from: "01:00", to: "05:00", days: [0,6] }
],
cronAlwaysOnWindows: [
{ from: "07:00", to: "08:30", days: [1,2,3,4,5] },
{ from: "07:00", to: "09:00", days: [0,6] }
],
style: 2,
colorFrom: "red",
colorTo: "lime",
colorCronActivation: "cornflowerblue",
showPresenceStatus: true,
debug: "off",
logFileName: "",
resetCountdownWidth: false
}
},Here’s a breakdown of all the available options, with tips and friendly advice.
-
mode
"PIR","MQTT", or"PIR_MQTT"(the default).- “PIR”: Only use the local PIR sensor.
- “MQTT”: Only use remote/MQTT presence.
- “PIR_MQTT”: Use both – whichever sensor triggers, presence is active.
-
pirGPIO BCM pin number for your PIR sensor (if used). Example:
4is typical for Pi users. -
mqttServer URL for your MQTT broker, e.g.
mqtt://localhost:1883 -
mqttTopic MQTT topic to listen for presence messages.
-
mqttPayloadOccupancyField Which field in the MQTT JSON payload contains the occupancy boolean. (For simple MQTT sensors, this is often just
"presence", containing "true" or "false".) -
mqttUser Username for MQTT broker authentication. Leave empty (
"") for brokers without authentication. -
mqttPassword Password for MQTT broker authentication. Leave empty (
"") for brokers without authentication. -
onCommand / offCommand The command to turn your screen ON or OFF. This is where the magic happens! You can use just about anything that works on your system. Here are some great examples:
# For Raspberry Pi (vcgencmd, HDMI on/off) (NOT suitable for bookworm or later):
onCommand: "vcgencmd display_power 1"
offCommand: "vcgencmd display_power 0"
# For Raspberry Pi, HDMI-CEC (for TVs with CEC support):
onCommand: "echo 'on 0' | cec-client -s -d 1"
offCommand: "echo 'standby 0' | cec-client -s -d 1"
# For X11 (PC/Notebook/most Linux):
onCommand: "xset dpms force on"
offCommand: "xset dpms force off"
# For Xrandr on pi (X11 with named output):
onCommand: "xrandr --output HDMI-1 --auto"
offCommand: "xrandr --output HDMI-1 --off"
often you have to mention the correct DISPLAY for proper function, e.g.:
offCommand: "DISPLAY=:0.0 xrandr --output HDMI-1 --off"
and sometimes the "--auto" part in the onCommand references to wrong configuration. In this case you can specify the desired config within the command e.g.:
onCommand: "DISPLAY=:0.0 xrandr --output HDMI-1 --primary --mode 2560x1440 --rate 59.951 --pos 0x0 --rotate left"
# For systems with systemd-backlight (rare):
onCommand: "sudo systemctl start backlight@backlight:acpi_video0"
offCommand: "sudo systemctl stop backlight@backlight:acpi_video0"
# For some HDMI-hat drivers (Pi hats):
onCommand: "sudo sh -c 'echo 0 > /sys/class/backlight/rpi_backlight/bl_power'"
offCommand: "sudo sh -c 'echo 1 > /sys/class/backlight/rpi_backlight/bl_power'"
# For Wayland with wlr-randr (Bookworm and later):
# Important: Use WAYLAND_DISPLAY (not DISPLAY) — wlr-randr is a Wayland tool.
# Find your socket name: ls /run/user/1000/wayland-*
onCommand: "WAYLAND_DISPLAY=wayland-0 wlr-randr --output HDMI-A-1 --on"
offCommand: "WAYLAND_DISPLAY=wayland-0 wlr-randr --output HDMI-A-1 --off"
# You can add --mode and --transform to the onCommand if needed, e.g.:
# onCommand: "WAYLAND_DISPLAY=wayland-0 wlr-randr --output HDMI-A-1 --on --mode 1920x1080 --transform 270"
# For Wayland with wlopm (recommended for Trixie and later):
# wlopm uses DPMS-level power management — more robust than wlr-randr --off.
# Install: sudo apt install wlopm
onCommand: "wlopm --on HDMI-A-1"
offCommand: "wlopm --off HDMI-A-1"-
counterTimeout How long (in seconds) the display stays ON after the last presence event (from either sensor).
-
startupGracePeriod How long (in seconds) the screen stays on after module startup before the presence logic kicks in.
0(default) – screen turns off after ~1 second if nobody is detected30– screen stays on for 30 seconds after startup, then turns off if nobody is present Useful for verifying that a restart completed successfully. During the grace period, sensor events work normally — if the PIR detects someone, the timer switches tocounterTimeout.
-
autoDimmer Set to
trueto dim the screen afterautoDimmerTimeoutseconds (instead of turning it off right away). -
autoDimmerTimeout How long (in seconds) before the auto-dimmer kicks in. Must be less than
counterTimeout— if set too high, it is automatically clamped. -
cronIgnoreWindows An object-array of time-windows: {from: "HH:MM", to: "HH:MM", days: [weekday_numbers]} "from": start time (24h format) "to": end time (24h format) "days": which weekdays to apply (0=Sunday, 1=Monday, ..., 6=Saturday) During these times, all presence sensors are ignored and the screen will not turn on. Great for nighttime or “do not disturb” periods.
-
cronAlwaysOnWindows An object-array of time-windows: {from: "HH:MM", to: "HH:MM", days: [weekday_numbers]} "from": start time (24h format) "to": end time (24h format) "days": which weekdays to apply (0=Sunday, 1=Monday, ..., 6=Saturday) During these times, the screen is forced ON, no matter what the sensors say. Perfect for breakfast, parties, or any time you want the mirror always awake.
-
colorFrom / colorTo / colorCronActivation Customize the progress bar colors:
colorTo: Bar color when the timer is full (presence just detected → usually green/lime)colorFrom: Bar color when the timer is empty (about to turn off → usually red)colorCronActivation: Bar color during always-on window (typically blue) With the defaults (colorFrom: "red",colorTo: "lime"), the bar starts green and gradually turns red as time runs out — like a traffic light.
-
showPresenceStatus Set to
trueto show a “Presence: YES/NO” indicator above the bar. -
debug Set the debug logging level:
"off"– no debug output"simple"– standard info"complex"– lots of details (useful for troubleshooting)
-
logFileName Controls where debug output goes (requires
debugto be set to"simple"or"complex"):""(empty string, default) – writes toconsole.log, visible inpm2 logs"myfile.log"– writes to that file in the module directory, for focused debugging
-
resetCountdownWidth If
true, the always-on bar jumps to 100% width at the start of the final countdown. Iffalse, the bar continues smoothly from wherever it is – no sudden jumps.
// Minimal config for PIR only:
{
module: "MMM-PresenceScreenControl",
position: "bottom_bar",
config: {
mode: "PIR",
pirGPIO: 4,
onCommand: "vcgencmd display_power 1",
offCommand: "vcgencmd display_power 0"
}
}
// Minimal config for MQTT only:
{
module: "MMM-PresenceScreenControl",
position: "bottom_bar",
config: {
mode: "MQTT",
mqttServer: "mqtt://localhost:1883",
mqttTopic: "sensor/presence",
mqttPayloadOccupancyField: "presence",
onCommand: "xset dpms force on",
offCommand: "xset dpms force off"
}
}
// MQTT with broker authentication:
{
module: "MMM-PresenceScreenControl",
position: "bottom_bar",
config: {
mode: "MQTT",
mqttServer: "mqtt://your-broker:1883",
mqttTopic: "sensor/presence",
mqttPayloadOccupancyField: "presence",
mqttUser: "myuser",
mqttPassword: "mypassword",
onCommand: "xset dpms force on",
offCommand: "xset dpms force off"
}
}
// Config with ignore and always-on windows:
{
module: "MMM-PresenceScreenControl",
position: "bottom_bar",
config: {
mode: "PIR_MQTT",
pirGPIO: 4,
mqttServer: "mqtt://localhost:1883",
mqttTopic: "sensor/presence",
cronIgnoreWindows: [
{ from: "23:00", to: "05:00", days: [1,2,3,4,5] },
{ from: "01:00", to: "05:00", days: [0,6] }
],
cronAlwaysOnWindows: [
{ from: "07:00", to: "08:30", days: [1,2,3,4,5] },
{ from: "07:00", to: "09:00", days: [0,6] }
],
onCommand: "xrandr --output HDMI-1 --auto",
offCommand: "xrandr --output HDMI-1 --off"
}
}MMM-PresenceScreenControl includes built-in touch/click support that works both locally and via VNC remote access.
Touch handling is always active — no configuration needed. A click or touch anywhere on the screen:
- Turns on the display if it is currently off (executes
onCommand) - Resets the presence timer to
counterTimeout
On Wayland with wayvnc, connecting via VNC already turns on the screen automatically (see below), so the click effectively only resets the timer. On X11, however, VNC does not control screen power, so the click-to-wake feature is essential for remote access.
On Wayland with labwc compositor and wayvnc, screen power management works natively through the wlr-output-power-management protocol:
- When a VNC client connects, wayvnc acquires a power-on hold (
output_acquire_power_on), ensuring the screen stays on - When the last VNC client disconnects, wayvnc releases the hold (
output_release_power_on), and the screen returns to its previous state
This means: if the screen was off (PIR timeout) and you connect via VNC, the screen turns on automatically. When you close VNC, the screen goes back to off. No manual VNC disconnect commands are needed.
Known issue: On some occasions, disconnecting from VNC does not reliably trigger the screen-off transition. The screen may stay on until the next PIR timeout cycle turns it off. This issue is intermittent and not yet reproducible. If you experience this, simply wait for the normal presence timeout to turn off the screen.
This module supports both X11 and Wayland through configurable commands:
onCommand/offCommand: Adapt to your display server
Simply change these config parameters — no code changes needed.
MMM-PresenceScreenControl uses gpiomon from gpiod for PIR edge detection.
Benefits:
- No native Node.js GPIO dependency
- No
electron-rebuildrequired - Works with modern libgpiod setups (including libgpiod 2.x)
The module auto-selects the GPIO chip:
- Raspberry Pi 5:
gpiochip4 - Other systems:
gpiochip0(or first available/dev/gpiochip*)
If gpiomon is not installed or no GPIO chip is available, the module falls back to Python/gpiozero (MotionSensor.py).
Fallback requirements:
- Python 3 with gpiozero (
python3-gpiozero) - lgpio backend (
python3-lgpio)
-
For PIR mode, install
gpiodsogpiomonis available (sudo apt install gpiod). -
If the bar does not appear, check that
styleis set to2(bar), or use0for no graphics. -
For custom hardware or unusual OS setups, make sure
onCommandandoffCommandare correct. -
If you use both PIR and MQTT, presence is triggered by either ("OR" logic, not "AND").
-
For advanced cron time windows, check the syntax carefully.
-
GPIO errors on Debian Trixie: Ensure
gpiomonis installed (gpiodpackage). If unavailable, check fallback dependenciespython3-gpiozeroandpython3-lgpio.
Created by Dr. Ralf Korell, 2025, with gratitude and credit to
- bugsounet/Coernel82 (MMM-Pir)
- olexs (MMM-MQTTScreenOnOff)
- KristjanESPERANTO for the gpiomon refactor (PR #2)
MIT License.
Logging, efficiency, and startup improvements
- New parameter
logFileName(default:""): Controls where debug output goes. Empty string (default) writes toconsole.log(visible inpm2 logs). Set to a filename (e.g."debug.log") to write to a dedicated file in the module directory. Breaking change: In v1.4.0, debug output always went to a local file and was not visible inpm2 logs. Now it defaults toconsole.log. SetlogFileNameto restore the old behavior. - Improved cron monitor efficiency: No longer sends presence updates to the frontend every second when nothing has changed. Updates are now only sent on state transitions (entering/leaving cron windows) and during always-on countdown display. This eliminates unnecessary DOM rebuilds when the screen is off.
- New parameter
startupGracePeriod(default:0): Seconds to keep the screen on after module startup before presence logic kicks in. Set to0for immediate behavior (screen off after ~1s if nobody present). Useful for verifying that a restart completed successfully. Suggested by @htilburgs. - Updated Wayland screen command examples: Added
wlopm(recommended for Trixie+) and correctedwlr-randrexamples to useWAYLAND_DISPLAYinstead ofDISPLAY. - Fixed: Screen on/off commands were executed every second during always-on and ignore windows instead of only on state transitions. Added screen state tracking to prevent redundant command execution.
- Fixed:
autoDimmerTimeoutis now automatically clamped tocounterTimeout - 1if set too high. - Removed dead code: unused variables from earlier versions, unused CSS class
.psc-overlay.
GPIO refactor & dependency cleanup
- Replaced native
node-libgpiodaccess withgpiomon(gpiodCLI tools); compatible with libgpiod 1.x and 2.x. Contributed by @KristjanESPERANTO in PR #2. - Removed the
postinstallelectron rebuild flow entirely. - PIR mode now works without native Node.js rebuilds.
- Kept automatic fallback to Python/gpiozero when
gpiomonis unavailable. - Fixed log spam: routed all runtime
console.logcalls through the debug system (simple/complexlevels). - Renamed log prefix from
[MMM-Pir]to[PresenceScreenControl]. - Fixed startup behavior: screen now turns off after ~1 second if no presence is detected at startup. Previously, the screen stayed on indefinitely until the first sensor event. Reported by @htilburgs.
- Removed
package-lock.jsonfrom repository tracking (added to.gitignore). Preventsgit pullconflicts afternpm install. Reported by @htilburgs.
Dependency cleanup: zero vulnerabilities
- Removed
@electron/rebuildfromoptionalDependencies. It is now installed on-demand by the postinstall script only whennode-libgpiodis present (Bookworm with libgpiod 1.x). This eliminates all npm deprecation warnings and 7 of 8 audit vulnerabilities that were caused by transitive build-time dependencies (tar, glob, rimraf, minimatch, etc.). - Upgraded
mqttdependency from v4 to v5. The MQTT client API is fully backwards-compatible; no configuration changes required. mqtt v5 eliminates the remaining minimatch vulnerability via updatedhelp-medependency. - Result:
npm installnow reports 0 vulnerabilities and 0 deprecation warnings, down from 8 vulnerabilities and 6 warnings. Package count reduced from 181 to 47.
New: MQTT broker authentication
Added support for MQTT brokers that require username/password authentication.
Two new optional config parameters: mqttUser and mqttPassword.
Credentials are passed to the MQTT client only when configured (non-empty).
Fully backwards-compatible — existing configurations without these parameters continue to work unchanged.
Closes #1.
Removed: VNC Disconnect & Double-Click
The vncDisconnectCommand parameter and double-click screen shutdown have been removed.
Why? wayvnc (0.9.1+) natively manages screen power via the wlr-output-power-management Wayland protocol:
- VNC client connects → wayvnc acquires power-on hold → screen turns on
- Last VNC client disconnects → wayvnc releases hold → screen returns to previous state
The manual VNC disconnect workaround (double-click → disconnect VNC → screen off) is no longer needed. Touch/click now only wakes up the screen and resets the timer.
- Remove
vncDisconnectCommandfrom your config (or leave it - it's ignored) - Touch behavior is now single-click only (wakeup/timer reset)
Major Changes: Touch Simplification & GPIO Fallback
-
Removed:
touchModeparameter (0-3)- Touch handling is now always active with fixed behavior (click = wakeup)
-
New: Automatic GPIO fallback for Debian Trixie
pirLib.jsnow auto-detects ifnode-libgpiodis unavailable- Falls back to Python/gpiozero transparently
- Supports both Debian 12 (Bookworm) and Debian 13 (Trixie)
-
New:
touchPresencemechanism in node_helper- Separate presence flag for click events (vs. PIR/MQTT)
- Auto-timeout after 100ms allows countdown to proceed
- Combined features from MMM-Pir and MMM-MQTTScreenOnOff
- PIR sensor support (GPIO via node-libgpiod)
- MQTT presence detection
- Auto-dimming with configurable timeout
- Cron-based ignore and always-on windows
- Visual timer bar with color gradient
- Touch override modes (0-3)
- Configurable screen ON/OFF commands

