MagicMirror Forum
    • Recent
    • Tags
    • Unsolved
    • Solved
    • MagicMirror² Repository
    • Documentation
    • 3rd-Party-Modules
    • Donate
    • Discord
    • Register
    • Login
    1. Home
    2. rkorell
    3. Posts
    A New Chapter for MagicMirror: The Community Takes the Lead
    Read the statement by Michael Teeuw here.
    R
    Offline
    • Profile
    • Following 0
    • Followers 1
    • Topics 25
    • Posts 424
    • Groups 1

    Posts

    Recent Best Controversial
    • RE: MMM-FRITZ-Box-Callmonitor-py3 + MMM-Callmonitor-Current-Call Window

      Dear @wuermchen - you’re welcome.

      Great that you’ve identified some additional issues and now have a working solution!
      Congratulations!

      Warm regards,
      Ralf

      posted in Troubleshooting
      R
      rkorell
    • RE: MMM-FRITZ-Box-Callmonitor-py3 + MMM-Callmonitor-Current-Call Window

      Dear @wuermchen ,
      as I read that you’ve already tried to solve this - may a more concrete description is useful for you:

      Repo: https://github.com/xIExodusIx/MMM-FRITZ-Box-Callmonitor-py3
      Patched file: node_helper.js (only this one file is changed)
      Frontend file: unchanged

      Problem

      Numbers blocked via the FRITZ!Box “number range” blocklist (Rufnummern-Bereichsliste, not phonebook-blocked) cause the FRITZ!Box to emit RING and DISCONNECT events within a few milliseconds of each other.

      The original module fires:

      • SHOW_ALERT immediately on inbound
      • HIDE_ALERT immediately on disconnected

      Both DOM updates happen within the same render frame → browser crashes.

      Phonebook-blocked numbers are handled separately (the library emits a dedicated blocked event) and are not affected by this issue.


      Fix concept

      Don’t fire SHOW_ALERT immediately. Buffer the inbound notification for SPAM_SUPPRESS_WINDOW_MS (default: 1500 ms). If a disconnected event for the same call.id arrives within that window → drop the call entirely. No SHOW_ALERT, no HIDE_ALERT, no DOM churn.

      Real calls (ring duration > 1.5 s, or accepted within 1.5 s) work exactly as before — the only difference is the pop-up appears with a ~1.5 s delay, which is invisible in practice.


      Behavior matrix

      Scenario Behavior
      Real call, rings > 1.5 s Pop-up appears with ~1.5 s delay (imperceptible)
      Real call, accepted within 1.5 s connected handler flushes the timer, fires CALL_CONNECTED
      Spam call (RING + DISCONNECT < 1.5 s) Timer is cancelled, no notification at all → no crash
      Phonebook-blocked number Unchanged — library emits dedicated blocked event
      Outbound call Unchanged — separate event path, Map is never touched

      Changes (all in node_helper.js)

      # Location Action
      1 After line 11 (after require imports) Add constant SPAM_SUPPRESS_WINDOW_MS = 1500
      2 start: (line 23–29) Add one line: this.pendingInbound = new Map();
      3 monitor.on("inbound", ...) (line 82–87) Replace body — instead of firing sendSocketNotification immediately, store a timer in the Map
      4 monitor.on("connected", ...) (line 105–110) Add timer-cleanup at the start, rest unchanged
      5 monitor.on("disconnected", ...) (line 113–117) Add timer-check at the start — if pending → clearTimeout + return

      All changes are marked with // CHANGES for wuermchen comments in the file.


      Tuning

      SPAM_SUPPRESS_WINDOW_MS = 1500 is a safe default.

      • Very fast hardware/network: 800–1000 ms is enough
      • Slow setups: bump to 2000 ms
      • For a runtime config option, expose it as this.config.suppressWindow and read in setupMonitor

      Caveats / things to verify in your setup

      1. call.id must exist on the library’s event objects.
        The library node-fritzbox-callmonitor derives it from the FRITZ!Box stream’s ConnectionId. Should work, but if your installed library version uses a different property name (e.g. call.connectionId), adjust accordingly. Easy to verify with console.log(call) in the inbound handler.

      2. Assumption: blocklist-range calls emit inbound + disconnected.
        This matches the forum description (RING + DISCONNECT). If your specific FRITZ!Box firmware emits something different (e.g. directly blocked), the patch won’t help — but won’t break anything either, because the existing blocked path is untouched.

      3. Side effect: no RELOAD_CALLS for suppressed spam calls.
        The original disconnected handler triggers a refresh of the call list from the FRITZ!Box API. Suppressed spam calls won’t trigger this refresh — the on-screen list updates only at the next real event. Acceptable in practice (spam calls are in the FRITZ!Box history anyway).


      Installation

      cd ~/MagicMirror/modules/MMM-FRITZ-Box-Callmonitor-py3
      cp node_helper.js node_helper.js.orig           # backup
      # replace node_helper.js with the patched version
      touch ~/MagicMirror/config/config.js            # graceful restart via pm2 file-watch
      

      Rollback:

      cd ~/MagicMirror/modules/MMM-FRITZ-Box-Callmonitor-py3
      cp node_helper.js.orig node_helper.js
      touch ~/MagicMirror/config/config.js
      

      Complexity assessment

      Structurally simple — one file, one function (setupMonitor), ~15 new lines. No race conditions (Node.js is single-threaded). No memory leak (timer + Map entry are explicitly cleaned up in every code path).

      The uncertainty is not in the code, but in the two library assumptions above (point 1 and 2 under Caveats). The fix has not been verified against a live spam call yet — it is derived from the forum description and the library’s documented event model.

      complete patched node_helper.js:
      (You will find all changes marked with “// CHANGES for wuermchen — Spam-Call Suppression”

      "use strict";
      
      const NodeHelper = require("node_helper");
      const CallMonitor = require("node-fritzbox-callmonitor");
      const vcard = require("vcard-json");
      const phoneFormatter = require("phone-formatter");
      const xml2js = require("xml2js");
      const moment = require('moment');
      const exec = require('child_process').exec;
      const {PythonShell} = require('python-shell');
      const path = require("path");
      
      // ============================================================================
      // CHANGES for wuermchen — Spam-Call Suppression
      // ----------------------------------------------------------------------------
      // Background: FRITZ!Box "number range" blocklist entries trigger RING and
      // DISCONNECT almost simultaneously. The original code fires SHOW_ALERT on
      // "inbound" and HIDE_ALERT on "disconnected" immediately, causing two
      // overlapping DOM updates within a few ms → browser crashes.
      //
      // Fix: buffer "inbound" notifications for SPAM_SUPPRESS_WINDOW_MS. If a
      // "disconnected" with the same call.id arrives before the timer fires, drop
      // the call entirely (no SHOW_ALERT, no HIDE_ALERT). Real calls (ring > 1.5 s
      // or accepted) work as before, just with ~1.5 s delay on the pop-up.
      // ============================================================================
      const SPAM_SUPPRESS_WINDOW_MS = 1500;
      // ============================================================================
      
      const CALL_TYPE = Object.freeze({
      	INCOMING: "1",
      	MISSED: "2",
      	OUTGOING: "3",
      	BLOCKED: "10"		//New entry for blocked calls as found on: https://fritzconnection.readthedocs.io/en/1.14.0/sources/library_modules.html
      })
      //outgoing missed calls are not in the list
      
      module.exports = NodeHelper.create({
      	// Subclass start method.
      	start: function () {
      		this.ownNumbers = []
      		this.started = false;
      		//create addressbook dictionary
      		this.AddressBook = {};
      		// CHANGES for wuermchen — pending inbound calls awaiting suppression decision
      		this.pendingInbound = new Map();   // call.id -> timer
      		console.log("Starting module: " + this.name);
      	},
      
      	normalizePhoneNumber(number) {
      		return phoneFormatter.normalize(number.replace(/\s/g, ""));
      	},
      
      	getName: function (number) {
      		//Normalize number
      		var number_formatted = this.normalizePhoneNumber(number);
      		//Check if number is in AdressBook if yes return the name
      		if (number_formatted in this.AddressBook) {
      			return this.AddressBook[number_formatted];
      		} else {
      			//Not in AdressBook return original number
      			return number;
      		}
      	},
      
      	socketNotificationReceived: function (notification, payload) {
      		//Received config from client
      		if (notification === "CONFIG") {
      			//set config to config send by client
      			this.config = payload;
      			//if monitor has not been started before (makes sure it does not get started again if the web interface is reloaded)
      			if (!this.started) {
      				//set started to true, so it won't start again
      				this.started = true;
      				console.log("Received config for " + this.name);
      
      				this.parseVcardFile();
      				this.setupMonitor();
      			}
      			//send fresh data to front end (page might have been refreshed)
      			if (this.config.password !== "") {
      				this.loadDataFromAPI();
      			}
      		}
      		if (notification === "RELOAD_CALLS") {
      			this.loadDataFromAPI("--calls-only");
      		}
      		if (notification === "RELOAD_CONTACTS") {
      			this.loadDataFromAPI("--contacts-only");
      		}
      	},
      
      	setupMonitor: function () {
      		//helper variable so that the module-this is available inside our callbacks
      		var self = this;
      
      		//Set up CallMonitor with config received from client
      		var monitor = new CallMonitor(this.config.fritzIP, this.config.fritzPort);
      
      		// ====================================================================
      		// CHANGES for wuermchen — buffer inbound instead of firing immediately
      		// ====================================================================
      		monitor.on("inbound", function (call) {
      			//If caller is not empty
      			if (call.caller == "") return;
      			var payload = self.getName(call.caller);
      			var timer = setTimeout(function () {
      				self.pendingInbound.delete(call.id);
      				self.sendSocketNotification("call", payload);
      			}, SPAM_SUPPRESS_WINDOW_MS);
      			self.pendingInbound.set(call.id, timer);
      		});
      		// ====================================================================
      
      		monitor.on("outbound", function (call) {
      			//Save own number (call.caller) to ownNumbers Array to distinguish inbound/outbound on "connected" handler
      			if (!self.ownNumbers.includes(call.caller))
      				self.ownNumbers.push(call.caller)
      				self.sendSocketNotification("outbound", call.called);
      
      		});
      
      		//Call blocked
      		monitor.on("blocked", function (call) {
      				var name = call.type === "blocked" ? self.getName(call.called) : self.getName(call.caller);
      				//send clear command to interface
      				self.sendSocketNotification("blocked", self.getName(call.caller)); //{ "caller": name, "duration": call.duration });
      		});
      
      		//Call accepted
      		monitor.on("connected", function (call) {
      			// CHANGES for wuermchen — flush pending inbound timer before firing connected
      			var pending = self.pendingInbound.get(call.id);
      			if (pending) {
      				clearTimeout(pending);
      				self.pendingInbound.delete(call.id);
      			}
      			var name = self.ownNumbers.includes(call.caller) ? self.getName(call.called) : self.getName(call.caller);
      			var direction = self.ownNumbers.includes(call.caller) ? "out" : "in";
      			self.sendSocketNotification("connected", { "caller": name, "direction": direction });
      
      		});
      
      		//Caller disconnected
      		monitor.on("disconnected", function (call) {
      			// CHANGES for wuermchen — if inbound was still buffered, this is a spam call: suppress both
      			var pending = self.pendingInbound.get(call.id);
      			if (pending) {
      				clearTimeout(pending);
      				self.pendingInbound.delete(call.id);
      				return;   // never showed the pop-up, no need to clear it
      			}
      			var name = call.type === 'outbound' ? self.getName(call.called) : self.getName(call.caller);
      			//send clear command to interface
      			self.sendSocketNotification("disconnected", { "caller": name, "duration": call.duration });
      		});
      		console.log(this.name + " is waiting for incoming calls.");
      	},
      
      	parseVcardFile: function () {
      		var self = this;
      
      		if (!this.config.vCard) {
      			return;
      		}
      		vcard.parseVcardFile(self.config.vCard, function (err, data) {
      			//In case there is an error reading the vcard file
      			if (err) {
      				self.sendSocketNotification("error", "vcf_parse_error");
      				if (self.config.debug) {
      					console.log("[" + self.name + "] error while parsing vCard " + err);
      				}
      				return
      			}
      
      			//For each contact in vcf file
      			for (var i = 0; i < data.length; i++) {
      				//For each phone number in contact
      				for (var a = 0; a < data[i].phone.length; a++) {
      					//normalize and add to AddressBook
      					self.AddressBook[self.normalizePhoneNumber(data[i].phone[a].value)] = data[i].fullname;
      				}
      			}
      			self.sendSocketNotification("contacts_loaded", Object.keys(self.AddressBook).length);
      		});
      	},
      
      	loadCallList: function (body) {
      		var self = this;
      
      		xml2js.parseString(body, function (err, result) {
      			if (err) {
      				self.sendSocketNotification("error", "calllist_parse_error");
      				console.error(self.name + " error while parsing call list: " + err);
      				return;
      			}
      			var callArray = result.root.Call;
      			var callHistory = []
      
      			for (var index in callArray) {
      				var call = callArray[index];
      				var type = call.Type[0];
      				//Trying to handle blocked calls these lines 164-171 are new from "if to else" and it works!
      				if  (name = type == CALL_TYPE.BLOCKED || type == CALL_TYPE.INCOMING ? self.getName(call.Caller[0]) : self.getName(call.Called[0]));
      					var duration = call.Duration[0];
      					if (type == CALL_TYPE.INCOMING && self.config.deviceFilter && self.config.deviceFilter.indexOf(call.Device[0]) > -1) {
      						continue;
      				}
      				else
      				//From here the original script is ongoing
      				var name = type == CALL_TYPE.MISSED || type == CALL_TYPE.INCOMING ? self.getName(call.Caller[0]) : self.getName(call.Called[0]);
      				var duration = call.Duration[0];
      					if (type == CALL_TYPE.INCOMING && self.config.deviceFilter && self.config.deviceFilter.indexOf(call.Device[0]) > -1) {
      						continue;
      				}
      				var callInfo = { "time": moment(call.Date[0], "DD.MM.YY HH:mm"), "caller": name, "type": type, "duration": duration };
      				if (call.Name[0]) {
      					callInfo.caller = call.Name[0];
      				}
      				callHistory.push(callInfo)
      		}
      			self.sendSocketNotification("call_history", callHistory);
      		});
      	},
      
      	loadPhonebook: function (body) {
      		var self = this;
      
      		xml2js.parseString(body, function (err, result) {
      			if (err) {
      				self.sendSocketNotification("error", "phonebook_parse_error");
      				if (self.config.debug) {
      					console.error(self.name + " error while parsing phonebook: " + err);
      				}
      				return;
      			}
      			var contactsArray = result.phonebooks.phonebook[0].contact;
      			for (var index in contactsArray) {
      				var contact = contactsArray[index];
      				var contactNumbers = contact.telephony[0].number;
      				var contactName = contact.person[0].realName;
      
      				for (var index in contactNumbers) {
      					var currentNumber = self.normalizePhoneNumber(contactNumbers[index]._);
      					self.AddressBook[currentNumber] = contactName[0];
      				}
      			}
      			self.sendSocketNotification("contacts_loaded", Object.keys(self.AddressBook).length);
      		});
      	},
      
      	loadDataFromAPI: function (additionalOption) {
      		var self = this;
      
      		if (self.config.debug) {
      			console.log('Starting access to FRITZ!Box...');
      		}
      
      		let args = ['-i', self.config.fritzIP, '-p', self.config.password];
      		if (self.config.username !== "") {
      			args.push('-u');
      			args.push(self.config.username);
      		}
      		if (additionalOption) {
      			args.push(additionalOption);
      		}
      
      		let options = {
      			pythonPath: 'python3',
      			mode: 'json',
      			scriptPath: path.resolve(__dirname),
      			args: args
      		};
      
      		let pyshell = new PythonShell('fritz_access.py', options);
      
      		pyshell.on('message', function (message) {
      			if (message.filename.indexOf("calls") !== -1) {
      				//Call list file
      				self.loadCallList(message.content);
      			} else {
      				//Phone book file
      				self.loadPhonebook(message.content);
      			}
      		});
      
      		//End the input stream and allow the process to exit
      		pyshell.end(function (error) {
      			if (error) {
      				var errorUnknown = true;
      				if (error.traceback.indexOf("XMLSyntaxError") !== -1) {
      					//Password is probably wrong
      					self.sendSocketNotification("error", "login_error");
      					errorUnknown = false;
      				}
      				if (error.traceback.indexOf("failed to load external entity") !== -1) {
      					//Probably no network connection
      					self.sendSocketNotification("error", "network_error");
      					errorUnknown = false;
      				}
      				if (errorUnknown) {
      					self.sendSocketNotification("error", "unknown_error");
      				}
      				if (self.config.debug) {
      					console.error(self.name + " error while accessing FRITZ!Box: ");
      					console.error(error.traceback);
      				}
      				return;
      			}
      			if (self.config.debug) {
      				console.log('Access to FRITZ!Box finished.');
      			}
      		});
      	}
      });
      
      
      posted in Troubleshooting
      R
      rkorell
    • RE: MMM-FRITZ-Box-Callmonitor-py3 + MMM-Callmonitor-Current-Call Window

      dear @wuermchen,

      this should be a classic race condition and should be solvable in node_helper.js without much effort.

      Background: The FRITZ!Box call monitor (port 1012) tags every event of a call with the same ConnectionId:

      date;RING;<id>;<caller>;<callee>;...
      date;DISCONNECT;<id>;<duration>;
      

      For numbers blocked by the FRITZ!Box, RING and DISCONNECT arrive within a few milliseconds. The module currently fires the notification immediately
      on RING — that’s why you end up with two overlapping pop-ups and the browser crashes.

      Fix: Don’t propagate RING immediately. Buffer it briefly, and if a DISCONNECT for the same ConnectionId arrives within a short window, drop the
      event entirely.

      Rough sketch for node_helper.js:

      const SUPPRESS_WINDOW_MS = 1500;
      const pendingRings = new Map();   // connectionId -> timer
      
      // on RING:
      const timer = setTimeout(() => {
        pendingRings.delete(connectionId);
        this.sendSocketNotification("call", ringPayload);   // original notify
      }, SUPPRESS_WINDOW_MS);
      pendingRings.set(connectionId, timer);
      
      // on DISCONNECT:
      const timer = pendingRings.get(connectionId);
      if (timer) {
        clearTimeout(timer);
        pendingRings.delete(connectionId);
        return;   // spam call suppressed, no notification at all
      }
      // otherwise: handle DISCONNECT as before
      

      Why this works:

      • Only suppresses calls that terminate almost instantly (FRITZ!Box block list, “do not disturb”)
      • Regular missed calls (phone rings 10 s, nobody answers) still trigger the pop-up — just delayed by ~1.5 s, which is invisible in practice
      • No second pop-up, no browser crash

      Tuning: 1500 ms is a safe default. You can lower it to 500–800 ms if you want faster feedback, or expose it as a config option

      suppressWindow: 1500,
      

      Hope that helps.
      Warmest regards,

      Ralf

      posted in Troubleshooting
      R
      rkorell
    • RE: PIR / MQTT - Presence sensor(s) revived

      MMM-PresenceScreenControl — two new releases (ecoMode + Notification API)

      Dear all

      a small but meaningful round of updates for MMM-PresenceScreenControl just landed on main. Both started from a single GitHub issue, so I thought I’d write up the chain of reasoning rather than just drop a changelog — maybe useful for anyone evaluating a migration from MMM-Pir, and definitely useful as a reminder to myself to check the parent project’s feature surface more carefully when forking.

      Part 1 — ecoMode (Issue #5)

      Rocket78 opened issue #5 with a really well-argued request: even with the screen physically off, Electron keeps repainting hidden DOM. On a Pi 3 with X11, MMM-PresenceScreenControl was leaving things like Newsfeed cross-fades running every 20 seconds — and those repaints showed up clearly as CPU spikes in his graph. He asked for an equivalent of MMM-Pir’s ecoMode, which hides every other module while the screen is off, so the browser skips layout/paint/composite for them.
      So I built it, but tried to keep it lean:

      ecoMode: false,        // opt-in, default off → no surprise
      ecoModeIgnore: []      // module names to keep visible
      

      Implementation notes that might interest other module authors:

      Rocket78 also dropped a great ddcutil snippet in the issue for monitors that show a “no signal” splash when the video output is cut — that splash is genuinely immersion-breaking, and ddcutil setvcp D6 sends the actual DDC/CI power command, which is much closer to pressing the hardware power button:

      onCommand:  ddcutil setvcp D6 1 --skip-ddc-checks
      offCommand: ddcutil setvcp D6 5 --skip-ddc-checks
      

      Added to the README’s offCommand examples. The --skip-ddc-checks is needed because some monitors stop responding to DDC queries when powered off but still process incoming power-on commands — so the check would fail before the command is even sent.

      → Issue #5 closed, commit 42b68a6.

      Part 2 — what else did I miss?

      Closing the issue could have been the end of it. But Rocket78’s request raised an uncomfortable question: I never used ecoMode in MMM-Pir myself, so I didn’t notice it was missing. What else did I overlook when reviving MMM-Pir?

      Three items jumped out as actual gaps that other modules in the ecosystem could legitimately depend on:

      1. MMM_PIR-USER_PRESENCE — MMM-Pir broadcasts this notification on presence transitions. Other modules (Remote-Control, voice assistants, smart-home bridges) can listen for it.

      2. MMM_PIR-WAKEUP / LOCK / UNLOCK / END — incoming notifications that let other modules control the screen logic externally.

      3. MMM_PIR-SCREEN_POWERSTATUS — broadcast when the physical screen turns on or off.

      Without these, anyone migrating from MMM-Pir to my fork would suddenly find their automations dead — because they’d be listening for MMM_PIR-USER_PRESENCE and nothing was coming through. Even worse, they wouldn’t know why it stopped working; the screen-on/off behavior would seem fine, but cross-module integration would be silently broken.

      So I built a parallel notification API, namespaced MMM_PSC-* rather than impersonating MMM-Pir’s MMM_PIR-*:

      Outgoing notifications — emitted on state transitions only:

      MMM_PSC-USER_PRESENCE       payload: true / false
                                  fires when combined presence changes
      
      MMM_PSC-SCREEN_POWERSTATUS  payload: true / false
                                  fires when physical screen turns on/off
      

      Both fire only on actual transitions — no spam every poll cycle.

      Incoming notifications — consumed by the module

      MMM_PSC-WAKEUP   wake screen, reset timer (equivalent to a touch)
      
      MMM_PSC-END      force screen off immediately
      
      MMM_PSC-LOCK     freeze presence handling — sensors are still tracked internally, but no longer change screen state
      
      MMM_PSC-UNLOCK   resume normal presence handling and re-evaluate the current sensor state
      

      Implementation detail worth flagging: the LOCK guard sits in updatePresence(), which is the single funnel through which all sensor inputs (PIR, MQTT, touch, external wakeup socket) eventually pass. So whatever the trigger source, it’s correctly suppressed while locked. UNLOCK calls updatePresence() again, which re-evaluates the current sensor state — so if you’ve unlocked while a person is still in front of the PIR, the screen comes back on immediately. No need for the caller to send a WAKEUP after UNLOCK.

      Useful patterns this enables:

      // Wake the mirror when a doorbell event arrives:
      this.sendNotification("MMM_PSC-WAKEUP");
      
      // Force-off cleanly from outside (smart-home rule, etc.) without
      // bypassing the module and going straight to the screen command:
      this.sendNotification("MMM_PSC-END");
      
      // Take exclusive control of the display for a video call,
      // then hand it back when done:
      this.sendNotification("MMM_PSC-LOCK");
      // ... your module is showing its full-screen content ...
      this.sendNotification("MMM_PSC-UNLOCK");
      

      END is the clean way to force-off the screen from outside without touching offCommand directly — the module’s internal state stays consistent, all the right outgoing notifications still fire, and any other listeners (logging, analytics, smart home) see the transition.

      Tested live end-to-end via MMM-Remote-Control’s notification API:

      curl -X GET "http://localhost:8080/api/notification/MMM_PSC-LOCK"
      curl -X GET "http://localhost:8080/api/notification/MMM_PSC-END"
          → screen off, stays off
      curl -X GET "http://localhost:8080/api/notification/MMM_PSC-WAKEUP"
          → ignored (locked)
      curl -X GET "http://localhost:8080/api/notification/MMM_PSC-UNLOCK"
          → screen back on
      

      All four routes verified, log trace clean, outgoing notifications fired in the right order on every transition.

      → Commit 10000ca.

      Update / install

      If you’re already on the module:

      cd ~/MagicMirror/modules/MMM-PresenceScreenControl
      rm -rf node_modules
      git pull
      npm install
      

      Both changes are backwards-compatible. ecoMode is opt-in (default false), and the new notifications are additive — nothing existing is modified. So you can update without touching your config and pick up only what you need.

      A genuine thank-you to Rocket78 for the well-reasoned issue and the ddcutil tip — it triggered the full audit, which in turn closed a much bigger latent gap.

      Exactly the kind of feedback that improves a fork. If you’ve migrated from MMM-Pir and notice anything else missing that you’d consider load-bearing, please open an issue.

      Hope you find it useful.
      Warmest regards,
      Ralf

      posted in System
      R
      rkorell
    • RE: MMM-Globe: Meteosat imagery broken — fork with fix available

      Good afternoon, all!
      europeDiscNat is back! (plus a small CSS fix for a new white footer)

      for you, guys a quick update on the European satellite image situation.

      Since my fork EUMETSAT’s new WMS endpoint at view.eumetsat.int/geoserver/wms works fast, reliably and nice.

      Yesterday that stopped delivering fresh images!

      Today I figured out there even is a new version (small changes in URL) v1.1.0 is now v1.3.0 API - but both of them are only returning a stale image from yesterday evening:

      https://view.eumetsat.int/geoserver/wms?service=WMS&version=1.1.0&request=GetMap&layers=msg_fes:rgb_naturalenhncd&...
      https://view.eumetsat.int/geoserver/wms?service=WMS&version=1.3.0&request=GetMap&layers=msg_fes:rgb_naturalenhncd&...
      

      But here’s the surprise: the old europeDiscNat URL is back and working again!
      So if you’ve been in trouble with looking for actual pictures without the SLIDER API, this works right now:

      style: 'europeDiscNat',
      

      is re-born …

      One thing to watch out for: Unfortunately EUMETSAT has changed the image format slightly. The footer bar at the bottom (logo + timestamp) used to be black — invisible against the MagicMirror background. It’s now white and a bit taller, which causes a small white artifact at the bottom of the globe.

      I’ve pushed a fix that adds a black CSS overlay to mask the footer. Just update the module:

      cd ~/MagicMirror/modules/MMM-Globe
      git pull
      npm install
      

      Of course the SLIDER styles (geoColorEurope, geoColorUSA, etc.) continue to work fine if you prefer those.

      Happy mirroring!

      Warmest regards,
      Ralf

      posted in Showcase
      R
      rkorell
    • RE: Wayland problems in the April 2026 release

      Dear @howest,

      Glad you got it working again! The Wayland transition is indeed a bit of a bumpy ride if it hits you unexpectedly.

      Just to share a different experience: I’m running MagicMirror on a Raspberry Pi 5 with Raspberry Pi OS Trixie, and Wayland works fine for me – including keyboard input.

      The wireless combo keyboard/mouse issue you described is a known pain point; Wayland handles HID devices differently than X11, and some receivers just don’t play nice out of the box.

      If you ever feel adventurous and want to try Wayland again: check if your keyboard receiver shows up cleanly under

      libinput list-devices.
      

      Sometimes a powered USB hub or simply a different receiver firmware makes all the difference.
      But honestly – if X11 runs stable and you’re happy, there’s zero pressure to switch. Wayland on the Pi is still maturing. Your setup, your rules! 😊

      Warm regards,
      Ralf

      posted in General Discussion
      R
      rkorell
    • RE: MMM-RainRadarDWD

      @OliWer

      Absolutely fine for me. Just a hint - as improvement path for further upcoming modules from your side :-)

      Warm regards,
      Ralf

      posted in Utilities
      R
      rkorell
    • RE: MMM-RainRadarDWD

      @OliWer cool idea, thanks!
      You could save a lot of your parameters (all of the “// — Language & Text —” section) if you use the translate - capability…
      But with your approach it’s straight forward and really “all languages” - intersting idea!

      Warm regrds,
      Ralf

      posted in Utilities
      R
      rkorell
    • RE: Wayland problems in the April 2026 release

      @parnic said:

      I was able to modify my startup scripts to use start:x11 instead of start and it came back online

      Dear parnic,
      if you can start your mirror with x11 - than you have configured your pi for x11 - and wayland will not work! As far as I know this is either-or, not in parallel…

      This is not related to the mirror upgrade, I guess.

      You can change X11/wayland with raspi-config (with sudo) …

      Hope this helps - good luck!

      Ralf

      posted in General Discussion
      R
      rkorell
    • RE: AI Coding Tools Infuse a new Life in MagicMirror

      @Rags yes, it is :-)

      posted in General Discussion
      R
      rkorell
    • RE: AI Coding Tools Infuse a new Life in MagicMirror

      @sdetweil

      I am using the cline integration (Sonnet 4.6) with VS to enhance some firmware (build env in VS), and its been interesting.

      I‘m using claude code - installed directly on Mirror‘s RasPi and it (mostly :-) ) works like a charm.
      Indeed you need a subscription and several times „auto compressions“ of the current chat happens.
      But if one insist in writing important information consequently to CLAUDE.md as well as to „memory“ even every fresh started session has pretty much context…

      Warm regards,
      Ralf

      posted in General Discussion
      R
      rkorell
    • RE: MMM-Globe: Meteosat imagery broken — fork with fix available

      @manu85340 :-) Thank You!
      Glad, if it works for you!

      Ralf

      posted in Showcase
      R
      rkorell
    • RE: PIR / MQTT - Presence sensor(s) revived

      I’ve just seen that you had invented a solution for your problem - MMM-HideModulesOnSpotify :-)
      Cool!

      Ralf

      posted in System
      R
      rkorell
    • RE: MMM-FRITZ-Box-Callmonitor-py3 and MMM-Callmonitor-Current-Call

      @MiPraSo cool! thanks for this confirmation.
      Glad to hear it works for you!

      Regards,
      Ralf

      posted in Utilities
      R
      rkorell
    • RE: PIR / MQTT - Presence sensor(s) revived

      Dear @htilburgs,

      Yes, I Like your garbage module - it’s really great - thanks for this!
      I’m using MMM-NowPlayingOnSpotify …
      This has exactly the behavioour as described.
      Shows a spotify logo - which I had replaced with Volumio logo (which is my favourite spotify player) and only expands if something is played.
      In this case the left corner looks like

      screen2.jpg

      Warm regards,
      Ralf

      posted in System
      R
      rkorell
    • RE: PIR / MQTT - Presence sensor(s) revived

      @htilburgs screen.jpg
      I have a quite similar layout.
      My MusicPlayer (Volumio) is on the left side, too but is spreading the region if cover-art is appearing…
      Warmest regards,
      Ralf

      posted in System
      R
      rkorell
    • RE: MMM-Globe: Meteosat imagery broken — fork with fix available

      @plainbroke you’re really welcome.
      Thanks for your patience :-)

      Ralf

      posted in Showcase
      R
      rkorell
    • RE: PIR / MQTT - Presence sensor(s) revived

      @htilburgs cool!
      happy, that you are satisfied!

      Warm regards,
      Ralf

      interesting that you are poistion this counterbar on thr right side of the screen.
      For me it feels/looks more natural on the left side.
      May this is the reason for my “acceptance” of the colorFrom / colorTo - “mismatch” you had reported …

      posted in System
      R
      rkorell
    • RE: PIR / MQTT - Presence sensor(s) revived

      @htilburgs said:

      I’m looking forward for the startupGracePeriod parameter and think this is going to make the module fully as I like it.

      Good news — your wish came true faster than expected! 😊

      v1.5.0 is released and includes the startupGracePeriod parameter you were looking forward to.

      How to update:

      cd ~/MagicMirror/modules/MMM-PresenceScreenControl
      rm -rf node_modules
      git pull
      npm install
      

      Then add to your config:

      startupGracePeriod: 30,  // seconds to keep screen on after startup
      

      Set it to however many seconds you want the screen to stay on after a restart — enough time to
      verify everything came up correctly. After the grace period, normal presence logic kicks in. If
      your PIR detects you during the grace period, it seamlessly switches to the regular countdown
      timer.

      Also included in v1.5.0:

      • logFileName parameter — debug output now goes to pm2 logs by default (no more hidden log file)
      • Several internal fixes found during a code quality review

      Full changelog in the README.

      Enjoy! 🎉

      Warm regards,
      Ralf

      posted in System
      R
      rkorell
    • RE: PIR / MQTT - Presence sensor(s) revived

      @htilburgs

      YES, IT IS WORKING!!!

      I’m SO happy.
      Great news - congratulations…
      So at least your stubborn issue leds to several code enhancements - during my investigation regarding your symptoms I had the chance to identify some optimization potential, so code is much cleaner now.
      Thanks for this gentle “push”.

      Warmest regards,
      Ralf

      posted in System
      R
      rkorell
    • 1
    • 2
    • 3
    • 4
    • 5
    • 21
    • 22
    • 1 / 22