UNPKG

@gravypower/node-red-franklinwh

Version:

Node-RED node to control FranklinWH gateway

152 lines (128 loc) 6.64 kB
const apiHandler = require('../../../api-handler'); module.exports = function(RED) { // Constants for formatting const POWER_PRECISION = 2; // Decimal places for power values const ENERGY_PRECISION = 3; // Decimal places for energy values function formatPower(value) { if (typeof value !== 'number') return '0'; return value.toFixed(POWER_PRECISION); } function formatEnergy(value) { if (typeof value !== 'number') return '0'; return value.toFixed(ENERGY_PRECISION); } function formatDuration(minutes) { if (minutes < 60) { return `${Math.round(minutes)}m`; } const hours = Math.floor(minutes / 60); const remainingMinutes = Math.round(minutes % 60); return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`; } function calculateTimeEstimate(currentPercentage, targetPercentage, powerOutput, reservePercentage = 5) { if (!powerOutput) return null; // Assume 13.6 kWh capacity (standard FranklinWH aPower capacity) const BATTERY_CAPACITY = 13.6; const currentKWh = (currentPercentage / 100) * BATTERY_CAPACITY; const targetKWh = (targetPercentage / 100) * BATTERY_CAPACITY; const kWhDifference = Math.abs(targetKWh - currentKWh); // Convert power from kW to rate per hour const hourlyRate = Math.abs(powerOutput); if (hourlyRate === 0) return null; // Calculate hours and convert to minutes const minutes = (kWhDifference / hourlyRate) * 60; return formatDuration(minutes); } function getStatusColor(percentage) { if (percentage >= 75) return 'green'; if (percentage >= 25) return 'yellow'; return 'red'; } function FranklinWHGetBatteryNode(config) { RED.nodes.createNode(this, config); const node = this; const server = RED.nodes.getNode(config.server); if (!server) { node.status({fill:"red", shape:"ring", text:"missing config"}); node.error("No server configuration"); return; } node.status({fill:"grey", shape:"ring", text:"waiting for input"}); node.on("input", async function (msg, send, done) { try { // Show requesting status node.status({fill:"blue", shape:"dot", text:"requesting..."}); await apiHandler.executeWithRetry( node, server, async (api) => { // Get battery status using getAGateStatus method const batteryData = await api.getAGateStatus(); // Map the data to our battery status structure const batteryStatus = { chargePercentage: batteryData.chargePercentage, powerOutput: batteryData.batteryOut, totalCharged: batteryData.batteryInKWh, totalDischarged: batteryData.batteryOutKWh }; // Format and validate the data const chargePercentage = Math.min(100, Math.max(0, parseInt(batteryStatus.chargePercentage) || 0)); const powerOutput = parseFloat(batteryStatus.powerOutput) || 0; const energyCharged = parseFloat(batteryStatus.totalCharged) || 0; const energyDischarged = parseFloat(batteryStatus.totalDischarged) || 0; // Show battery status const statusColor = getStatusColor(chargePercentage); node.status({ fill: statusColor, shape: "dot", text: `${chargePercentage}% (${formatPower(powerOutput)}kW)` }); // Get reserve level and calculate time estimate const reserve = await api.getReserve(); const reservePercentage = reserve || 5; // Default to 5% if not set // Calculate time estimate based on charging direction let timeEstimate = null; if (powerOutput !== 0) { const targetPercentage = powerOutput < 0 ? 100 : reservePercentage; timeEstimate = calculateTimeEstimate(chargePercentage, targetPercentage, powerOutput, reservePercentage); } // Return structured data return { state: { chargePercentage: chargePercentage, powerOutput: parseFloat(formatPower(powerOutput)), charging: powerOutput < 0 // Negative power means charging (power flowing into battery) }, energy: { charged: parseFloat(formatEnergy(energyCharged)), discharged: parseFloat(formatEnergy(energyDischarged)), unit: 'kWh' }, estimate: { timeRemaining: timeEstimate, type: powerOutput < 0 ? 'timeToFull' : powerOutput > 0 ? 'timeToReserve' : null, reserveLevel: reservePercentage } }; }, msg, send, done ); } catch (err) { const errorMsg = err.message || 'Unknown error'; const displayMsg = errorMsg.substring(0, 25); node.status({fill:"red", shape:"ring", text: displayMsg}); node.error("FranklinWH Error: " + errorMsg, msg); if (done) done(err); } }); node.on('close', function(done) { node.status({}); done(); }); } RED.nodes.registerType("franklinwh-get-battery", FranklinWHGetBatteryNode); }