Sungrow Binding

Hey,

i’ve implemented a new binding that allows monitoring values from sungrow inverters without caring about any modbus registers, adresses, conversions and so on.

The plugin just requires a default modbus bridge (most common the tcp bridge) and the sungrow inverter thing, that is available withing the modbus binding.

I’ve successfully tested the plugin with my SH10RT and it’s working fine, but i’ve no battery yet, so i can’t check if the battery information are working too. So it would be great to get some feedback about other inverters and battery information.

Further information can be found in binding readme:
https://github.com/soenkekueper/openhab-addons/tree/sungrow/bundles/org.openhab.binding.modbus.sungrow

Jar files and source code can be found here:

direct links:

openHAB 3.4.x:
https://github.com/soenkekueper/openhab-addons/releases/download/sungrow-beta/org.openhab.binding.modbus.sungrow-3.4.5-SNAPSHOT.jar

openHAB 4.x
https://github.com/soenkekueper/openhab-addons/releases/download/sungrow-beta/org.openhab.binding.modbus.sungrow-4.0.0-SNAPSHOT.jar

You may also leave a comment within the PR: [sungrow][WIP] Initial contribution by soenkekueper · Pull Request #15130 · openhab/openhab-addons · GitHub

Thanks for your feedback!

4 Likes

Hello Sönke,
thank you for the binding. I have a Sungrow SH8.0RT Inverter and a 5.1 kWh battery. At the moment I am using the “bohdans/sungather” under docker.

After the installation of your binding I get the following Warning every 5 seconds:

2023-06-25 17:56:31.438 [WARN ] [rt.modbus.internal.ModbusManagerImpl] - Execution of scheduled (5000ms) poll task BasicPollTask [getEndpoint=ModbusIPSlaveEndpoint [address=192.168.2.125, port=502], request=ModbusReadRequestBlueprint [slaveId=1, functionCode=READ_INPUT_REGISTERS, start=13001, length=46, maxTries=1], getResultCallback()=org.openhab.binding.modbus.sungrow.internal.SungrowInverterHandler$$Lambda$2524/0x00000007c13f9840@6be2f6e3, getFailureCallback()=org.openhab.binding.modbus.sungrow.internal.SungrowInverterHandler$$Lambda$2525/0x00000007c13f9c40@58ad37a7] failed unexpectedly. Ignoring exception, polling again according to poll interval.

java.lang.IllegalArgumentException: Value must be between 0 and 100
	at org.openhab.core.library.types.PercentType.validateValue(PercentType.java:93) ~[?:?]
	at org.openhab.core.library.types.PercentType.<init>(PercentType.java:88) ~[?:?]
	at org.openhab.binding.modbus.sungrow.internal.SungrowInverterRegisters.createState(SungrowInverterRegisters.java:212) ~[?:?]
	at java.util.Optional.map(Optional.java:265) ~[?:?]
	at org.openhab.binding.modbus.sungrow.internal.SungrowInverterHandler.lambda$4(SungrowInverterHandler.java:167) ~[?:?]
	at java.util.Optional.ifPresent(Optional.java:183) ~[?:?]
	at org.openhab.binding.modbus.sungrow.internal.SungrowInverterHandler.readSuccessful(SungrowInverterHandler.java:156) ~[?:?]
	at org.openhab.binding.modbus.sungrow.internal.SungrowInverterHandler.lambda$2(SungrowInverterHandler.java:149) ~[?:?]
	at org.openhab.core.io.transport.modbus.internal.ModbusLibraryWrapper.invokeCallbackWithResponse(ModbusLibraryWrapper.java:337) ~[?:?]
	at org.openhab.core.io.transport.modbus.internal.ModbusManagerImpl$PollOperation.lambda$1(ModbusManagerImpl.java:216) ~[?:?]
	at org.openhab.core.io.transport.modbus.internal.SimpleStopWatch.timeRunnable(SimpleStopWatch.java:152) ~[?:?]
	at org.openhab.core.io.transport.modbus.internal.ModbusManagerImpl$PollOperation.accept(ModbusManagerImpl.java:216) ~[?:?]
	at org.openhab.core.io.transport.modbus.internal.ModbusManagerImpl$PollOperation.accept(ModbusManagerImpl.java:1) ~[?:?]
	at org.openhab.core.io.transport.modbus.internal.ModbusManagerImpl.executeOperation(ModbusManagerImpl.java:614) ~[?:?]
	at org.openhab.core.io.transport.modbus.internal.ModbusManagerImpl$ModbusCommunicationInterfaceImpl.lambda$1(ModbusManagerImpl.java:812) ~[?:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) [?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) [?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
	at java.lang.Thread.run(Thread.java:829) [?:?]

The actual values you can see in the picture below

BR
Ulf

Hey,

thanks for testing and fast reply.

This issue may be caused by three values/registers, as they are converted to the PercentType:

  • Battery Level (13023)
  • Battery Healthy (13024)
  • Self Consumption rate (13029)

The all return a percent value, with scale 0.1% means, the read value is diveded by 10 to get the percentage value. The specification and implementation is the same for all three values and is working at my installation for the self consumption rate. This said, is assume that some of the battery values are not correct read/interpreted/documentated.

Can you try to read the above registers (for example with QModMaster QModMaster download | SourceForge.net) as decimal values and check if the value is like 455 (for 45,5%) and matches the values you can see in your iSolarCloud App?

When i read this registers (without battery) the values simply return 0, which is valid.

Thanks :slight_smile:

Hello,
I will use QModMaster tomorrow.

Actual with from the container output, here are my values:

{"model": "SH8.0RT", "serial_number": "A222xxxxx1", "level": 2, "scan_interval": 30, "use_local_time": true, "smart_meter": false, "connection": "sungrow", "slave": 1, "start_time": "", "host": "192.168.2.125", "port": 502, "timeout": 10, "retries": 3, "RetryOnEmpty": false, "device_type_code": "SH8.0RT", "run_state": "ON", "last_reset": "2023-06-26 00:00:17", "daily_export_to_grid": 5.250983333333331, "daily_import_from_grid": 0.04772500000000002, "protocol_number": 1094856704, "protocol_version": 16781568, "arm_software_version": 16722, "dsp_software_version": 19780, "nominal_active_power": 8.0, "output_type": "3P4L", "daily_power_yields": 13.7, "total_power_yields": 25667, "internal_temperature": 40.8, "mppt_1_voltage": 246.8, "mppt_1_current": 0.8, "mppt_2_voltage": 521.0, "mppt_2_current": 1.2, "total_dc_power": 870, "phase_a_voltage": 235.2, "phase_b_voltage": 234.1, "phase_c_voltage": 236.2, "total_reactive_power": -4, "power_factor": 1.0, "grid_frequency": 49.9, "active_power_regulation_setpoint": 0, "daily_running_time": 0, "export_limit_min": 0, "export_limit_max": 9750, "bdc_rated_power": 8000, "bms_max_charging_current": 0, "bms_max_discharging_current": 25, "pv_power_of_today": 2439, "daily_pv_energy_yields": 35.6, "monthly_pv_energy_yields": 0, "yearly_pv_energy_yields": 0.0, "direct_power_consumption_today_pv": 451, "direct_power_consumption_pv": 4.1, "direct_power_consumption_monthly_pv": 0.0, "direct_power_consumption_yearly_pv": 0.0, "export_power_from_pv_today": 1988, "export_power_from_pv": 26.8, "export_power_from_pv_monthly": 0.0, "export_power_from_pv_yearly": 0.0, "battery_charge_power_from_pv_today": 0.0, "battery_charge_power_from_pv": 4.7, "battery_charge_power_from_pv_monthly": 0.0, "battery_charge_power_from_pv_yearly": 0.0, "system_state": "Run", "running_state": 25, "state_power_generated_from_pv": 1, "state_battery_charging": 0, "state_battery_discharging": 0, "state_load_active": 1, "state_feed_into_grid": 1, "state_import_from_grid": 0, "state_power_generated_from_load": 0, "daily_pv_generation": 14.9, "total_pv_generation": 2694.7, "daily_pv_export": 8.6, "total_pv_export": 1829.5, "load_power_hybrid": 317, "export_power_hybrid": 553, "daily_battery_charge_from_pv": 4.1, "total_battery_charge_from_pv": 422.3, "co2_reduction": 1886.2, "daily_direct_energy_consumption": 2.2, "total_direct_energy_consumption": 442.9, "battery_voltage": 218.3, "battery_current": 0.0, "battery_power": 0, "battery_level": 100.0, "battery_state_of_healthy": 100.0, "battery_temperature": 25.0, "daily_battery_discharge_energy": 3.8, "total_battery_discharge_energy": 383.6, "self_consumption_of_day": 20.3, "grid_state": null, "phase_a_current": 1.2, "phase_b_current": 1.2, "phase_c_current": 1.2, "total_active_power": 870, "daily_import_energy": 0.0, "total_import_energy": 81.8, "daily_charge_energy": 4.1, "total_charge_energy": 422.2, "drm_state": 255, "daily_export_energy": 8.7, "total_export_energy": 1836.8, "start_stop": "Start", "power_limitation_switch": "Enable", "power_limitation_setting": 100.0, "power_factor_setting": 0.0, "reactive_power_adjustment_mode": "Off", "reactive_power_percentage_setting": 0.0, "power_limitation_adjustment": 0.0, "reactive_power_adjustment": 0.0, "start_charging_power": 0, "start_discharging_power": 0, "energy_meter_comm": "Enabled", "export_power_limitation": "Disabled", "soc_reserve": 0, "timestamp": "2023-06-26 10:22:08", "export_to_grid": 553, "import_from_grid": 0}

Thx

No success with QModMaster

grafik

:face_exhaling:

Sorry,

i did not explain what to do with this tool - as is has several options it is not self-explaining.

  • Connect via TCP to your sungrow inverter (as you did already)
  • Select Function Code Read Input Registers (0x04)
  • Select Start Address as listed above (13023, 13024, 13029)
  • Press Read/Write
  • The result is shown at the bottom, in my case i’ve read the self consumtion rate, which is 16,8%.

  • Sönke

Hi,
The values for the addresses are:

  • Address 13023 = 1000
  • Address 13024 = 1000
  • Address 13029 = 277

BR Ulf

Hey Ulf,

i’ve checked this and it figured out as bug while scaling the values.
As my battery is always 0% it never occured here, happy guy with 100% battery level :wink:

I’ve uploaded an fixed version right now:

Thanks for testing again :slight_smile:

BR

Sönke

Apply a transformation that divides the raw value by 10 before putting it into the item.

markus@emspc:/etc/openhab/things/STORE $ grep 13023 */*
pv/sungrow.things:        Thing data SungrowStateOfHealth "Sungrow SoH Speicherzustand" [readStart = "13023", readValueType="uint16", readTransform="JS(divide10.js)"]
markus@emspc:/etc/openhab/things/STORE $ cat ../../transform/divide10.js
// Wrap everything in a function (no global variable pollution)
// variable "input" contains data passed by openHAB
(function(inputData) {
    // on read: the polled number as string
    // on write: openHAB command as string
    var DIVIDE_BY = 10;
    return parseFloat(inputData) / DIVIDE_BY;
})(input)

Now the log is ok. :+1:
But there are some values I don’t understand i.e. the battery capacity

May be the item definition is not correct, especially the Number:Dimensionless items

Number:Power                SG_Overview_TotalActivePower            "Total Active Power [%.1f %unit%]"                      <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-overview#sg-total-active-power" }
Number:Power                SG_Overview_TotalDcPower                "Total DC Power [%.1f %unit%]"                          <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-overview#sg-total-dc-power" }
Number:Energy               SG_Overview_DailyPvGeneration           "Daily PV Generation [%.1f %unit%]"                     <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-overview#sg-daily-pv-generation" }
Number:Energy               SG_Overview_TotalPvGeneration           "Total PV Generation [%.1f %unit%]"                     <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-overview#sg-total-pv-generation" }

Number:Power                SG_BatteryInfo_BatteryPower             "Battery Power [%.1f %unit%]"                           <pv_battery>            (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-battery-power" }
Number:Dimensionless        SG_BatteryInfo_BatteryLevel             "BatteryLevel [%.3f %%]"                                <batterylevel>          (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-battery-level" }
Number:Dimensionless        SG_BatteryInfo_BatteryHealthy           "BatteryHealthy [%.3f %%]"                              <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-battery-healthy" }
Number:Temperature          SG_BatteryInfo_BatteryTemperature       "Battery Temperature [%.1f %unit%]"                     <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-battery-temperature" }
Number:Energy               SG_BatteryInfo_BatteryCapacity          "Battery Capacity [%.1f %unit%]"                        <pv_battery>            (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-battery-capacity" }
Number:Energy               SG_BatteryInfo_DailyChargeEnergy        "Daily Charge Energy [%.1f %unit%]"                     <pv_panel_to_battery>   (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-daily-charge-energy" }
Number:Energy               SG_BatteryInfo_TotalChargeEnergy        "Total Charge Energy [%.1f %unit%]"                     <pv_panel_to_battery>   (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-total-charge-energy" }
Number:Energy               SG_BatteryInfo_DailyBatteryCharge       "Daily Battery Charge [%.1f %unit%]"                    <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-daily-battery-charge" }
Number:Energy               SG_BatteryInfo_TotalBatteryCharge       "Total Battery Charge [%.1f %unit%]"                    <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-total-battery-charge" }
Number:Energy               SG_BatteryInfo_DailyBatteryDischarge    "Daily Battery Discharge [%.1f %unit%]"                 <pv_battery_to_home>    (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-daily-battery-discharge-energy" }
Number:Energy               SG_BatteryInfo_TotalBatteryDischarge    "Total Battery Discharge [%.1f %unit%]"                 <pv_battery_to_home>    (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-battery-information#sg-total-battery-discharge-energy" }

Number:Energy               SG_GridInfo_DailyExportEnergy           "Daily Export Energy [%.1f %unit%]"                     <pv_panel_to_grid>      (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-grid-information#sg-daily-export-energy" }
Number:Energy               SG_GridInfo_TotalExportEnergy           "Total Export Energy [%.1f %unit%]"                     <pv_panel_to_grid>      (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-grid-information#sg-total-export-energy" }
Number:Energy               SG_GridInfo_DailyImportEnergy           "Daily Import Energy [%.1f %unit%]"                     <pv_grid_to_home>       (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-grid-information#sg-daily-import-energy" }
Number:Energy               SG_GridInfo_TotalImportEnergy           "Total Import Energy [%.1f %unit%]"                     <pv_grid_to_home>       (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-grid-information#sg-total-import-energy" }
Number:Power                SG_GridInfo_DailyExportPowerFromPv      "Daily Export Power from PV [%.1f %unit%]"              <pv_panel_to_grid>      (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-grid-information#sg-daily-export-power-from-pv" }
Number:Energy               SG_GridInfo_TotalExportPowerFromPv      "Total Export Power from PV [%.1f %unit%]"              <pv_panel_to_grid>      (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-grid-information#sg-total-export-energy-from-pv" }
Number:Power                SG_GridInfo_ExportPower                 "Export Power [%.1f %unit%]"                            <pv_panel_to_grid>      (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-grid-information#sg-export-power" }

Number:Power                SG_LoadInfo_LoadPower                   "Load Power [%.1f %unit%]"                              <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-load-information#sg-load-power" }
Number:Energy               SG_LoadInfo_DDEC                        "Daily Direct Energy Consumption [%.1f %unit%]"         <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-load-information#sg-daily-direct-energy-consumption" }
Number:Energy               SG_LoadInfo_TDEC                        "Total Direct Energy Consumption [%.1f %unit%]"         <pv_panel>              (gAutoPvRrd)                    { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-load-information#sg-total-direct-energy-consumption" }
Number:Dimensionless        SG_LoadInfo_SelfConsumptionToday        "Self Consumption Today [%.3f %%]"                                                                              { channel="modbus:sungrow-inverter:b27d83c504:902b11b70f:sg-load-information#sg-self-consumption-today" }

Hey,

i’ve just checked the spec for battery capacity: there is actually a note, that it is only applicable to a set of types and yours (and mine) is not listed. So what would you say, leave it and add a comment in documentation or remove the channel?

The Number:Dimensionless-Channels (the three mentioned above with the percentage values) is very confusing. I had the same issue during implementation and changed the spec to Number:Dimensionless so that the percentage values are displayed correctly:

But when testing this in openHAB 3.x its wrong, i’ve to check if there was any change there and adjust the 3.x backport accordingly.

  • Sönke

Hi,

My personal meaning is add a comment in the documentation, if possible with a link to the note.

I’m using OH 3.4.2, so the percent values are wrong, but hopefully OH 4 is coming soon (about 4 weeks) I will live with it :innocent:.

Another question about the day change of the values. With the “bohdans/sungather” functionality I have the problem that the day change does not occur at midnight (Germany), but at “18:00” (that’s the difference of about 6 hours like the time zones from Germany to China) . Can you tell me (I assume you also live in Germany) how the binding handles this? Since I didn’t find a solution about the settings, I solve the problem using rules that use the historical totals.

Unfortunately, my old solution and your binding don’t work properly in parallel, so I’ve currently disabled the thing for the binding so as not to lose my history.
But I’ll see how I can solve it.

Hey,

the binding does just provide the information read from the inverter via modbus - this said, the day-change is performed by the device not by the binding. If i’m right the sungather also just reads the modbus registers and provide the values, so the result will be the same. So both approches don’t use any of the sungrow servers (with is one of the main benefits of this solutions) so i assume that this must be an configuration error within your inverter. Did you check the local time on you inverter? When i logon to my WiNet-S dongle and check the system time it is just the correct local time.

Anyway i’ve just checked this within my installation and it seems right, the daily pv production value drops to zero just at midnight as you can see here:

Sönke

OK. So I will see to my inverter.

Hey Ulf,
just one short question:
Which iconset are you using?
The icons you’ve used are pretty nice and i’m not able to find them in any of the provided icon sets.

Thanks!

Sönke

The icons are inspired by the isolarcloud app. I’m on vacation at the moment, then I can upload them.

Hey,

i’ve created an openHAB widget that shows all important information about PV production and usage.
The widget uses the binding items by default and i tried to make it look like the original widget within the iSolarCloud app/web page.

In addition to the current power produced/consumed per source/target the widget also shows the total energy produced/consumed per target per day.

It works well in my installation - only battery is not testet yet (but my battery will be installed next week), so there might be an update afterwards.

widget source:

uid: iSolarCloudWidget
tags:
  - card
props:
  parameters:
    - default: PV Status
      label: Header Text
      name: headerText
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Total_DC_Power
      label: Solar Power
      name: solarPower
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Daily_PV_Generation
      description: Solar Energy
      label: Solar Energy
      name: solarEnergy
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Load_Power
      description: Current Load Power
      label: Power Consumption Home
      name: loadPower
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Daily_Import_Energy
      label: Daily Import Energy
      name: importEnergy
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Daily_Direct_Energy_Consumption
      description: Daily Direct Energy Consumption
      name: directEnergyConsumption
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Export_Power
      description: Export Power
      name: exportPower
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Daily_Export_Energy
      description: Daily Export Energy
      name: exportEnergy
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Battery_Level
      description: Battery Level
      name: batteryLevel
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Daily_Charge_Energy
      description: Battery Charge Energy
      name: batteryChargeEnergy
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Daily_Battery_Discharge_Energy
      description: Battery Discharge Energy
      name: batteryDischargeEnergy
      required: true
      type: TEXT
    - context: item
      default: Sungrow_Inverter_Battery_Power
      description: Battery Power
      name: batteryPower
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Jul 15, 2023, 11:03:56 AM
component: f7-card
config:
  style:
    height: 385px
    width: 330px
  title: =props.headerText
slots:
  default:
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><line x1="150" y1="80" x2="150" y2="205" style="stroke:lightgrey;stroke-width:2"/></svg>')
          border-style: solid
          border-width: 0px
          height: 300px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><line x1="150" y1="80" x2="150" y2="205" style="stroke:orange;stroke-width:2"/><polygon points="144,140 156,140 150,147" fill="orange"/></svg>')
          border-style: solid
          border-width: 0px
          height: 300px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
        visible: =Number.parseFloat(items[props.solarPower].state) > 0
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><line x1="150" y1="205" x2="250" y2="267" style="stroke:lightgrey;stroke-width:2"/></svg>')
          border-style: solid
          border-width: 0px
          height: 300px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><line x1="150" y1="205" x2="250" y2="267" style="stroke:orange;stroke-width:2"/><polygon points="194,236 206,236 200,243" fill="orange" transform="rotate(120 200 236)"/></svg>')
          border-style: solid
          border-width: 0px
          height: 300px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
        visible: =Number.parseFloat(items[props.exportPower].state) < 0
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><line x1="150" y1="205" x2="50" y2="267" style="stroke:lightgrey;stroke-width:2"/></svg>')
          border-style: solid
          border-width: 0px
          height: 300px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><line x1="150" y1="205" x2="50" y2="267" style="stroke:orange;stroke-width:2"/><polygon points="94,236 106,236 100,243" fill="orange" transform="rotate(-120 100 236)"/></svg>')
          border-style: solid
          border-width: 0px
          height: 300px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
        visible: =Number.parseFloat(items[props.batteryPower].state) > 0 && Number.parseFloat(items[props.loadPower].state) > Number.parseFloat(items[props.solarPower].state)
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><path d="M 150 80 A 125 125 0 0 1 250 267" stroke="lightgrey" stroke-width="2" fill="none"/></svg>')
          border-style: solid
          border-width: 0px
          height: 300px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><path d="M 150 80 A 125 125 0 0 1 250 267" stroke="orange" stroke-width="2" fill="none"/><polygon points="239,133 251,133 245,128" fill="orange" transform="rotate(140 245 133)"/></svg>')
          border-style: solid
          border-width: 0px
          height: 300px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
        visible: =Number.parseFloat(items[props.exportPower].state) > 0
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><path d="M 150 80 A 125 125 0 0 0 50 267" stroke="lightgrey" stroke-width="2" fill="none"/></svg>')
          border-style: solid
          border-width: 0px
          height: 300px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><path d="M 150 80 A 125 125 0 0 0 50 267" stroke="orange" stroke-width="2" fill="none"/><polygon points="61,133 49,133 55,138" fill="orange" transform="rotate(40 55 133)"/></svg>')
          border-style: solid
          border-width: 0px
          height: 300px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
        visible: =Number.parseFloat(items[props.batteryPower].state) > 0 && Number.parseFloat(items[props.loadPower].state) < Number.parseFloat(items[props.solarPower].state)
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><path d="M 257 267 A 125 125 0 0 1 50 267" stroke="lightgrey" stroke-width="2" fill="none"/></svg>')
          border-style: solid
          border-width: 0px
          height: 327px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><path d="M 257 267 A 125 125 0 0 1 50 267" stroke="orange" stroke-width="2" fill="none"/><polygon points="144,323 156,323 150,328" fill="orange" transform="rotate(270 150 322)"/></svg>')
          border-style: solid
          border-width: 0px
          height: 330px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 35px
          width: 300px
        visible: =Number.parseFloat(items[props.batteryPower].state) > 0 && Number.parseFloat(items[props.loadPower].state) > Number.parseFloat(items[props.solarPower].state) && Number.parseFloat(items[props.batteryPower].state) > Number.parseFloat(items[props.loadPower].state)
    - component: f7-block
      config:
        style:
          background-color: '=themeOptions.dark === "dark" ? "black" : "white"'
          border-color: orange
          border-radius: 50%
          border-style: solid
          border-width: 2px
          height: 60px
          left: 120px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 85px
          width: 60px
    - component: f7-block
      config:
        style:
          background-color: '=themeOptions.dark === "dark" ? "black" : "white"'
          border-color: orange
          border-radius: 50%
          border-style: solid
          border-width: 2px
          height: 60px
          left: 120px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 200px
          width: 60px
    - component: f7-block
      config:
        style:
          background-color: '=themeOptions.dark === "dark" ? "black" : "white"'
          border-color: orange
          border-radius: 50%
          border-style: solid
          border-width: 2px
          height: 60px
          left: 216px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 272px
          width: 60px
    - component: f7-block
      config:
        style:
          background-color: '=themeOptions.dark === "dark" ? "black" : "white"'
          border-color: orange
          border-radius: 50%
          border-style: solid
          border-width: 2px
          height: 60px
          left: 20px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 272px
          width: 60px
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m80-80 80-400h640l80 400H80Zm40-740v-60h120v60H120Zm34 680h296v-110H176l-22 110Zm80-460-43-42 85-85 43 42-85 85Zm-46 290h262v-110H210l-22 110Zm292-390q-74 0-125-52.5T300-880h60q3 50 37 85t83 35q49 0 83-35t37-85h60q-4 75-55 127.5T480-700Zm0-180Zm-30 360v-120h60v120h-60Zm60 380h296l-22-110H510v110Zm0-170h262l-22-110H510v110Zm216-291-85-85 42-42 86 84-43 43Zm-6-219v-60h120v60H720Z" fill="orange"/></svg>')
          background-position: center
          background-repeat: no-repeat
          background-size: 100% 100%
          height: 35px
          left: 133px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 97px
          width: 35px
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m286-79 171-307-337-40 495-455h59L503-574l337 40L345-79h-59Zm167-187 252-223-297-36 99-169-253 224 297 35-98 169Zm27-214Z" fill="orange"/></svg>')
          background-position: center
          background-repeat: no-repeat
          background-size: 100% 100%
          height: 35px
          left: 228px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 285px
          width: 35px
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-180h150v-250h220v250h150v-390L480-765 220-570v390Zm-60 60v-480l320-240 320 240v480H530v-250H430v250H160Zm320-353Z" fill="orange"/></svg>')
          background-position: center
          background-repeat: no-repeat
          background-size: 100% 100%
          height: 35px
          left: 133px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 211px
          width: 35px
    - component: f7-block
      config:
        style:
          background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M174-280q-12.75 0-21.375-8.625T144-310v-90H80v-160h64v-90q0-12.75 8.625-21.375T174-680h676q12.75 0 21.375 8.625T880-650v340q0 12.75-8.625 21.375T850-280H174Zm30-60h616v-280H204v280Z" fill="orange"/></svg>')
          background-position: center
          background-repeat: no-repeat
          background-size: 100% 100%
          height: 35px
          left: 32px
          margin-left: auto
          margin-right: auto
          margin-top: auto
          position: absolute
          top: 275px
          width: 35px
    - component: f7-block
      config:
        style:
          border-style: solid
          border-width: 0px
          height: 350px
          left: 0px
          margin-left: auto
          margin-right: auto
          margin-top: 10px
          position: absolute
          text-align: right
          width: 350px
      slots:
        default:
          - component: Label
            config:
              style:
                font-size: 12px
                left: 130px
                position: absolute
                top: 0px
                width: 50px
              text: =(items[props.solarPower].state)
          - component: Label
            config:
              style:
                font-size: 12px
                left: 130px
                position: absolute
                top: 15px
                width: 50px
              text: =(items[props.solarEnergy].state)
          - component: Label
            config:
              style:
                font-size: 12px
                left: 90px
                position: absolute
                top: 105px
                width: 50px
              text: = Math.min(Number.parseFloat(items[props.loadPower].state), Number.parseFloat(items[props.solarPower].state)) + " " + items[props.loadPower].state.split(" ")[1]
          - component: Label
            config:
              style:
                font-size: 12px
                left: 90px
                position: absolute
                top: 120px
                width: 50px
              text: =(items[props.directEnergyConsumption].state)
          - component: Label
            config:
              style:
                font-size: 12px
                left: 120px
                position: absolute
                top: 215px
                width: 50px
              text: =(items[props.loadPower].state)
          - component: Label
            config:
              style:
                font-size: 12px
                left: 120px
                position: absolute
                top: 230px
                width: 50px
              text: = (Number.parseFloat(items[props.importEnergy].state) + Number.parseFloat(items[props.directEnergyConsumption].state) + Number.parseFloat(items[props.batteryDischargeEnergy].state)).toFixed(1) + " " + items[props.importEnergy].state.split(" ")[1]
          - component: Label
            config:
              style:
                font-size: 12px
                left: 250px
                position: absolute
                top: 80px
                width: 60px
              text: "=Number.parseFloat(items[props.exportPower].state) > 0 ? items[props.exportPower].state : '0 W'"
          - component: Label
            config:
              style:
                font-size: 12px
                left: 250px
                position: absolute
                top: 95px
                width: 60px
              text: =items[props.exportEnergy].state
          - component: Label
            config:
              style:
                font-size: 12px
                left: 180px
                position: absolute
                top: 185px
                width: 60px
              text: "=Number.parseFloat(items[props.exportPower].state) < 0 ? Math.abs(Number.parseFloat(items[props.exportPower].state)) + ' W' : '0 W'"
          - component: Label
            config:
              style:
                font-size: 12px
                left: 180px
                position: absolute
                top: 200px
                width: 60px
              text: =items[props.importEnergy].state
          - component: Label
            config:
              style:
                font-size: 12px
                left: -10px
                position: absolute
                top: 80px
                width: 60px
              text: "=Number.parseFloat(items[props.loadPower].state) < Number.parseFloat(items[props.solarPower].state) ? items[props.batteryPower].state : '0 W'"
          - component: Label
            config:
              style:
                font-size: 12px
                left: -10px
                position: absolute
                top: 95px
                width: 60px
              text: =items[props.batteryChargeEnergy].state
          - component: Label
            config:
              style:
                font-size: 12px
                left: 40px
                position: absolute
                top: 185px
                width: 60px
              text: "=Number.parseFloat(items[props.loadPower].state) > Number.parseFloat(items[props.solarPower].state) ? items[props.batteryPower].state : '0 W'"
          - component: Label
            config:
              style:
                font-size: 12px
                left: 40px
                position: absolute
                top: 200px
                width: 60px
              text: =items[props.batteryDischargeEnergy].state
          - component: Label
            config:
              style:
                font-size: 12px
                left: 0px
                position: absolute
                top: 250px
                width: 60px
              text: =items[props.batteryLevel].state
          - component: Label
            config:
              style:
                font-size: 12px
                left: 120px
                position: absolute
                top: 310px
                width: 50px
              text: "=Number.parseFloat(items[props.batteryPower].state) <= 0 || Number.parseFloat(items[props.loadPower].state) < Number.parseFloat(items[props.solarPower].state) || Number.parseFloat(items[props.batteryPower].state) <= Number.parseFloat(items[props.loadPower].state) ? '0 W' : (Number.parseFloat(items[props.batteryPower].state) - Number.parseFloat(items[props.loadPower].state)).toFixed(1) + ' W'"

Changelog:

17.07.2023

  • Changed to use the default card title instead of custom f7-block
  • Fixed decimal representation on derived values (limited to 1 decimal digit)
  • Adjusted Battery information:
  • battery is charging, if battery power is greater than zero and load is lower than pv-generation
  • battery is discharging, if battery power is greater than zero and load is greater than pv-generation
  • battery feeds into grid, if battery power is greater than load

Any feedback is welcome.

  • Sönke
2 Likes

Now it’s time for me to change from sitemap to Main UI :heart_eyes:

Hi Sönke,
first of all: Thanks for this binding, it is exactly what I was searching for when getting my Sungrow inverter 2 weeks ago. It is much appreciated.
One thing I recognized is, that the “Battery Power” is always positive, no matter if the battery is chargin g or discharging. Some research showed, that this is actually not a problem of your binding, but rather how the modbus gives it out. There seem to be 2 possible workarounds to get the correct flow direction into the number (both ideas from this thread:

  1. There seem to be some flags given out by the modbus indicating, if the battery is currently charging
  2. According to the thread, the battery current field will contain the correct sign

I will test, if the second version is true (currently I did not map the current to any item, so I do need to collect data first), but it would be great, if the correct sign could be already added by the binding (or would a transformation be the way to go here), so that you do not need to look at multiple items every time you want to know, whats going on with the battery

KR,
Simon

Hi Simon,

thanks for your reply and feedback.
Next week i’ll get my battery too, then i’m able to check this - i’m sure we’ll find a suitable solution.

  • Sönke