Read the statement by Michael Teeuw here.
MMM-FroniusSolar family modules
-
@chrisfr1976 everyone with reputation below 2 has every post reviewed (by me) before going live. sometimes i sleep! or do other things. lol
-
Hello Chris
FYI.
They remove node fetch from the magic mirror package on the last release as we drop back to the integrated fetch that’s provided in node and in the electron browser unfortunately that breaks a bunch of modules.Just remove the the line const fetch = require(“fetch”); in the file node-helper.js in the module’s directory.
I Have removed the line, and now it works
-
Tried to install 2 and 3 on an RPI 3B+ running 64
bookworm - will not even load and throws a .js error - IP address configured and did copy and paste for the config.js - mm just stalls -
@sdetweil
Hi,I’ve updated the node _helper.js.
//const fetch = require("node-fetch");
If some has trouble it can be reactivated. Maybe I hab installed node_helper in the past manually. I don’t remember. Until now I had no errors from that. But yes, it woks well without this line.
-
@waynerob11
Please send the error massage that you receive. I want to help you! -
Hi,
Thanks for your response - I am attempting to install Fronius Solar 2 and have commented out the line in node-helper.js as advised in a previous post ( //const fetch = require(“node-fetch”);) and ran MM.It all loaded OK with no errors but it doesn’t show up on the dashboard, doesn’t even say loading. IP address is correct.
Wayne
-
@waynerob11 Hello,
I’ve created a version with console.logs inside. Go to the MMM-FroniusSolar2 module folder and replace the files with this content:MMM-FroniusSolar2.js:
Module.register("MMM-FroniusSolar2", { defaults: { InverterIP: "192.168.178.134", updateInterval: 60000, // Update every 60 seconds icons: { P_Akku: "mdi:car-battery", P_Grid: "mdi:transmission-tower", P_Load: "mdi:home-lightbulb", P_PV: "mdi:solar-panel-large", }, Radius: 80, // Radius for the SVG gauges MaxPower: 1000, // Maximum power for grid, house, and battery MaxPowerPV: 10400, // Maximum power for solar PV ShowText: true, TextMessge: [ { about: "600", Text: "Leicht erhöhter Netzbezug.", color: "#999" }, { about: "1000", Text: "Über 1 KW Netzbezug!", color: "#ffffff" }, { about: "1500", Text: "Über 1,5KW Netzbezug.", color: "#eea205" }, { about: "2500", Text: "Über 2,5KW aus dem Netz!", color: "#ec7c25" }, { about: "5000", Text: "Auto lädt, richtig? Nächstes Mal auf Sonne warten.", color: "#cc0605" }, { less: "-500", Text: "Sonne scheint! Mehr als 500W frei.", color: "#f8f32b" }, { less: "-2000", Text: "Wäsche waschen! Über 2KW freie Energie!", color: "#00bb2d" }, { less: "-4000", Text: "Auto laden! Über 4KW freie Energie!", color: "#f80000" }, ], debug: true, // Set to true to enable debug logging }, start: function () { if (this.config.debug) { console.log("[MMM-FroniusSolar2] Starting module with config:", this.config); } this.solarData = null; this.solarSOC = null; // Added for SOC console.log("[MMM-FroniusSolar2] Sending configuration to node_helper..."); this.sendSocketNotification("SET_CONFIG", this.config); // Send configuration to node_helper this.scheduleUpdate(); // Schedule periodic updates }, getStyles: function () { return ["MMM-FroniusSolar2.css", "https://code.iconify.design/2/2.2.1/iconify.min.js"]; }, scheduleUpdate: function () { if (this.config.debug) { console.log("[MMM-FroniusSolar2] Scheduling updates every", this.config.updateInterval, "ms"); } const self = this; setInterval(function () { if (self.config.debug) { console.log("[MMM-FroniusSolar2] Requesting data update from node_helper..."); } self.sendSocketNotification("GET_FRONIUS_DATA"); }, this.config.updateInterval); }, socketNotificationReceived: function (notification, payload) { if (notification === "FRONIUS_DATA") { if (this.config.debug) { console.log("[MMM-FroniusSolar2] Received FRONIUS_DATA:", payload); } this.solarData = { P_Akku: Math.round(payload.P_Akku || 0), P_Grid: Math.round(payload.P_Grid || 0), P_Load: Math.round(payload.P_Load || 0), P_PV: Math.round(payload.P_PV || 0), }; this.solarSOC = payload.Inverters && payload.Inverters["1"] && payload.Inverters["1"].SOC ? Math.round(payload.Inverters["1"].SOC) : 0; // Fallback to 0 if SOC is undefined if (this.config.debug) { console.log("[MMM-FroniusSolar2] Processed solarData:", this.solarData, "SOC:", this.solarSOC); } this.updateDom(); } }, getDom: function () { if (this.config.debug) { console.log("[MMM-FroniusSolar2] Rendering DOM"); } const wrapper = document.createElement("div"); wrapper.className = "solar2-wrapper"; if (!this.solarData) { wrapper.innerHTML = "Loading..."; return wrapper; } const radius = this.config.Radius || 80; const strokeWidth = 12; const svgSize = 350; // Adjusted SVG height for labels and padding // Recalculate house consumption const outerPower = this.solarData.P_Grid + this.solarData.P_Akku + this.solarData.P_PV; // Fixed positions for the gauges in dice layout const positions = { PV: { x: 75, y: 75 }, Grid: { x: 225, y: 75 }, Akku: { x: 75, y: 225 }, House: { x: 225, y: 225 } }; // Define colors for gauges const gridColor = this.solarData.P_Grid >= 0 ? "#808080" : "#add8e6"; // Gray for positive, light blue for negative const akkuColor = "#00ff00"; // Always green const pvColor = "#ffff00"; // Always yellow // House Gauge: Logic for color determination let houseColor; if (this.solarData.P_Akku - 100 > Math.abs(this.solarData.P_Grid)) { houseColor = "#a3c49f"; // Light green for high battery activity } else if (this.solarData.P_Grid > 150) { houseColor = "#808080"; // Gray for high grid consumption } else if (outerPower > 0) { houseColor = "#00ff00"; // Green for positive power flow } else { houseColor = "#1f84ff"; // Light blue } if (this.config.debug) { console.log("[MMM-FroniusSolar2] Gauge colors determined:", { pvColor, gridColor, akkuColor, houseColor }); } // Create unified SVG const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("width", svgSize); svg.setAttribute("height", svgSize); svg.setAttribute("viewBox", "0 -20 300 350"); // Adjusted viewBox to fix label cutoff svg.style.margin = "auto"; // Center the SVG // Define glow effect const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); defs.innerHTML = ` <filter id="glow"> <feGaussianBlur in="SourceGraphic" stdDeviation="4" result="blurred" /> <feMerge> <feMergeNode in="blurred" /> <feMergeNode in="SourceGraphic" /> </feMerge> </filter> `; svg.appendChild(defs); // Function to create a line const createLine = (x1, y1, x2, y2, color) => { if (this.config.debug) { console.log("[MMM-FroniusSolar2] Creating line:", { x1, y1, x2, y2, color }); } const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); line.setAttribute("x1", x1); line.setAttribute("y1", y1); line.setAttribute("x2", x2); line.setAttribute("y2", y2); line.setAttribute("stroke", color); line.setAttribute("stroke-width", "4"); line.setAttribute("class", "flow-lines"); return line; }; // Add flow lines based on power conditions if (this.solarData.P_Akku < -10 && this.solarData.P_PV > 0 ) { svg.appendChild(createLine(positions.PV.x, positions.PV.y + radius, positions.Akku.x, positions.Akku.y - radius, "#ffff00")); } if (this.solarData.P_PV > 10) { svg.appendChild(createLine(positions.PV.x + (radius * 0.7071), positions.PV.y + (radius * 0.7071), positions.House.x - (radius * 0.7071), positions.House.y - (radius * 0.7071), "#ffff00")); } if (this.solarData.P_Grid > 10) { svg.appendChild(createLine(positions.Grid.x, positions.Grid.y + radius, positions.House.x, positions.House.y - radius, "#808080")); } if (this.solarData.P_Akku > 10) { svg.appendChild(createLine(positions.Akku.x + radius, positions.Akku.y, positions.House.x - radius, positions.House.y, "#00ff00")); } if (this.solarData.P_Grid < -10 && this.solarData.P_PV > Math.abs(this.solarData.P_Grid)) { svg.appendChild(createLine(positions.PV.x + radius, positions.PV.y, positions.Grid.x - radius, positions.Grid.y, "#add8e6")); } if (this.solarData.P_Grid < -10 && this.solarData.P_PV <= 0) { svg.appendChild(createLine(positions.Akku.x + (radius * 0.7071), positions.Akku.y - (radius * 0.7071), positions.Grid.x - (radius * 0.7071), positions.Grid.y + (radius * 0.7071), "#00ff00")); } if (this.solarData.P_PV <= 0 && this.solarData.P_Akku < -10) { svg.appendChild(createLine(positions.Grid.x - (radius * 0.7071), positions.Grid.y + (radius * 0.7071), positions.Akku.x + (radius * 0.7071), positions.Akku.y - (radius * 0.7071), "#808080")); } // Function to create a gauge const createGauge = (x, y, mainValue, subValue, percentage, color, label, icon, labelPosition) => { if (this.config.debug) { console.log("[MMM-FroniusSolar2] Creating gauge:", { label, mainValue, subValue, percentage, color }); } const group = document.createElementNS("http://www.w3.org/2000/svg", "g"); group.setAttribute("transform", `translate(${x},${y})`); // Circle Background const bgCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); bgCircle.setAttribute("cx", 0); bgCircle.setAttribute("cy", 0); bgCircle.setAttribute("r", radius); bgCircle.setAttribute("stroke", "#e0e0e0"); bgCircle.setAttribute("opacity", "1"); bgCircle.setAttribute("stroke-width", strokeWidth); bgCircle.setAttribute("fill", "none"); group.appendChild(bgCircle); // Circle Progress with glow const progressCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); progressCircle.setAttribute("cx", 0); progressCircle.setAttribute("cy", 0); progressCircle.setAttribute("r", radius); progressCircle.setAttribute("stroke", color); progressCircle.setAttribute("stroke-width", strokeWidth); progressCircle.setAttribute("fill", "none"); progressCircle.setAttribute("stroke-dasharray", `${percentage * 2 * Math.PI * radius} ${2 * Math.PI * radius}`); progressCircle.setAttribute("transform", "rotate(-90 0 0)"); progressCircle.setAttribute("filter", "url(#glow)"); // Apply glow filter group.appendChild(progressCircle); // Main Text const mainText = document.createElementNS("http://www.w3.org/2000/svg", "text"); mainText.setAttribute("x", 0); mainText.setAttribute("y", 6); // Adjusted y position for main text mainText.setAttribute("text-anchor", "middle"); mainText.setAttribute("font-size", "22px"); mainText.setAttribute("fill", "#ffffff"); mainText.textContent = mainValue; group.appendChild(mainText); // Sub Text if (subValue) { const subText = document.createElementNS("http://www.w3.org/2000/svg", "text"); subText.setAttribute("x", 0); subText.setAttribute("y", 25); // Adjusted y position for sub text subText.setAttribute("text-anchor", "middle"); subText.setAttribute("font-size", "16px"); subText.setAttribute("fill", "#ffffff"); subText.textContent = subValue; group.appendChild(subText); } // Label with foreignObject for icon const labelY = labelPosition === "top" ? -(radius + 45) : radius + 15; // Increased offset for top labels const labelContainer = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject"); labelContainer.setAttribute("x", -radius - 20); labelContainer.setAttribute("y", labelY); labelContainer.setAttribute("text-anchor", "middle"); labelContainer.setAttribute("width", radius * 2); labelContainer.setAttribute("height", labelPosition === "top" ? 60 : 40); // Adjusted label height labelContainer.innerHTML = ` <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: center; justify-content: center; text-align: center; font-size: 20px; color: white;"> <span class="iconify" data-icon="${icon}" style="margin-right: 5px;"></span> ${label} </div> `; group.appendChild(labelContainer); return group; }; // Add gauges to SVG svg.appendChild(createGauge( positions.PV.x, positions.PV.y, `${this.solarData.P_PV || 0} W`, null, Math.min((this.solarData.P_PV || 0) / this.config.MaxPowerPV, 1), pvColor, "PV", this.config.icons.P_PV, "top" )); svg.appendChild(createGauge( positions.Grid.x, positions.Grid.y, `${this.solarData.P_Grid || 0} W`, null, Math.min(Math.abs(this.solarData.P_Grid || 0) / this.config.MaxPower, 1), gridColor, "Grid", this.config.icons.P_Grid, "top" )); svg.appendChild(createGauge( positions.Akku.x, positions.Akku.y, `${this.solarSOC || 0}%`, `${this.solarData.P_Akku || 0} W`, Math.min(this.solarSOC / 100, 1), akkuColor, "Akku", this.config.icons.P_Akku, "bottom" )); svg.appendChild(createGauge( positions.House.x, positions.House.y, `${outerPower || 0} W`, null, Math.min(Math.abs(outerPower || 0) / this.config.MaxPower, 1), houseColor, "House", this.config.icons.P_Load, "bottom" )); wrapper.appendChild(svg); // Add dynamic text message below the gauge if (this.config.ShowText) { const textMessageDiv = document.createElement("div"); textMessageDiv.className = "text-message2"; const messageConfig = this.config.TextMessge || []; let selectedMessage = null; for (const message of messageConfig) { if ( (message.about && this.solarData.P_Grid > parseInt(message.about)) || (message.less && this.solarData.P_Grid < parseInt(message.less)) ) { // If no message is selected yet, or the new match is more specific if ( !selectedMessage || (message.about && parseInt(message.about) > parseInt(selectedMessage.about || -Infinity)) || (message.less && parseInt(message.less) < parseInt(selectedMessage.less || Infinity)) ) { selectedMessage = message; } } } if (selectedMessage) { if (this.config.debug) { console.log("[MMM-FroniusSolar2] Selected text message:", selectedMessage); } textMessageDiv.innerHTML = ` <span style="color: ${selectedMessage.color}; font-size: 18px;"> ${selectedMessage.Text} </span> `; } else { textMessageDiv.innerHTML = ` <span style="color: #999; font-size: 16px;"> PV Anlage läuft... </span> `; } wrapper.appendChild(textMessageDiv); } return wrapper; } });
node_helper.js::
const NodeHelper = require("node_helper"); module.exports = NodeHelper.create({ config: null, // Initially, no config is set start: function () { console.log("[MMM-FroniusSolar2] Node helper started"); }, socketNotificationReceived: function (notification, payload) { console.log("[MMM-FroniusSolar2] Received socket notification:", notification, payload); if (notification === "SET_CONFIG") { this.config = payload; if (this.config.debug) { console.log("[MMM-FroniusSolar2] Configuration received:", this.config); } this.startFetchingData(); } else if (notification === "GET_FRONIUS_DATA") { if (!this.config) return; if (this.config.debug) { console.log("[MMM-FroniusSolar2] GET_FRONIUS_DATA triggered"); } this.getFroniusData(); } }, startFetchingData: function () { if (this.config && this.config.InverterIP) { if (this.config.debug) { console.log("[MMM-FroniusSolar2] Starting data fetch interval for IP", this.config.InverterIP); } this.fetchInterval = setInterval(() => { this.getFroniusData(); }, 60000); // Fetch data every 60 seconds } }, getFroniusData: function () { if (!this.config || !this.config.InverterIP) return; const url = `http://${this.config.InverterIP}/solar_api/v1/GetPowerFlowRealtimeData.fcgi`; if (this.config.debug) { console.log("[MMM-FroniusSolar2] Fetching data from URL:", url); } fetch(url) .then(response => { if (this.config.debug) { console.log("[MMM-FroniusSolar2] Response received:", response); } if (!response.ok) { throw new Error(`HTTP error!`); } return response.json(); }) .then(data => { if (this.config.debug) { console.log("[MMM-FroniusSolar2] JSON data received:", data); } const siteData = data.Body.Data.Site; const inverterData = data.Body.Data.Inverters ? data.Body.Data.Inverters["1"] : {}; const result = { P_Akku: siteData.P_Akku || 0, P_Grid: siteData.P_Grid || 0, P_Load: siteData.P_Load || 0, P_PV: siteData.P_PV || 0, Inverters: { "1": { SOC: inverterData.SOC || 0 } }, }; if (this.config.debug) { console.log("[MMM-FroniusSolar2] Parsed result:", result); } this.sendSocketNotification("FRONIUS_DATA", result); }) .catch(error => { console.error("[MMM-FroniusSolar2] Error fetching data:", error); }); }, });
This will create a lot of log entry. So only use this temporary. If you see any errors you can’t solve, please send me the logs.
Later use
pm2 flush
to clear the logs. The log-file size will increase with this version quite fast.