Morning all, did a summary version of this in the sungrow binding thread, but had been meaning to do an expanded version as I was implementing it.
EDIT: In typical fashion, Sungrow has now updated the firmware to “fix” export occurring when set to 0 export rate… So the below will still dynamically switch the self consumption & forced operation mode - but you won’t gain any DC coupled charging while it’s in forced mode unless you configure a charge rate. If your PV can’t supply enough to accomdate - it’ll draw from the grid again.
Intended Goal
- My power plan includes 3hrs TOU free power from 9pm → midnight.
- Instead of fully charging the battery during summer - I only want to charge it to 50 or 60% depending on the whether - then focus on exporting so I can maximize export earnings, especially during the warmer months.
- After sundown, switch back to battery until 9pm when I can charge it again for free.
Implementation - using OH 4.2.2 for Sungrow SH6.0RS with 13.8kw battery
-
Install SolarForeCast binding
- Configure as required
- For my purposes - I used both Solcast & ForeCastSolar channels
- Then created a grouped with Aggregate Average to estimate daily generation.
-
Install Sungrow binding
- Configure as required
- For my purposes I set a 60,000ms (60 second) poll interval
- I also have other things such as SunGather using modbus, so don’t want conflicts
- Then configure channels for at minimum
- Battery Level = Battery SOC.
- Had some issues with the value being /100 - setting ‘Offset’ profile with a value of 0 corrected this.
- Load Power = Household Load
- Total DC Power = PV generation
- Battery Level = Battery SOC.
-
Create ModBus Poller Thing
- Using ModBus slave to Inverter
- 60,000ms poll interval (60 seconds)
- Start 13049 (zero indexed 13050 for EMS mode)
- Length 1
- Type: Holding Register
-
ModBus Data Thing Created
- Read Address: 13049
- Write Address: 13049
- Write Type: holding register
- All register types defined as uint16
- Read/Write MAP transform for 0 = ON, 2 = OFF
- Channel Linked - Value as Switch - Labelled ‘Solar Self Consumption Mode’
- The above basically gives you a switch which can turn Self Consumption mode ON or OFF via OH.
-
13049 - EMS - Transform Rule
- I used a plain MAP type - can be implemented in file, or UI.
- For convenience I used UI and named it as:
- ID: sungrow_13049_ems_mode
- Label: Sungrow - Self Consumption Mode
- MAP value’s
- 0=ON
- 2=OFF
- ON=0
- OFF=2
- If implemented using file - set read/write transform to MAP(filename.map)
- If implemented using UI - set read/write transform to MAP:< transformation-uid >
- The UID can be copied from the UI after creating
- Example: config:map:sungrow_13049_ems_mode
-
OH/Blockly Rule Built to:
Rule Trigger - PV generation > 0 (noting PV value update set for 60seconds above)
Run - inline script - Blockly
Script Blocks- Assign all required variables - so we don’t have to set them in more than one place.
- Calculate Target_SOC using a function to comparing forecast generation against an arbitrary list. Return the TARGET_SOC value.
- I’ve used a dictionary implementation using the key as the kwh threshold, and the value as the target SOC.
- 11kwh or less - 100% target, then steps the target down 10% for every extra in 2kw generation until 21kwh+ is only 50% target charge.
- The dictionary is then iterated through using the for each in list block - with an inline script to retrieve the index value (for kwh comparison).
- As it interates through the list - the target SOC ends on it’s lowest suitable targetted value.
- I’ve used a dictionary implementation using the key as the kwh threshold, and the value as the target SOC.
- IF conditions check:
- Battery_SOC is >= Target_SOC
- AND
- Load <= PV - to make sure we’re covering at least our present consumption - otherwise it’ll start drawing from the grid
- Conditions Met
- Call Cmd function - which will check if the switch state <> requested state, and if not - issue the command to set it OFF.
- Conditions NOT met - Call Cmd function - check state, and then set Self Consumption mode to ON - normal operation/draw from battery.
Result
- Sunny days - once target SOC is hit - unit switches to export mode
- Bonus side effect - as the unit is DC Coupled - if the generation exceeds the max export + household load - the unit will automatically divert the surplus to the DC coupled battery
- So where previously production would have been clipped at maximum AC capacity, it’s now consumed by the DC battery instead = more usable generation
- General Use - if load exceeds generation - self-consumption mode is turned back on within 60 seconds - so if any high draw items are turned on - ie: oven, dryer, etc - it resumes normal operation and draws from the battery.
- Rainy days (less than 11kwh forecast) - SOC target is 100% - it stays in self consumption mode - normal/factory default operation.
Footnotes
The export in Forced mode is achieved in tandem with the Forced Charge/Discharge settings, and Forced Charge/Discharge rate.
So for my set up:
Modbus 13050 (zero indexed 13051) - Charge/Discharge Mode = 0xCC Hex - Stop - Default mode
Modbus 13051 (zero indexed 13052) - Charge/Discharge Power = 0 (zero) - rate is limited to 0 watts.
Sidenote: Since these 3 registers EMS, Mode, and Power are sequential, it would probably be easy enough to expand the range for Modbus Polling to 3, and then add the other 2 registers data.
But otherwise - in forced mode at 0 power rate - it’s not going to draw from the battery once it’s reached the intended SOC target, and similarly not going to export back to the grid. Then once 9pm hits - the forced charge mode that’s set in the inverter kicks in, and it charges it up to 100% before midnight.
The full blockly view for anyone else looking to implement is copied below.
The operation during the day looks like this, 48% up to 50%, then export, with excess throughout the day gradually bumping the battery up to 56% before switching back to self consumption mode once the PV dropped off.
Learnt a fair bit on blockly eccentricates since this is my first implementation using it. Also quite likely to continue tweaking things as I go, but for now - it’s a functional implementation, and others are welcome to use or adapt as may benefit them.
Thx
Jp.