Sungrow Hybrid Inverter - Target Battery SOC & Optimized Exporting using Modbus

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. :confused:

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
  • 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.
    • 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.
iSolarCloud

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. :slight_smile:

Thx
Jp.