AC PV Inverter power limiting via virtual device, possible?

I’m wondering if anyone has managed to get power limiting working for an AC PV inverter that’s injected into the Cerbo GX via a Node-RED virtual device.

My setup: I’m using a Node-RED virtual pv inverter to feed my PV data into the Cerbo. This works fine.

I noticed that the path /Ac/PowerLimit exists on the virtual PV inverter device, and I’ve been able to read from it and map that value back to my actual inverter so the inverter does respond and limits its output to the specified wattage. So far so good.

The problem: I can’t seem to enable dynamic power limiting for this virtual device in the Cerbo GX settings. The option just isn’t available the way it would be for a natively supported inverter.

Has anyone else tried to do this, or have any insights on whether it’s actually possible to get the Cerbo to actively manage/write to /Ac/PowerLimit on a virtual device? Any pointers on the right approach would be much appreciated!

you could send directly to the PV Inverter with Node Red , without using the virtual device for that?

The sending to the inverter part is not really the issue. I want that dess takes control of that inverter what can be with sunspec compatible inverters or solaredge inverters. I want to use that through an virtual devices because me solis inverter doesn’t support sunspec

I build a sunspec bridge for my Growatt, does the ESS use Sunspec to limit output from PVs? If so I want to add that to my code.

No the issue is that the virtual pv inverter from node red isnt controlled by ess. Its purely between the virtual pv inverter and ess. Im looking for some know how to do this, if even possible

I did not use a virtual PV Inverter. My Cerbo talks to my sunspec bridge directly (port 502) and I make the bridge talk to my growatt inverter.

How did you make the sunspec bridge?

I currently also build my own sunspec bridge for my solis inverter but its reusable if you have a way to read you inverter with and esp32 and esphome: GitHub - remcom/sunspec-esphome: SunSpec ESPHome component for solar inverters · GitHub

Nice! i wasn’t even aware that you made one already when i made mine.

Currently I’m making a modbus sunspec server in nodered which can be used instead of the virtual pv node and supports power limitation.

Yeah, saw you did the same (great minds and all :)), I love how stable a single esp32 with esphome project can just do these things. Currently moving to Ethernet and POE versions. Also made this GitHub - mahoekst/em24-emulator: ESP32 ESPHome firmware that emulates a Carlo Gavazzi EM24 energy meter over Modbus TCP · GitHub for my Zaptec charger.

indeed great minds hahaha. i made the same :stuck_out_tongue: GitHub - remcom/victron-grid-meter-esphome: ESPHome external component: serves DSMR P1 smart meter data as Carlo Gavazzi EM24 Modbus TCP for Victron Cerbo GX · GitHub a few weeks back. im my use case to for P1 to victron for the grid meter

I created a modbus sunspec server flow for nodered which seems to work, didn’t test the power limiting yet, although the value (100%) seems to read correct.

[{"id":"0e653fa492d095a3","type":"group","z":"af5e2095a73fd6b0","name":"SunSpec TCP Modbus Server 1","style":{"label":true},"nodes":["84679027d0b86a8e","0d232675e2162ea7","6b059763da11294d","3057eb4d3fc0a4c7","8aeba526ff040a8c","69dc5d0199b5e293","d29dea8b916c6d89","d35e32def6767db0","f9d097a063c97250"],"x":54,"y":2196.5,"w":892,"h":244.5},{"id":"84679027d0b86a8e","type":"modbus-server","z":"af5e2095a73fd6b0","g":"0e653fa492d095a3","name":"TCP Modbus PV Server","logEnabled":true,"hostname":"127.0.0.1","serverPort":"5021","responseDelay":100,"delayUnit":"ms","coilsBufferSize":"1000","holdingBufferSize":"1000","inputBufferSize":"1000","discreteBufferSize":"1000","showErrors":true,"showStatusActivities":true,"x":810,"y":2260,"wires":[[],[],[],[],[]]},{"id":"0d232675e2162ea7","type":"modbus-flex-write","z":"af5e2095a73fd6b0","g":"0e653fa492d095a3","name":"","showStatusActivities":true,"showErrors":true,"showWarnings":true,"server":"347fea107e1f82f1","emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":790,"y":2340,"wires":[[],[]]},{"id":"6b059763da11294d","type":"modbus-read","z":"af5e2095a73fd6b0","g":"0e653fa492d095a3","name":"Read PV Percentage","topic":"","showStatusActivities":true,"logIOActivities":false,"showErrors":true,"showWarnings":true,"unitid":"","dataType":"HoldingRegister","adr":"155","quantity":"1","rate":"5","rateUnit":"s","delayOnStart":false,"enableDeformedMessages":false,"startDelayTime":"","server":"347fea107e1f82f1","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":200,"y":2400,"wires":[["69dc5d0199b5e293"],[]]},{"id":"3057eb4d3fc0a4c7","type":"function","z":"af5e2095a73fd6b0","g":"0e653fa492d095a3","name":"PV Data to Modbus Server","func":"/*\n  \n  SunSpec PV Modbus Server Data\n\n  Based on:\n    https://raw.githubusercontent.com/remcom/sunspec-esphome/refs/heads/master/components/sunspec/sunspec.cpp\n    https://www.forum-fronius.pl/wp-content/uploads/asgarosforum/2812/Inverter_register_map.pdf\n\n*/\n\n// Hier kun je eenvoudig waarden aanpassen of koppelen aan je sensoren\nlet inv = { ac_w: msg.ac.watt|| 0,\n            ac_a: msg.ac.amps || 0,\n            ac_v: msg.ac.volt || 0,\n            ac_hz: msg.ac.hz || 0,\n            ac_kwh: msg.ac.kwh || 0,\n            dc_w: msg.dc.watt || 0,\n            dc_v: msg.dc.volt || 0,\n            dc_a: msg.dc.amps || 0,\n            temp: msg.temp || 0 };\n\n\nlet regs68 = new Array(46).fill(0xFFFF);\n\n// --- BLOCK 2: INVERTER MODEL (S101 - 1 fase) ---\nregs68[0] = 1;\nregs68[1] = 0xFFFF;\nregs68[2] = 101; \nregs68[3] = 50;  \n\n// AC Waarden\nregs68[4] = Math.round(inv.ac_a * 100); // 40072: TotalAC Amps\nregs68[5] = Math.round(inv.ac_a * 100); // 40073: Phase A AC Amps\nregs68[8] &= -2 ;                        // 40076: AC Amps SF\nregs68[12] = Math.round(inv.ac_v);      // 40080: AC Volts\nregs68[15] = 0;                        // 40083: AC vOLTS SF\nregs68[16] = Math.round(inv.ac_w);      // 40084: AC Watts\nregs68[17] = 0;                         // 40085: AC Watts SF\nregs68[18] = Math.round(inv.ac_hz * 100); // 40086 AC Freq\nregs68[19] &= -2;                          // 40087 AC Freq SF\n\n// Energy\nregs68[26] = Math.round(inv.ac_kwh * 1000) >> 16;\nregs68[27] = Math.round(inv.ac_kwh * 1000) & 0xFFFF;\nregs68[28] = 0;                        // 40096: Wh  SF\n\n\n// DC Waarden (Nu specifiek uit de array)\nregs68[29] = Math.round(inv.dc_a * 100); // 40097: DC Amps (SF -1)\nregs68[30] &= -2;                        // 40098: DC Amps SF\nregs68[31] = Math.round(inv.dc_v);      // 40099: DC Volts\nregs68[32] = 0;                         // 40100: DC Volts SF\nregs68[33] = Math.round(inv.dc_w);      // 40101: DC Watts\nregs68[34] = 0;                         // 40102: DC Watts SF\n\nregs68[35] = Math.round(inv.temp * 10);      // 40103: Temperature\nregs68[39] &= -1;                         // 40107 Temperature SF\n\nregs68[40] = inv.ac_w > 0 ? 4 : 1;      // 40108 Generating power (4) or off (1)\n\nregs68[42] = 0;                         // 40110 Events\nregs68[43] = 0;                         // 40111 Events\nregs68[44] = 0;                         // 40112 Events\nregs68[45] = 0;                         // 40113 Events\n\nreturn {\n    payload: { value: regs68, 'fc': 16, 'unitid': 1, 'address': 68 , 'quantity': 46 }\n};\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":2340,"wires":[["0d232675e2162ea7"]]},{"id":"8aeba526ff040a8c","type":"inject","z":"af5e2095a73fd6b0","g":"0e653fa492d095a3","name":"PV Values","props":[{"p":"ac.watt","v":"pv1.ac.power","vt":"global"},{"p":"ac.volt","v":"pv1.ac.voltage","vt":"global"},{"p":"ac.amps","v":"pv1.ac.current","vt":"global"},{"p":"ac.hz","v":"pv1.ac.frequency","vt":"global"},{"p":"ac.kwh","v":"pv1.ac.energy","vt":"global"},{"p":"dc.volt","v":"pv1.dc.voltage","vt":"global"},{"p":"dc.amps","v":"pv1.dc.current","vt":"global"},{"p":"dc.watt","v":"pv1.dc.power","vt":"global"},{"p":"temperature","v":"pv1.temperature","vt":"global"}],"repeat":"2","crontab":"","once":true,"onceDelay":"5","topic":"","x":170,"y":2340,"wires":[["3057eb4d3fc0a4c7"]]},{"id":"69dc5d0199b5e293","type":"function","z":"af5e2095a73fd6b0","g":"0e653fa492d095a3","name":"Get PV Percentage","func":"msg.payload = msg.payload[0];\n\nnode.status({\n    fill: msg.payload === 100 ? \"green\" : msg.payload === 0 ? \"red\" : \"yellow\",\n    shape: \"dot\",\n    text: `PV Percentage=${msg.payload}`\n});\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":2400,"wires":[["d29dea8b916c6d89"]]},{"id":"d29dea8b916c6d89","type":"change","z":"af5e2095a73fd6b0","g":"0e653fa492d095a3","name":"","rules":[{"t":"set","p":"pv1.percentage","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":810,"y":2400,"wires":[[]]},{"id":"d35e32def6767db0","type":"function","z":"af5e2095a73fd6b0","g":"0e653fa492d095a3","name":"PV Static Blocks to Modbus Server","func":"/*\n  \n  SunSpec PV Modbus Server Data\n\n  Based on:\n    https://raw.githubusercontent.com/remcom/sunspec-esphome/refs/heads/master/components/sunspec/sunspec.cpp\n    https://www.forum-fronius.pl/wp-content/uploads/asgarosforum/2812/Inverter_register_map.pdf\n\n*/\n\nlet inv = { manufacturer: msg.manufacturer, \n            model: msg.model,\n            version: msg.version,\n            serial: msg.serial,\n            max_w: msg.maxwatt};\n\nlet regs = new Array(200).fill(0xFFFF);\n\n// --- BLOCK 1: COMMON MODEL (S1) ---\nregs[0] = 0x5375; // \"Su\"\nregs[1] = 0x6E53; // \"nS\"\nregs[2] = 1;      // Model ID 1\nregs[3] = 66;     // Lengte\n\n// Save inverter info\nfor (let i = 4; i < 68; i++) {\n    regs[i] = 0;\n}\n\n// Manufacturer\nfor (let i = 0; i < inv.manufacturer.length && i < 32; i++) {\n    let regIdx = 4 + Math.floor(i/2);\n    if (i % 2 === 0) regs[regIdx] = inv.manufacturer.charCodeAt(i) << 8;\n    else regs[regIdx] |= inv.manufacturer.charCodeAt(i);\n}\n\n// Model\nfor (let i = 0; i < inv.model.length && i < 32; i++) {\n    let regIdx = 20 + Math.floor(i/2);\n    if (i % 2 === 0) regs[regIdx] = inv.model.charCodeAt(i) << 8;\n    else regs[regIdx] |= inv.model.charCodeAt(i);\n}\n\n// Options\nfor (let i = 0; i < 8; i++) {\n    let regIdx = 36 + i;\n    regs[regIdx] = 0;\n}\n\n\n// Version\nfor (let i = 0; i < inv.version.length && i < 16; i++) {\n    let regIdx = 44 + Math.floor(i/2);\n    if (i % 2 === 0) regs[regIdx] = inv.version.charCodeAt(i) << 8;\n    else regs[regIdx] |= inv.version.charCodeAt(i);\n}\n\n\n// Serial\nfor (let i = 0; i < inv.serial.length && i < 32; i++) {\n    let regIdx = 52 + Math.floor(i/2);\n    if (i % 2 === 0) regs[regIdx] = inv.serial.charCodeAt(i) << 8;\n    else regs[regIdx] |= inv.serial.charCodeAt(i);\n}\n\n// --- BLOCK 2: INVERTER MODEL (S101 - 1 fase) ---\nregs[68] = 1;\nregs[69] = 0xFFFF;\nregs[70] = 101; \nregs[71] = 50;  \n\n// AC Waarden\nregs[72] = 0;           // 40072: TotalAC Amps\nregs[73] = 0;           // 40073: Phase A AC Amps\nregs[76] &= -2 ;        // 40076: AC Amps SF\nregs[80] = 0;           // 40080: AC Volts\nregs[83] = 0;           // 40083: AC vOLTS SF\nregs[84] = 0;           // 40084: AC Watts\nregs[85] = 0;           // 40085: AC Watts SF\nregs[86] = 0;           // 40086 AC Freq\nregs[87] &= -2;         // 40087 AC Freq SF\n\n// Energy\nregs[94] = 0;\nregs[95] = 0;\nregs[96] = 0;           // 40096: Wh  SF\n\n\n// DC Values\nregs[97] = 0;           // 40097: DC Amps (SF -1)\nregs[98] &= -2;         // 40098: DC Amps SF\nregs[99] = 0;           // 40099: DC Volts\nregs[100] = 0;          // 40100: DC Volts SF\nregs[101] = 0;          // 40101: DC Watts\nregs[102] = 0;          // 40102: DC Watts SF\n\nregs[103] = 0;          // 40103: Temperature\nregs[107] &= -1;        // 40107 Temperature SF\n\nregs[108] = 1;          // 40108 Generating power (4) or off (1)\n\nregs[110] = 0;          // 40110 Events\nregs[111] = 0;          // 40111 Events\nregs[112] = 0;          // 40112 Events\nregs[113] = 0;          // 40113 Events\n\n\n// --- Nameplate Block (Model 120) ---\nregs[122] = 120;        // Model 120\nregs[123] = 26;         // Length\nregs[124] = 4;          // DER type = PV\nregs[125] = inv.max_w;  // WRtg (watts, SF=0)\nregs[126] = 0;          // Power SF = 0\n\n\n// --- Controls Block (Model 123) ---\nregs[150] = 123;        // Model ID  123\nregs[151] = 24;         // Length\nregs[155] = 100;        // WMaxLimPct default = 100 (no limit)\nregs[157] = 0;          // WMaxLimPct_RvrtTms = 0 (no auto-revert)\nregs[159] = 1;          // WMaxLim_Ena = 1 (enabled)\nregs[173] = 0;          // WMaxLimPct_SF = 0\n\nlet regs8bit = [];\n\nregs.forEach(num => {\n    let msb = (num >> 8) & 0xFF;\n    let lsb = num & 0xFF;\n    regs8bit.push(msb, lsb);\n});\n\nreturn {payload :  { 'value': regs8bit, 'register': 'holding', 'address': 0 , 'disableMsgOutput' : 0 }};","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":2260,"wires":[["84679027d0b86a8e"]]},{"id":"f9d097a063c97250","type":"inject","z":"af5e2095a73fd6b0","g":"0e653fa492d095a3","name":"PV Static Values","props":[{"p":"manufacturer","v":"Growatt","vt":"str"},{"p":"model","v":"PV1 2000","vt":"str"},{"p":"serial","v":"0001","vt":"str"},{"p":"version","v":"1.0","vt":"str"},{"p":"maxwatt","v":"2000","vt":"num"}],"repeat":"","crontab":"","once":true,"onceDelay":"0.1","topic":"","x":190,"y":2260,"wires":[["d35e32def6767db0"]]},{"id":"347fea107e1f82f1","type":"modbus-client","name":"","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"failureLogEnabled":true,"tcpHost":"127.0.0.1","tcpPort":"5021","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":9600,"serialDatabits":8,"serialStopbits":1,"serialParity":"none","serialConnectionDelay":100,"serialAsciiResponseStartDelimiter":"0x3A","unit_id":1,"commandDelay":1,"clientTimeout":1000,"reconnectOnTimeout":true,"reconnectTimeout":2000,"parallelUnitIdsAllowed":true,"showErrors":false,"showWarnings":true,"showLogs":true},{"id":"9aaab91be38b9afd","type":"global-config","env":[],"modules":{"node-red-contrib-modbus":"5.45.2"}}]

Thanks @matthijshoekstra

I was looking for this.

I am using it right now with my Growatt MIN 3600TL and the ShineWifi-X (USB) stick. https://www.stralendgroen.nl/wp-content/uploads/2020/06/Growatt-Shinewifi-X.png?v=1a13105b7e4e

I was able to install ESPHome on it and the sensors are show in HA and the inverter is shown in my CerboGX.

It seems to work pretty well but your code is designed for a 3-phase inverter and the inverter is shown as a 3-phase inverter in my CerboGX

The Growatt MIN 3600TL is a single phase inverter.

Is there any possibility to add a configuration option for single or 3-phase system?

@matthijshoekstra

I have made a pull request for support for single phase.