sorry I didn’t want to clutter the page here if it was not your problem. That error is because the response is providing two responses, 1 for inverter and 1 for battery.
I modified a lot of the flow, and made it user friendly. Feel free to import to your NodeRed, and follow the instructions. You need to modify 2 nodes only, and then click “Create Sensors”.
[{"id":"29c887befe280b70","type":"tab","label":"Felicity Inverter","disabled":true,"info":"","env":[]},{"id":"a30aaddd5daeb1b0","type":"group","z":"29c887befe280b70","name":"Get info from inverter locally","style":{"label":true,"color":"#bfbfbf","fill":"#bfdbef","fill-opacity":"0.3"},"nodes":["12db757824215eb8","46845906337ab6b4","a7c36b8bfb83e339","5d45a78e2721e0c3","2564482e9b2a3b42","7818c761c132ed8e","bf943deed6c1bedd","6178ae9e99148806","db09d1838d487e9b","e5e41351d3dcd6a1","e21f92a11cee4126","3632a32802b9c6c9","5c8734638ceb9137","ad91b5078af5d8c6","e69193f22ece2bfb","124f0aec0e4531b3","26442e839fd50b6a","5981cd3c46b7ae99"],"x":19,"y":334,"w":1272,"h":292},{"id":"e90aabdb500bb547","type":"group","z":"29c887befe280b70","name":"Additional Inverter Entities","style":{"fill":"#dbcbe7","label":true,"fill-opacity":"0.2"},"nodes":["1a4e3fee13ec8588"],"x":32,"y":657,"w":1056,"h":366},{"id":"1a4e3fee13ec8588","type":"group","z":"29c887befe280b70","g":"e90aabdb500bb547","name":"Estimate Battery Times","style":{"label":true},"nodes":["ae3dc6bbe23d7756","8cecb62f87f81e12"],"x":58,"y":683,"w":1004,"h":314},{"id":"ae3dc6bbe23d7756","type":"group","z":"29c887befe280b70","g":"1a4e3fee13ec8588","name":"Battery Runtime (Discharge)","style":{"label":true},"nodes":["b3113159ccb8d6cb","645488c02db606e0","64875674f2ce4d6b","65ae6b5304ec0efc","1647a12172770002","2f1b79e25a100e56","0f20fb7ea418ea6d","335b0ef01524a1c6","8c49f1e83b970111","46c0aff5f34d1ed1"],"x":84,"y":709,"w":952,"h":162},{"id":"8cecb62f87f81e12","type":"group","z":"29c887befe280b70","g":"1a4e3fee13ec8588","name":"Battery Charge Time","style":{"label":true},"nodes":["a347d55288b88773","92e2bdc3dd900cf6","98fc3027962df1f0","397cdfe563f110fd"],"x":84,"y":889,"w":732,"h":82},{"id":"7d8592a87f7a1690","type":"group","z":"29c887befe280b70","name":"⚙️ System Configuration (Setup once)","style":{"stroke":"#929292","fill":"#e5e5e5","label":true},"nodes":["55336ddfaae7dfe6","796e59ef61436baf","6b40d8748eeb0d25","391b152b41ec8b20","470d9ef368817781","17addf91115ec69a","65809f93b1e262de"],"x":23,"y":4,"w":963,"h":288},{"id":"6b40d8748eeb0d25","type":"group","z":"29c887befe280b70","g":"7d8592a87f7a1690","name":"Define entities using HA Discovery","style":{"stroke":"none","fill":"#ffffbf","label":true},"nodes":["cc940a16d881b6be","e0b4d040337abb99","56f314e1538a7daa","09a2eb44673d2e09"],"x":49,"y":169,"w":667,"h":97},{"id":"12db757824215eb8","type":"inject","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"Basic","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"devbasiceinfor","payload":"wifilocalMonitor:get dev basice infor","payloadType":"str","x":115,"y":435,"wires":[["3632a32802b9c6c9"]]},{"id":"46845906337ab6b4","type":"inject","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"Date","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"getdate","payload":"wifilocalMonitor:get Date","payloadType":"str","x":115,"y":495,"wires":[["3632a32802b9c6c9"]]},{"id":"a7c36b8bfb83e339","type":"tcp request","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"Inverter TCP","server":"","port":"","out":"time","ret":"string","splitc":"1000","newline":"","trim":false,"tls":"","x":625,"y":450,"wires":[["5d45a78e2721e0c3","7818c761c132ed8e"]]},{"id":"5d45a78e2721e0c3","type":"debug","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"tcp","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":745,"y":510,"wires":[]},{"id":"2564482e9b2a3b42","type":"function","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"devsetinfor","func":"global.set(\"devsetinfor\",msg.payload);\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":945,"y":375,"wires":[[]]},{"id":"7818c761c132ed8e","type":"switch","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"devsetinfor","vt":"str"},{"t":"eq","v":"devbasiceinfor","vt":"str"},{"t":"eq","v":"devrealinfor","vt":"str"},{"t":"eq","v":"getdate","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":775,"y":450,"wires":[["2564482e9b2a3b42","e5e41351d3dcd6a1","ad91b5078af5d8c6"],["5981cd3c46b7ae99"],["e21f92a11cee4126","26442e839fd50b6a"],["bf943deed6c1bedd"]]},{"id":"bf943deed6c1bedd","type":"function","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"Date","func":"global.set(\"getdate\",msg.payload);\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":930,"y":585,"wires":[[]]},{"id":"6178ae9e99148806","type":"inject","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"Monitor","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"5","crontab":"","once":false,"onceDelay":"5","topic":"devrealinfor","payload":"wifilocalMonitor:get dev real infor","payloadType":"str","x":125,"y":465,"wires":[["3632a32802b9c6c9"]]},{"id":"db09d1838d487e9b","type":"inject","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"Settings","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":false,"onceDelay":"1","topic":"devsetinfor","payload":"wifilocalMonitor:get dev set infor","payloadType":"str","x":125,"y":405,"wires":[["3632a32802b9c6c9"]]},{"id":"e5e41351d3dcd6a1","type":"function","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"Parse Config","func":"// 1. Parse Input\n// TCP requests return strings, so we must parse it into an Object.\nif (typeof msg.payload === \"string\") {\n try {\n msg.payload = JSON.parse(msg.payload);\n } catch (e) {\n node.warn(\"Failed to parse JSON: \" + e.message);\n return null;\n }\n}\n\n// Ensure we have data\nlet data = msg.payload;\nif (!data) return null;\n\nlet msgs = [];\n\n// Helper to create MQTT message\nfunction send(topicSuffix, value) {\n msgs.push({\n topic: \"felicity/state/\" + topicSuffix,\n payload: value,\n retain: true\n });\n}\n\n// 2. NUMERIC VALUES (Matches your Inverter JSON keys)\n// We check for undefined to safely skip if the key is missing\n// Convert MACCurr from DC to AC for display (Divide by 4.6)\nif (data.MACCurr !== undefined) {\n let acAmps = (data.MACCurr / 4.6).toFixed(1); // e.g. 50A DC -> 10.9A AC\n send(\"maccurr\", Number(acAmps));\n}\n// Old DC way for Grid charging current\n// if (data.MACCurr !== undefined) send(\"maccurr\", data.MACCurr);\nif (data.BBkUt !== undefined) send(\"bbkut\", data.BBkUt);\nif (data.BBkBat !== undefined) send(\"bbkbat\", data.BBkBat);\nif (data.BSOCUN !== undefined) send(\"bsocun\", data.BSOCUN);\n\n// 3. MAPPING OSPri (Int -> Text)\n// 0=USB, 1=SUB, 2=SBU\nif (data.oSPri !== undefined) {\n let map = [\"Utility First (USB)\", \"Solar First (SUB)\", \"Battery First (SBU)\"];\n // Ensure the value is within the map's range\n if (map[data.oSPri]) send(\"ospri\", map[data.oSPri]);\n}\n\n// 4. MAPPING CSPri (Int -> Text)\n// 1=CSO, 2=SNU, 3=OSO\nif (data.cSPri !== undefined) {\n // Array index 0 is null because map starts at 1\n let map = [null, \"Solar First (CSO)\", \"Solar + Utility (SNU)\", \"Solar Only (OSO)\"];\n if (map[data.cSPri]) send(\"cspri\", map[data.cSPri]);\n}\n\n// Return all MQTT messages as a single burst\nreturn [msgs];","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1080,"y":420,"wires":[["e69193f22ece2bfb"]]},{"id":"e21f92a11cee4126","type":"function","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"Parse & Fix JSON","func":"// 1. Convert buffer/payload to string\nlet input = msg.payload.toString();\nlet mergedData = {};\nlet hasData = false;\n\n// 2. Handle \"Back-to-Back\" JSONs\nif (input.includes(\"}{\")) {\n let parts = input.split(\"}{\");\n parts.forEach((part, index) => {\n let cleanPart = part;\n if (index < parts.length - 1) cleanPart = cleanPart + \"}\";\n if (index > 0) cleanPart = \"{\" + cleanPart;\n try {\n let data = JSON.parse(cleanPart);\n if (data && data !== 0 && data !== \"0\") {\n Object.assign(mergedData, data);\n hasData = true;\n }\n } catch (e) { node.warn(\"Part parse error\"); }\n });\n} else {\n // Single Message\n try {\n let data = JSON.parse(input);\n if (data && data !== 0 && data !== \"0\") {\n Object.assign(mergedData, data);\n hasData = true;\n }\n } catch (e) { return null; }\n}\n\n// 3. SANITY CHECKS & ENRICHMENT\nif (hasData) {\n // Check if SOC exists\n if (mergedData.Batsoc && Array.isArray(mergedData.Batsoc) && mergedData.Batsoc[0]) {\n let rawSoc = mergedData.Batsoc[0][0];\n\n // A. Zero Protection\n if (rawSoc === 0) {\n node.warn(\"🛡️ Glitch Protection: Dropped packet because SOC was 0\");\n return null;\n }\n\n // B. Gradient/Spike Protection (New)\n let currentSoc = rawSoc / 100;\n let lastSoc = context.get('lastSoc');\n\n // Initialize on first run\n if (lastSoc === undefined) {\n context.set('lastSoc', currentSoc);\n lastSoc = currentSoc;\n }\n\n let diff = Math.abs(currentSoc - lastSoc);\n\n // THRESHOLD: If SOC jumps by more than 5% in one reading\n if (diff > 5) {\n let strikes = context.get('socStrikes') || 0;\n strikes++;\n context.set('socStrikes', strikes);\n\n // Ignore the first 2 weird readings (approx 6-10 seconds)\n if (strikes < 3) {\n node.warn(`🛡️ Glitch Protection: Ignored SOC spike ${lastSoc}% -> ${currentSoc}% (Strike ${strikes})`);\n return null; // Stop here. Don't publish.\n }\n // If it happens 3 times, it's real (or we accepted the new reality)\n context.set('socStrikes', 0);\n } else {\n // Valid value, reset strikes\n context.set('socStrikes', 0);\n }\n\n // Save valid SOC for next time\n context.set('lastSoc', currentSoc);\n }\n\n // --- INJECT BSOCUN FROM GLOBAL MEMORY ---\n let settingsStr = global.get(\"devsetinfor\");\n if (settingsStr) {\n try {\n let settings = (typeof settingsStr === 'string') ? JSON.parse(settingsStr) : settingsStr;\n if (settings.BSOCUN !== undefined) {\n mergedData.BSOCUN = settings.BSOCUN;\n }\n } catch (e) { /* Ignore parsing errors from global */ }\n }\n // ---------------------------------------------\n\n msg.payload = mergedData;\n msg.topic = \"inverter_fs1/state\";\n return msg;\n}\n\nreturn null;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1085,"y":510,"wires":[["e69193f22ece2bfb"]]},{"id":"3632a32802b9c6c9","type":"delay","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"1msg/3s","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"3","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":305,"y":450,"wires":[["124f0aec0e4531b3"]]},{"id":"5c8734638ceb9137","type":"link in","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"link in 6","links":["dafd53b3be75ccb0"],"x":110,"y":525,"wires":[["3632a32802b9c6c9"]]},{"id":"ad91b5078af5d8c6","type":"debug","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"settings","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":740,"y":390,"wires":[]},{"id":"e69193f22ece2bfb","type":"link out","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"To MQTT","mode":"link","links":["470d9ef368817781"],"x":1250,"y":465,"wires":[]},{"id":"124f0aec0e4531b3","type":"change","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"Set IP/Port","rules":[{"t":"set","p":"host","pt":"msg","to":"inverter_ip","tot":"global"},{"t":"set","p":"port","pt":"msg","to":"inverter_port","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":450,"wires":[["a7c36b8bfb83e339"]]},{"id":"b3113159ccb8d6cb","type":"server-state-changed","z":"29c887befe280b70","g":"ae3dc6bbe23d7756","name":"Watch Raw Load","server":"43e792f7.5c883c","version":6,"outputs":1,"exposeAsEntityConfig":"","entities":{"entity":["sensor.inverter_fs1_load_power"],"substring":[],"regex":[]},"outputInitially":true,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":true,"ignorePrevStateUnknown":true,"ignorePrevStateUnavailable":true,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"string","valueType":"entityState"}],"x":190,"y":750,"wires":[["645488c02db606e0"]]},{"id":"645488c02db606e0","type":"time moving average","z":"29c887befe280b70","g":"ae3dc6bbe23d7756","name":"Avg Load (30m)","timeframe":"1800","rampPositive":"","rampNegative":"","rampTimeframe":"","round":true,"roundDecimals":"0","x":405,"y":750,"wires":[["64875674f2ce4d6b"]]},{"id":"64875674f2ce4d6b","type":"function","z":"29c887befe280b70","g":"ae3dc6bbe23d7756","name":"Save","func":"flow.set(\"inverter_avg_load\", msg.payload);\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":575,"y":750,"wires":[["0f20fb7ea418ea6d"]]},{"id":"65ae6b5304ec0efc","type":"server-state-changed","z":"29c887befe280b70","g":"ae3dc6bbe23d7756","name":"Watch Raw Batt Out","server":"43e792f7.5c883c","version":6,"outputs":1,"exposeAsEntityConfig":"","entities":{"entity":["sensor.inverter_fs1_battery_power_out"],"substring":[],"regex":[]},"outputInitially":true,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":true,"ignorePrevStateUnknown":true,"ignorePrevStateUnavailable":true,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"string","valueType":"entityState"}],"x":200,"y":830,"wires":[["1647a12172770002"]]},{"id":"1647a12172770002","type":"time moving average","z":"29c887befe280b70","g":"ae3dc6bbe23d7756","name":"Avg Batt (30m)","timeframe":"1800","rampPositive":"","rampNegative":"","rampTimeframe":"","round":true,"roundDecimals":"0","x":405,"y":830,"wires":[["2f1b79e25a100e56"]]},{"id":"2f1b79e25a100e56","type":"function","z":"29c887befe280b70","g":"ae3dc6bbe23d7756","name":"Save","func":"flow.set(\"inverter_avg_batt\", msg.payload);\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":575,"y":830,"wires":[["0f20fb7ea418ea6d"]]},{"id":"0f20fb7ea418ea6d","type":"api-current-state","z":"29c887befe280b70","g":"ae3dc6bbe23d7756","name":"Get kWh Available","server":"43e792f7.5c883c","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"sensor.inverter_fs1_battery_state_of_charge","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"kwh_available","propertyType":"msg","value":"$entity().attributes.kwh_available","valueType":"jsonata"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":755,"y":765,"wires":[["335b0ef01524a1c6"]]},{"id":"335b0ef01524a1c6","type":"function","z":"29c887befe280b70","g":"ae3dc6bbe23d7756","name":"Smart Discharge Calc (Averaged)","func":"const batt_w = parseFloat(flow.get(\"inverter_avg_batt\")) || 0;\nconst load_w = parseFloat(flow.get(\"inverter_avg_load\")) || 0;\nconst kwh = parseFloat(msg.kwh_available) || 0;\n\nlet effective_load = 0;\nlet mode = \"\";\n\nif (batt_w > 20) {\n effective_load = batt_w;\n mode = \"Real Usage (Avg)\";\n} else {\n effective_load = load_w;\n mode = \"Prediction (Avg Load)\";\n}\n\nlet runtime_hours = 0;\nif (effective_load > 1 && kwh > 0) {\n const load_kw = effective_load / 1000;\n runtime_hours = kwh / load_kw;\n} else {\n runtime_hours = 24; \n mode += \" (Idle)\";\n}\n\nif (runtime_hours > 24) runtime_hours = 24;\n\nlet empty_time_iso = null;\nlet human_str = \"Stable (>24h)\";\n\nif (runtime_hours > 0 && runtime_hours < 24) {\n const now = new Date();\n const ms_remaining = runtime_hours * 3600 * 1000;\n let target_time = new Date(now.getTime() + ms_remaining);\n\n const coeff = 1000 * 60 * 15;\n target_time = new Date(Math.round(target_time.getTime() / coeff) * coeff);\n\n empty_time_iso = target_time.toISOString();\n\n const today = new Date();\n const tomorrow = new Date(today);\n tomorrow.setDate(tomorrow.getDate() + 1);\n const isSameDay = (d1, d2) => d1.toDateString() === d2.toDateString();\n const time_str = target_time.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });\n\n if (isSameDay(target_time, today)) {\n human_str = `Today at ${time_str}`;\n } else if (isSameDay(target_time, tomorrow)) {\n human_str = `Tomorrow at ${time_str}`;\n } else {\n human_str = target_time.toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric' }) + \" \" + time_str;\n }\n}\n\nconst msgState = {\n topic: \"inverter_fs1/sensor/battery_runtime/state\",\n payload: parseFloat(runtime_hours.toFixed(1)),\n retain: true\n};\n\nconst msgAttrs = {\n topic: \"inverter_fs1/sensor/battery_runtime/attributes\",\n payload: {\n empty_at: empty_time_iso,\n empty_time: human_str,\n raw_kwh: kwh,\n effective_load_used: effective_load,\n average_load_watts: load_w,\n average_batt_out_watts: batt_w,\n calculation_mode: mode\n },\n retain: true\n};\n\nreturn [[msgState, msgAttrs]];","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":825,"y":810,"wires":[["8c49f1e83b970111"]]},{"id":"8c49f1e83b970111","type":"link out","z":"29c887befe280b70","g":"ae3dc6bbe23d7756","name":"To MQTT","mode":"link","links":["470d9ef368817781"],"x":995,"y":810,"wires":[]},{"id":"46c0aff5f34d1ed1","type":"inject","z":"29c887befe280b70","g":"ae3dc6bbe23d7756","name":"Refresh","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":180,"y":795,"wires":[["645488c02db606e0"]]},{"id":"a347d55288b88773","type":"server-state-changed","z":"29c887befe280b70","g":"8cecb62f87f81e12","name":"Watch Power In","server":"43e792f7.5c883c","version":6,"outputs":1,"exposeAsEntityConfig":"","entities":{"entity":["sensor.inverter_fs1_battery_power_in"],"substring":[],"regex":[]},"outputInitially":true,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":true,"ignorePrevStateUnknown":true,"ignorePrevStateUnavailable":true,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"charge_watts","propertyType":"msg","value":"string","valueType":"entityState"}],"x":190,"y":930,"wires":[["92e2bdc3dd900cf6"]]},{"id":"92e2bdc3dd900cf6","type":"api-current-state","z":"29c887befe280b70","g":"8cecb62f87f81e12","name":"Get SOC Attributes","server":"43e792f7.5c883c","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"sensor.inverter_fs1_battery_state_of_charge","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"kwh_current","propertyType":"msg","value":"$entity().attributes.kwh_total","valueType":"jsonata"},{"property":"battery_capacity","propertyType":"msg","value":"$entity().attributes.battery_size_kwh","valueType":"jsonata"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":410,"y":930,"wires":[["98fc3027962df1f0"]]},{"id":"98fc3027962df1f0","type":"function","z":"29c887befe280b70","g":"8cecb62f87f81e12","name":"Calc Charge Time","func":"// Inputs\nconst charge_w = parseFloat(msg.charge_watts);\nconst kwh_current = parseFloat(msg.kwh_current);\n\n// DYNAMIC CAPACITY (From Attribute)\n// Fallback to 25.6 if attribute is missing\nconst battery_capacity = parseFloat(msg.battery_capacity) || 25.6;\n\n// 1. Calculate Remaining Capacity to Fill\nconst kwh_needed = battery_capacity - kwh_current;\n\n// 2. Calculate Charge Time\nlet charge_hours = 0;\n\n// Logic: Charging Power > 0 AND we actually need charge\nif (charge_w > 0 && kwh_needed > 0) {\n const charge_kw = charge_w / 1000;\n charge_hours = kwh_needed / charge_kw;\n} else {\n charge_hours = 0;\n}\n\n// CAP: STRICT 24H LIMIT\nif (charge_hours > 24) charge_hours = 24;\n\n// 3. Calculate Timestamp\nlet full_at = null;\nlet human_str = \"Discharging / Full\";\n\nif (charge_hours > 0 && charge_hours < 24) { // Only calculate timestamp if under 24h\n const now = new Date();\n const ms_remaining = charge_hours * 3600 * 1000;\n let target_time = new Date(now.getTime() + ms_remaining);\n\n // Smooth to 15 mins\n const coeff = 1000 * 60 * 15;\n target_time = new Date(Math.round(target_time.getTime() / coeff) * coeff);\n\n full_at = target_time.toISOString();\n\n const today = new Date();\n const tomorrow = new Date(today);\n tomorrow.setDate(tomorrow.getDate() + 1);\n const isSameDay = (d1, d2) => d1.toDateString() === d2.toDateString();\n const time_str = target_time.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });\n\n if (isSameDay(target_time, today)) {\n human_str = `Today at ${time_str}`;\n } else if (isSameDay(target_time, tomorrow)) {\n human_str = `Tomorrow at ${time_str}`;\n } else {\n human_str = target_time.toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric' }) + \" \" + time_str;\n }\n} else if (charge_hours >= 24) {\n human_str = \"> 24 Hours\";\n}\n\n// 4. Prepare Outputs\nconst msgState = {\n topic: \"inverter_fs1/sensor/battery_charge_remaining/state\",\n payload: parseFloat(charge_hours.toFixed(1)),\n retain: true\n};\n\nconst msgAttrs = {\n topic: \"inverter_fs1/sensor/battery_charge_remaining/attributes\",\n payload: {\n full_at: full_at,\n full_time: human_str,\n raw_kwh_needed: parseFloat(kwh_needed.toFixed(2)),\n raw_charge_w: charge_w,\n capacity_used_for_calc: battery_capacity\n },\n retain: true\n};\n\nreturn [[msgState, msgAttrs]];","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":640,"y":930,"wires":[["397cdfe563f110fd"]]},{"id":"397cdfe563f110fd","type":"link out","z":"29c887befe280b70","g":"8cecb62f87f81e12","name":"To MQTT","mode":"link","links":["470d9ef368817781"],"x":775,"y":930,"wires":[]},{"id":"55336ddfaae7dfe6","type":"inject","z":"29c887befe280b70","g":"7d8592a87f7a1690","name":"On Startup","props":[],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":145,"y":120,"wires":[["796e59ef61436baf"]]},{"id":"796e59ef61436baf","type":"function","z":"29c887befe280b70","g":"7d8592a87f7a1690","name":"Set Globals","func":"// === NETWORK CONFIG ===\nglobal.set(\"inverter_ip\", \"192.168.xx.xx\");\nglobal.set(\"inverter_port\", \"53970\");\n\n// SILENT SUCCESS\n// Sets a green dot under the node instead of spamming debug\nnode.status({ fill: \"green\", shape: \"dot\", text: \"Config Loaded\" });\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":335,"y":120,"wires":[[]]},{"id":"cc940a16d881b6be","type":"function","z":"29c887befe280b70","g":"6b40d8748eeb0d25","name":"Define Sensors","func":"// Retrieve setup from previous node\nlet device = msg.device;\nlet configs = msg.configs;\n\n// HELPER FUNCTION\nfunction add(topic, payload) {\n payload.device = device;\n configs.push({ topic: topic, payload: payload });\n}\n\n// =========================================\n// GROUP 1: GENERAL STATUS & DIAGNOSTICS\n// =========================================\n\nadd(\"homeassistant/binary_sensor/inverter_fs1/fs1_fault/config\", {\n \"unique_id\": \"fs1_fault\", \"name\": \"Fault\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ 'ON' if value_json.fault == 1 else 'OFF' }}\",\n \"device_class\": \"problem\", \"entity_category\": \"diagnostic\"\n});\n\nadd(\"homeassistant/binary_sensor/inverter_fs1/fs1_warning/config\", {\n \"unique_id\": \"fs1_warning\", \"name\": \"Warning\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ 'ON' if value_json.warn == 1 else 'OFF' }}\",\n \"device_class\": \"problem\", \"entity_category\": \"diagnostic\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_batt_state/config\", {\n \"unique_id\": \"fs1_batt_state\",\n \"name\": \"Battery State\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ 'Full' if value_json.Bstate == 320 else 'Standby' if value_json.Bstate == 960 else 'Charging' if value_json.Bstate == 9152 else 'Discharging' if value_json.Bstate == 5056 else 'Unknown: ' + value_json.Bstate | string }}\",\n \"icon\": \"mdi:battery-sync\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_workm/config\", {\n \"unique_id\": \"fs1_workm\",\n \"name\": \"Work Mode\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{% set modes = {0: 'Initializing', 1: '1', 2: '2', 3: 'Battery', 4: '4', 5: 'Grid'} %} {{ modes.get(value_json.workM, 'Unknown (' + value_json.workM|string + ')') }}\",\n \"icon\": \"mdi:state-machine\",\n \"entity_category\": \"diagnostic\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pflowe1/config\", {\n \"unique_id\": \"fs1_pflowe1\", \"name\": \"pFlowE1\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ value_json.pFlowE1 }}\",\n \"entity_category\": \"diagnostic\"\n});\n\n// =========================================\n// GROUP 2: GRID (AC IN)\n// =========================================\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_grid_voltage/config\", {\n \"unique_id\": \"fs1_grid_voltage\", \"name\": \"Grid Voltage\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.ACin[0][0] / 10) | float }}\",\n \"unit_of_measurement\": \"V\", \"device_class\": \"voltage\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_grid_current/config\", {\n \"unique_id\": \"fs1_grid_current\", \"name\": \"Grid Current\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.ACin[1][0] / 10) | float }}\",\n \"unit_of_measurement\": \"A\", \"device_class\": \"current\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_grid_power/config\", {\n \"unique_id\": \"fs1_grid_power\", \"name\": \"Grid Power\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.ACin[3][0] ) | float }}\",\n \"unit_of_measurement\": \"W\", \"device_class\": \"power\", \"state_class\": \"measurement\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_grid_freq/config\", {\n \"unique_id\": \"fs1_grid_freq\",\n \"name\": \"Grid Frequency\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ 'unavailable' if value_json.ACin[2][0] == 0 else (value_json.ACin[2][0] / 100) | float }}\",\n \"device_class\": \"frequency\",\n \"unit_of_measurement\": \"Hz\"\n});\n\nadd(\"homeassistant/binary_sensor/inverter_fs1/fs1_grid_available/config\", {\n \"unique_id\": \"fs1_grid_available\", \"name\": \"Grid Available\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ 'ON' if (value_json.ACin[0][0] / 10 > 0) else 'OFF' }}\",\n \"device_class\": \"power\"\n});\n\nadd(\"homeassistant/binary_sensor/inverter_fs1/fs1_grid_in_use/config\", {\n \"unique_id\": \"fs1_grid_in_use\", \"name\": \"Grid in Use\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ 'ON' if (value_json.ACin[1][0] / 10 > 0.1) else 'OFF' }}\",\n \"device_class\": \"power\"\n});\n\n// =========================================\n// GROUP 3: BATTERY\n// =========================================\n\nadd(\"homeassistant/sensor/inverter_fs1/soc/config\", {\n \"unique_id\": \"soc\", \"name\": \"Battery State of Charge\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.Batsoc[0][0] / 100) | round(0) }}\",\n \"unit_of_measurement\": \"%\", \"device_class\": \"battery\",\n \"json_attributes_topic\": \"inverter_fs1/state\",\n \"json_attributes_template\": \"{{ {'battery_size_kwh': 25.6, 'kwh_total': (0.00256 * value_json.Batsoc[0][0]) | round(2), 'kwh_available': ([0, (0.00256 * value_json.Batsoc[0][0]) - (0.256 * value_json.BSOCUN | float(10))] | max) | round(2) } | to_json }}\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_batt_voltage/config\", {\n \"unique_id\": \"fs1_batt_voltage\", \"name\": \"Battery Voltage\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.Batt[0][0] / 1000) | float }}\",\n \"unit_of_measurement\": \"V\", \"device_class\": \"voltage\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_batt_current/config\", {\n \"unique_id\": \"fs1_batt_current\", \"name\": \"Battery Current\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.Batt[1][0] / 10) | float }}\",\n \"unit_of_measurement\": \"A\", \"device_class\": \"current\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_batt_power/config\", {\n \"unique_id\": \"fs1_batt_power\", \"name\": \"Battery Power\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (((value_json.Batt[0][0] / 1000) * (value_json.Batt[1][0] / 10)) | round(0)) }}\",\n \"unit_of_measurement\": \"W\", \"device_class\": \"power\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_batt_power_in/config\", {\n \"unique_id\": \"fs1_batt_power_in\", \"name\": \"Battery Power In\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ [(((value_json.Batt[0][0] / 1000) * (value_json.Batt[1][0] / 10)) | round(0)), 0] | max }}\",\n \"unit_of_measurement\": \"W\", \"device_class\": \"power\", \"state_class\": \"measurement\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_batt_power_out/config\", {\n \"unique_id\": \"fs1_batt_power_out\", \"name\": \"Battery Power Out\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ [(-1 * ((value_json.Batt[0][0] / 1000) * (value_json.Batt[1][0] / 10)) | round(0)), 0] | max }}\",\n \"unit_of_measurement\": \"W\", \"device_class\": \"power\", \"state_class\": \"measurement\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_batt_temp/config\", {\n \"unique_id\": \"fs1_batt_temp\", \"name\": \"Battery Temperature\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.Temp[0][0] / 10) | round(1) }}\",\n \"unit_of_measurement\": \"°C\", \"device_class\": \"temperature\", \"entity_category\": \"diagnostic\"\n});\n\nadd(\"homeassistant/binary_sensor/inverter_fs1/fs1_bcstat/config\", {\n \"unique_id\": \"fs1_bcstat\", \"name\": \"Battery Charging\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ 'ON' if value_json.bCStat == 1 else 'OFF' }}\"\n});\n\n// =========================================\n// GROUP 4: LOAD (AC OUT)\n// =========================================\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_load_voltage/config\", {\n \"unique_id\": \"fs1_load_voltage\", \"name\": \"Load Voltage\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.ACout[0][0] / 10) | float }}\",\n \"unit_of_measurement\": \"V\", \"device_class\": \"voltage\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_load_current/config\", {\n \"unique_id\": \"fs1_load_current\", \"name\": \"Load Current\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.ACout[1][0] / 10) | float }}\",\n \"unit_of_measurement\": \"A\", \"device_class\": \"current\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_load_power/config\", {\n \"unique_id\": \"fs1_load_power\", \"name\": \"Load Power\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.ACout[3][0] ) | float }}\",\n \"unit_of_measurement\": \"W\", \"device_class\": \"power\", \"state_class\": \"measurement\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_load_perc/config\", {\n \"unique_id\": \"fs1_load_perc\", \"name\": \"Load Percentage\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.lPerc / 10) | float }}\",\n \"unit_of_measurement\": \"%\"\n});\n\n// =========================================\n// GROUP 5: PV (SOLAR)\n// =========================================\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv_power/config\", {\n \"unique_id\": \"fs1_pv_power\", \"name\": \"PV Power\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.PV[3][0]) | float }}\",\n \"unit_of_measurement\": \"W\", \"device_class\": \"power\", \"state_class\": \"measurement\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv1_voltage/config\", {\n \"unique_id\": \"fs1_pv1_voltage\", \"name\": \"PV1 Voltage\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.PV[0][0] / 10) | float }}\",\n \"unit_of_measurement\": \"V\", \"device_class\": \"voltage\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv1_current/config\", {\n \"unique_id\": \"fs1_pv1_current\", \"name\": \"PV1 Current\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.PV[1][0] / 10) | float }}\",\n \"unit_of_measurement\": \"A\", \"device_class\": \"current\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv1_power/config\", {\n \"unique_id\": \"fs1_pv1_power\", \"name\": \"PV1 Power\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.PV[2][0]) | float }}\",\n \"unit_of_measurement\": \"W\", \"device_class\": \"power\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv2_voltage/config\", {\n \"unique_id\": \"fs1_pv2_voltage\", \"name\": \"PV2 Voltage\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.PV[0][1] / 10) | float }}\",\n \"unit_of_measurement\": \"V\", \"device_class\": \"voltage\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv2_current/config\", {\n \"unique_id\": \"fs1_pv2_current\", \"name\": \"PV2 Current\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.PV[1][1] / 10) | float }}\",\n \"unit_of_measurement\": \"A\", \"device_class\": \"current\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv2_power/config\", {\n \"unique_id\": \"fs1_pv2_power\", \"name\": \"PV2 Power\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.PV[2][1]) | float }}\",\n \"unit_of_measurement\": \"W\", \"device_class\": \"power\"\n});\n\n// ENERGY COUNTERS\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv_energy_today/config\", {\n \"unique_id\": \"fs1_pv_energy_today\", \"name\": \"PV Energy Today\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.Energy[0][2]) / 1000 | float }}\",\n \"unit_of_measurement\": \"kWh\", \"device_class\": \"energy\", \"state_class\": \"total_increasing\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv_energy_month/config\", {\n \"unique_id\": \"fs1_pv_energy_month0\", \"name\": \"PV Energy This Month\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.Energy[0][3]) / 1000 | float }}\",\n \"unit_of_measurement\": \"kWh\", \"device_class\": \"energy\", \"state_class\": \"total_increasing\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv_energy_year/config\", {\n \"unique_id\": \"fs1_pv_energy_year\", \"name\": \"PV Energy This Year\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.Energy[0][4]) / 1000 | float }}\",\n \"unit_of_measurement\": \"kWh\", \"device_class\": \"energy\", \"state_class\": \"total_increasing\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_pv_energy_total/config\", {\n \"unique_id\": \"fs1_pv_energy_total\", \"name\": \"PV Energy Total Lifetime\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.Energy[0][1]) / 1000 | float }}\",\n \"unit_of_measurement\": \"kWh\", \"device_class\": \"energy\", \"state_class\": \"total_increasing\"\n});\n\n// =========================================\n// GROUP 6: ADVANCED DIAGNOSTICS (NEW)\n// =========================================\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_inv_temp_ac/config\", {\n \"unique_id\": \"fs1_inv_temp_ac\", \"name\": \"Inverter AC Temp\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.Temp[0][1] / 10) | float }}\",\n \"unit_of_measurement\": \"°C\", \"device_class\": \"temperature\",\n \"entity_category\": \"diagnostic\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_inv_temp_dc/config\", {\n \"unique_id\": \"fs1_inv_temp_dc\", \"name\": \"Inverter DC Temp\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.Temp[0][2] / 10) | float }}\",\n \"unit_of_measurement\": \"°C\", \"device_class\": \"temperature\",\n \"entity_category\": \"diagnostic\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_batt_soh/config\", {\n \"unique_id\": \"fs1_batt_soh\", \"name\": \"Battery SOH\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.BatsocList[0][1] / 10) | float }}\",\n \"unit_of_measurement\": \"%\", \"icon\": \"mdi:heart-pulse\",\n \"entity_category\": \"diagnostic\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_cell_max_vol/config\", {\n \"unique_id\": \"fs1_cell_max_vol\", \"name\": \"Max Cell Voltage\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.BMaxMin[0][0] / 1000) | float }}\",\n \"unit_of_measurement\": \"V\", \"device_class\": \"voltage\", \"icon\": \"mdi:battery-plus\",\n \"entity_category\": \"diagnostic\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_cell_min_vol/config\", {\n \"unique_id\": \"fs1_cell_min_vol\", \"name\": \"Min Cell Voltage\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.BMaxMin[0][1] / 1000) | float }}\",\n \"unit_of_measurement\": \"V\", \"device_class\": \"voltage\", \"icon\": \"mdi:battery-minus\",\n \"entity_category\": \"diagnostic\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_cell_imbalance/config\", {\n \"unique_id\": \"fs1_cell_imbalance\", \"name\": \"Cell Imbalance\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.BMaxMin[0][0] - value_json.BMaxMin[0][1]) }}\",\n \"unit_of_measurement\": \"mV\", \"icon\": \"mdi:scale-balance\",\n \"entity_category\": \"diagnostic\"\n});\n\nadd(\"homeassistant/sensor/inverter_fs1/fs1_bus_voltage/config\", {\n \"unique_id\": \"fs1_bus_voltage\", \"name\": \"Bus Voltage\",\n \"state_topic\": \"inverter_fs1/state\",\n \"value_template\": \"{{ (value_json.busVp / 10) | float }}\",\n \"unit_of_measurement\": \"V\", \"device_class\": \"voltage\",\n \"entity_category\": \"diagnostic\"\n});\n\n\n// =========================================\n// GROUP 7: ADVANCED SENSORS (LOGIC)\n// =========================================\n\n// Battery Runtime (MAIN)\nadd(\"homeassistant/sensor/inverter_fs1/battery_runtime/config\", {\n \"name\": \"Battery Runtime\",\n \"unique_id\": \"inverter_fs1_battery_runtime\",\n \"state_topic\": \"inverter_fs1/sensor/battery_runtime/state\",\n \"json_attributes_topic\": \"inverter_fs1/sensor/battery_runtime/attributes\",\n \"device_class\": \"duration\",\n \"unit_of_measurement\": \"h\",\n \"icon\": \"mdi:timer-sand\",\n \"state_class\": \"measurement\"\n});\n\n// Battery Charge Time\nadd(\"homeassistant/sensor/inverter_fs1/battery_charge_remaining/config\", {\n \"name\": \"Battery Charge Time\",\n \"unique_id\": \"inverter_fs1_battery_charge_remaining\",\n \"state_topic\": \"inverter_fs1/sensor/battery_charge_remaining/state\",\n \"json_attributes_topic\": \"inverter_fs1/sensor/battery_charge_remaining/attributes\",\n \"device_class\": \"duration\",\n \"unit_of_measurement\": \"h\",\n \"icon\": \"mdi:battery-charging-100\",\n \"state_class\": \"measurement\"\n});\n\n// =========================================\n// SEND SEQUENTIALLY (AVOID FLOODING)\n// =========================================\nconfigs.forEach((c, index) => {\n setTimeout(() => {\n node.send({\n topic: c.topic,\n payload: c.payload,\n retain: true\n });\n }, index * 50); // 50ms delay between each\n});\n\nreturn null;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":225,"wires":[["391b152b41ec8b20"]]},{"id":"e0b4d040337abb99","type":"inject","z":"29c887befe280b70","g":"6b40d8748eeb0d25","name":"Create Sensors","props":[],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","x":175,"y":225,"wires":[["56f314e1538a7daa"]]},{"id":"470d9ef368817781","type":"link in","z":"29c887befe280b70","g":"7d8592a87f7a1690","name":"link in 7","links":["2b6b66536dafeb01","f424bee361b00c32","e69193f22ece2bfb","97cf33da8e39f957","c04769f81418f2b2","8c49f1e83b970111","397cdfe563f110fd"],"x":645,"y":90,"wires":[["391b152b41ec8b20"]]},{"id":"391b152b41ec8b20","type":"mqtt out","z":"29c887befe280b70","g":"7d8592a87f7a1690","name":"Send to MQTT","topic":"","qos":"","retain":"","broker":"357b169354f2f5e9","x":880,"y":90,"wires":[]},{"id":"26442e839fd50b6a","type":"function","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"devrealnfor","func":"global.set(\"devrealinfor\",msg.payload);\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":540,"wires":[[]]},{"id":"5981cd3c46b7ae99","type":"function","z":"29c887befe280b70","g":"a30aaddd5daeb1b0","name":"devbasiceinfor","func":"global.set(\"devbasiceinfor\",msg.payload);\nreturn msg;\n\n// // 1. Ensure payload is a string\n// let inputString = msg.payload.toString();\n\n// // 2. Check if we have the \"}{\" duplicate pattern\n// // This implies two objects are stuck together\n// let splitIndex = inputString.indexOf(\"}{\");\n\n// if (splitIndex !== -1) {\n// // If found, slice the string from the start up to the first closing bracket\n// // splitIndex is where '}' starts in the pattern \"}{\", so we add 1 to include it.\n// inputString = inputString.substring(0, splitIndex + 1);\n// }\n\n// // 3. Parse the cleaned string to ensure it is valid JSON\n// try {\n// let jsonObject = JSON.parse(inputString);\n\n// // Store the clean object, not the string\n// global.set(\"devbasiceinfor\", jsonObject);\n\n// // Update the message payload to the clean object for downstream nodes\n// msg.payload = jsonObject;\n\n// } catch (e) {\n// node.error(\"Failed to parse JSON: \" + e.message, msg);\n// }\n\n// return msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":450,"wires":[[]]},{"id":"17addf91115ec69a","type":"comment","z":"29c887befe280b70","g":"7d8592a87f7a1690","name":"Read Me","info":"Modify the following:\n1- \"Set Globals\": change IP\n2- Modify \"Define Inverter HA Device\" to use the correct info for your inverter\n3- Click \"Create Sensors\" once. The entities will be created inside HA\n4- Success. You should have a new device in HA frontend with all the values","x":120,"y":45,"wires":[]},{"id":"56f314e1538a7daa","type":"function","z":"29c887befe280b70","g":"6b40d8748eeb0d25","name":"Define Inverter HA Device","func":"// 1. CENTRAL DEVICE DEFINITION\n// We attach this to 'msg' so the next node can use it\nmsg.device = {\n \"identifiers\": [\"inverter_fs1\"],\n \"name\": \"Inverter FS1\",\n \"model\": \"48V LiFePO4 25kWh\",\n \"manufacturer\": \"Felicity Solar\"\n};\n\n// Initialize the array to hold our configurations\nmsg.configs = [];\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":225,"wires":[["cc940a16d881b6be"]]},{"id":"65809f93b1e262de","type":"comment","z":"29c887befe280b70","g":"7d8592a87f7a1690","name":"MODIFY THIS","info":"","x":355,"y":105,"wires":[]},{"id":"09a2eb44673d2e09","type":"comment","z":"29c887befe280b70","g":"6b40d8748eeb0d25","name":"MODIFY THIS","info":"","x":415,"y":210,"wires":[]},{"id":"43e792f7.5c883c","type":"server","name":"Home Assistant","version":6,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":["y","yes","true","on","home","open"],"connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true},{"id":"357b169354f2f5e9","type":"mqtt-broker","name":"","broker":"localhost","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"6a617d40e71a8b30","type":"global-config","env":[],"modules":{"node-red-contrib-home-assistant-websocket":"0.80.3","node-red-contrib-time-moving-average":"1.0.3"}}]