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.
Sungrow-Target_SOC-Export

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.

1 Like

Hello @jpwise

I am trying to control my Sungrow SH10RT-20 according to your instructions.

The Modbus Sungrow Binding runs without problems and also the reading of further input registers (backup data register 5720…) via item and thing files worked wonderfully.

But I just can’t manage to create the mapping for parameter 13049 without errors.

2025-07-07 13:51:22.134 [WARN ] [al.profiles.MapTransformationProfile] - Could not transform state ‘0’ with function ‘sungrowEmsModeSelection’ and format '%s'

I have really tried everything. Can you share some screenshots of your settings, especially the mapping?

ModBus Data Thing Created

* Read/Write MAP transform for 0 = ON, 2 = OFF

I found out that the MAP file must be in the thing configuration and not in the item configuration. As I have done all the time.

image

image

Are you aware that you are mapping the value itself and not just how it’s displayed?
I doubt that is what you want.
For display only mapping, use the stateDescription metadata input field in the UI.