Hi,
i found a small bug. At this moment you need to change your inverter-IP in the node_helper.js file.
I‘ll fix this to a config entry this weekend.
Hi,
i found a small bug. At this moment you need to change your inverter-IP in the node_helper.js file.
I‘ll fix this to a config entry this weekend.
Hi,
i was working the last days on a module to get rid of the last iframes on my mirror. These modules can all visualize solar energy data from a Fronius solar system using SVG magic :)
If you do not have a FroniusPV but your system has an API it should be not much work to adjust the node_helper file.
All in a column:

Hi,
I have created a quick module to visualize outside temperatures from the Temp2IOT Sensor:

Get your own Sensor: Temp2IOT
-1 iFrame on my mirror :)
@sdetweil thats why I made the module. The Pi is completely headless. So I’m in the kitchen with my laptop and enjoy this autorefresh now. Before that I’ve used a Script on my Smartphone. Also manual and annoying :-)
And sure, also the xdotool command is just pressing “ctrl+r” for me.
Hi,
I’m currently working on a new module. I had a lot of updates to do in my code to adjust the layout. After saving changes, I had to manually refresh the MagicMirror application every time. This process is tedious, especially if you’re frequently experimenting with configurations.
That’s where this script comes in. By monitoring your config.js file (or a module.js file) for changes, it automates the refresh process. Now, every time I save my config.js, the Mirror refreshes itself instantly.
It’s seamless, efficient, and saves you from the hassle of restarting the application manually.
Perfect for developers and tinkerers alike, this script ensures your MagicMirror reflects your updates in real-time, letting you focus on creativity instead of logistics.
https://github.com/ChrisF1976/MM-Watcher
Give it a try…
Regards,
Chris.
@sdetweil no, the start time is relevant. If the start time is in the past and the effect time may be stll okay like 3652460601000, the effect does not start. This could be the problem here. So you really need to wait for the defined time in the config.
I‘ve spent several minutes with waiting….
@com1cedric see my answer on GitHub. It only starts if the start time is exactly now or in the future.
Hello,
unfortunately even more optimizations were necessary. So I update the repository.
New:
Reduced effects:
createExplosion(x, y) {
const particleCount = 25; // was 50
this.particles.forEach((p) => {
p.x += p.vx;
p.y += p.vy;
p.vy += 0.01; // less gravity
p.alpha -= 0.01;
});
setup() {
this.canvas.width = this.width*0.95 //reduced width
this.canvas.height = this.height*0.95 //reduced height
window.addEventListener("resize", () => this.resize());
}
Hopefully you’ll celebrate this, too!
Hello,
a short update since time is running out :)
I’ve modified the code a little. In my set up I had some performance problems with the animation.
So I put all other modules in the “Hide” during the animation is running. Afterwards everything will come back (so the plan).
I don’t know if everybody likes or needs this. So I show the modified code just here.
Replace the complete code in “MMM-Fireworks.js” with this:
Module.register("MMM-Fireworks", {
defaults: {
startDateTime: "2024-12-31T23:59:00", // ISO format
duration: 60000, // Duration in milliseconds
},
start: function () {
this.fireworksActive = false;
},
getDom: function () {
const wrapper = document.createElement("div");
wrapper.id = "fireworksContainer";
return wrapper;
},
notificationReceived: function (notification, payload, sender) {
if (notification === "DOM_OBJECTS_CREATED") {
this.scheduleFireworks();
}
},
scheduleFireworks: function () {
const startTime = new Date(this.config.startDateTime).getTime();
const currentTime = Date.now();
const delay = startTime - currentTime;
if (delay > 0) {
setTimeout(() => this.startFireworks(), delay);
} else {
console.warn("Fireworks start time has already passed.");
}
},
startFireworks: function () {
this.fireworksActive = true;
const container = document.getElementById("fireworksContainer");
container.classList.add("fullscreen");
const canvas = document.createElement("canvas");
canvas.id = "fireworksCanvas";
container.appendChild(canvas);
this.sendNotificationToModules("HIDE"); // Module ausblenden
const fireworks = new Fireworks(canvas);
fireworks.start();
setTimeout(() => {
this.stopFireworks(fireworks, container);
}, this.config.duration);
},
stopFireworks: function (fireworks, container) {
fireworks.stop();
container.innerHTML = ""; // Clear the canvas
this.fireworksActive = false;
this.sendNotificationToModules("SHOW"); // Module wieder einblenden
},
sendNotificationToModules: function (action) {
MM.getModules().enumerate((module) => {
if (module.name !== "MMM-Fireworks") {
if (action === "HIDE") {
module.hide(500); // Mit Animation ausblenden
} else if (action === "SHOW") {
module.show(500); // Mit Animation einblenden
}
}
});
},
getStyles: function () {
return ["MMM-Fireworks.css"];
},
getScripts: function () {
return ["fireworks.js"];
}
});
Party on!
Cheers Chris.
Hi,
I’ve created a quick module for New Year’s Eve or other events you’d like to highlight on your mirror.
The MMM-Fireworks module brings a visually stunning fireworks display to your MagicMirror, designed to celebrate special occasions. The fireworks animation runs best in the fullscreen_above region, creating an immersive experience. The start time and duration of the display are configurable via the config.js file.

Have fun and take care :-)
Chris.
Hi guys,
I’ve created a new module. My motivation was to see what’s up around me in my city. I’ve missed so many events in my life, so I was searching for an event reminder. But I didn’t find anything that fits to my belongings. So I decided to create something on my own:
The MMM-EventSearch module for MagicMirror fetches and displays event information using the SerpAPI to search for events based on a given query, location, and other parameters. It uses in this case google (see SerpApi for other options) and displays the list results from google which everyone has seen already.
To find a suitable query go to goolge and try a little for your city or surrounding city/provice/state.
The results are currently limited to the the results that google initally shows. So the “mode results” button does not work or is not accessible with this API!
The module dynamically generates a table with event details, displaying:
Event Fetching:
Events are refreshed periodically based on the updateInterval. On the free plan from SerpAPI 100 calls are free per month. So be careful.
CSS Styling is a little acc. to Google results.

Have fun!
Cheers
Chris.
Hello,
I’ve added one nice interactions to the module now. This took me some days to get it fully running. But now I’m quite happy with the result, so I also share it here again:
The state is written in the state.json file. So now your kids can “really” open a door in the morning and if you shut down your mirror in the night it has the same state after a reboot.
If you’re already running the module delete the state.json file if you feel that something does not work correct (I deleted it quite often during development so I can’t say if there could be an issue).
The update sounds easy but believe me for me it was not ;-)
@KristjanESPERANTO Done. I’m not yet completely happy with the style. I’ll work on it next Summer :)
Hello,
I’ve created a quick module for an advent calendar. Is is customizable and the images can be chosen individual.
I’ve made this module quite quick after I got the ideas yesterday morning. I’ll refine it a little but I did’t want to let you wait any longer g
It could contain some bugs and may not be perfect at this time. Let me know what you notice, and I will try to improve it.

@visionmaster Danke für die Antwort. Die Module sind in GitHub aktualisiert.
So, issue closed until new devices pop up ;-)
@visionmaster
Vielleicht einfacher als gedacht. Wieder als node_helper.js übernehmen:
const NodeHelper = require("node_helper");
const axios = require("axios");
module.exports = NodeHelper.create({
start: function () {
this.config = {};
setTimeout(() => {
console.log("MagicMirror is ready. Fetching ShellyPV status...");
this.fetchShellyPVStatus();
}, 15000); // Delay to ensure MagicMirror is fully loaded
},
socketNotificationReceived: function (notification, payload) {
if (notification === "CONFIG") {
this.config = payload;
if (this.config.shellysPV && Array.isArray(this.config.shellysPV)) {
console.log("Configuration received, fetching ShellyPV status...");
this.fetchShellyPVStatus();
}
} else if (notification === "GET_SHELLYPV_STATUS") {
this.fetchShellyPVStatus();
}
},
fetchShellyPVStatus: async function () {
const results = [];
if (!this.config.shellysPV || !Array.isArray(this.config.shellysPV)) {
console.error("No valid shellysPV configuration found or 'shellysPV' is not an array.");
return;
}
for (const shellyPV of this.config.shellysPV) {
try {
const response = await axios.post(
`${this.config.serverUri}/device/status`,
`id=${shellyPV.id}&auth_key=${this.config.authKey}`
);
const data = response.data?.data?.device_status;
if (data) {
let isOn = false;
let power = null;
// Check for Gen 1/2 structure (relay-based devices)
if (data.relays) {
const channel = parseInt(shellyPV.ch || 0, 10);
isOn = data.relays[channel]?.ison || false;
power = data.meters?.[channel]?.power || null;
}
// Check for Gen 3 structure (pm1:0 devices)
else if (data["pm1:0"]) {
isOn = true; // Assume true if data exists for the device
power = data["pm1:0"].apower; // Use 'apower' from pm1:0
}
// Check for "switch:0" structure
else if (data["switch:0"]) {
isOn = data["switch:0"].output; // Use 'output' for on/off status
power = data["switch:0"].apower; // Use 'apower' for power
}
// Check for RGB Shelly structure (lights array)
else if (data.lights) {
const light = data.lights[0]; // Assume single channel for RGB device
isOn = light?.ison || false;
power = light?.power || null;
}
results.push({
name: shellyPV.name,
isOn: isOn,
power: power !== undefined ? power : null,
statusClass: isOn ? 'on' : 'off', // Dynamically set status class
});
} else {
results.push({
name: shellyPV.name,
isOn: false,
power: null,
statusClass: 'off', // Default to off if no data found
});
}
} catch (error) {
console.error(`Error fetching status for ${shellyPV.name}:`, error);
results.push({
name: shellyPV.name,
isOn: false,
power: null,
statusClass: 'off', // Default to off on error
});
}
}
this.sendSocketNotification("SHELLYPV_STATUS_UPDATE", results);
},
});
In der config wieder wie üblich einrichten.
@visionmaster
Perfekt. Freut mich! Morgen probiere mich an der RGB Shelly. Kann es aber noch nicht versprechen. Wenn das läuft aktualisiere ich die Module für Table und PV bei Github.
Hi,
please replace the node_helper.js in the module MMM-ShellyPV with the following code. If you receive an error, please describe what you see or what error messages you get. Since I do not have such a device yet it is not possible for me to test it.
At first I start with the Shelly 2.5 device. The “shellyrgbw2” is then the next step!
The config must be like this for these specific 2.5 devices:
shellysPV: [
{ name: "Kueche 1", id: "dcda0cb93bb8", ch: "1" }, // Channel 1 (relay[0])
{ name: "Kueche 2", id: "dcda0cb93bb8", ch: "2" } // Channel 2 (relay[1])
]
I’ve used 1 and 2 instead of 0 and 1. It is easier for my head :-)
And next the updated node_helper.js file:
const NodeHelper = require("node_helper");
const axios = require("axios");
module.exports = NodeHelper.create({
start: function () {
this.config = {};
setTimeout(() => {
console.log("MagicMirror is ready. Fetching ShellyPV status...");
this.fetchShellyPVStatus();
}, 15000); // Delay to ensure MagicMirror is fully loaded
},
socketNotificationReceived: function (notification, payload) {
if (notification === "CONFIG") {
this.config = payload;
if (this.config.shellysPV && Array.isArray(this.config.shellysPV)) {
console.log("Configuration received, fetching ShellyPV status...");
this.fetchShellyPVStatus();
}
} else if (notification === "GET_SHELLYPV_STATUS") {
this.fetchShellyPVStatus();
}
},
fetchShellyPVStatus: async function () {
const results = [];
if (!this.config.shellysPV || !Array.isArray(this.config.shellysPV)) {
console.error("No valid shellysPV configuration found or 'shellysPV' is not an array.");
return;
}
for (const shellyPV of this.config.shellysPV) {
try {
const response = await axios.post(
`${this.config.serverUri}/device/status`,
`id=${shellyPV.id}&auth_key=${this.config.authKey}`
);
const data = response.data?.data?.device_status;
if (data) {
let isOn = false;
let power = null;
// Handle multi-channel devices
if (data.relays && shellyPV.ch) {
const channelIndex = parseInt(shellyPV.ch, 10) - 1; // Convert to zero-based index
if (channelIndex >= 0 && channelIndex < data.relays.length) {
const relay = data.relays[channelIndex];
const meter = data.meters?.[channelIndex];
isOn = relay.ison;
power = meter?.power || 0;
} else {
console.error(`Invalid channel index for ${shellyPV.name}: ${shellyPV.ch}`);
}
}
// Handle single-channel devices (or default behavior for Gen 1/2)
else if (data.relays) {
isOn = data.relays[0].ison;
power = data.meters ? data.meters[0].power : null;
}
// Handle Gen 3 devices (pm1:0 structure)
else if (data["pm1:0"]) {
isOn = true; // Assume true if data exists
power = data["pm1:0"].apower;
}
// Handle switch:0 structure
else if (data["switch:0"]) {
isOn = data["switch:0"].output;
power = data["switch:0"].apower;
}
results.push({
name: shellyPV.name,
isOn: isOn,
power: power !== undefined ? power : null,
statusClass: isOn ? 'on' : 'off', // Dynamically set status class
});
} else {
results.push({
name: shellyPV.name,
isOn: false,
power: null,
statusClass: 'off', // Default to off if no data found
});
}
} catch (error) {
console.error(`Error fetching status for ${shellyPV.name}:`, error);
results.push({
name: shellyPV.name,
isOn: false,
power: null,
statusClass: 'off', // Default to off on error
});
}
}
this.sendSocketNotification("SHELLYPV_STATUS_UPDATE", results);
},
});
For devices without a ch parameter, the logic defaults to using the first relay and meter (relays[0], meters[0]), ensuring compatibility with single-channel or older devices.
If a ch parameter is specified in the config, the code now accesses the corresponding relays and meters arrays for the specific channel.
@visionmaster Hi, werde es mir morgen anschauen. Danke für die Infos.
@visionmaster Hi, sehe ich mir an. Solche Geräte habe ich im Moment noch nicht im Einsatz. Schicke in der Console mal diesen Befehl an die entsprechende Shelly und poste mir die Antwort:
curl -X POST https://<server_uri>/device/status -d "id=<device_id>&auth_key=<auth_key>"
In der Antwort Deinen Auth-key löschen!
Vielleicht werden hier mehrere Infos zurückgeschickt.