Reading data from Huawei inverter SUN 2000 (3KTL-10KTL) via modbus TCP and RTU

In my case: I have two solar plants. One is without battery but > 10 kWP - so it has AC to share :slight_smile:

Oh, there’s quite some scenarios where it does make sense, for example if you have a dynamic tariff and charge during the cheapest hours of the day.
Or when you have the 70% feed-in limitation and want to free some battery capacity so the 70+ cap fits in. In your setup this electric power is wasted (or never produced, actually).

… charging from grid might make sense with a smaller plant in winter times while using a dynamic tariff. So at night when the price falls down one can charge the battery from grid and use the electricity at day time.

To force a time based charge one has to do:

  1. enable Luna AC charging (47087 = 1)
  2. set Luna Forcible Charge Power (47247 = 4000[W])
  3. set Luna Power of Charge from Grid (47242 = 4000[W])
  4. set Luna Forced Charging Period (47083 = 60[min])
  5. enable Luna Force Charge (47100 = 1)

Also make sure register 47244 >= 47242.

After the Luna Forced Charging Period time expires, charging stops automatically. Or one can stop by setting Luna Force Charge (47100) = 0.

No guarantee, but this is how it works for me.

47244 or 47247 force charge power as above?

Do you mind to show your config?

Hi Markus,

register 47244 is the maximum power the inverter can produce and depends on the model. My 8KTL-M1 has a maximum setting of 8800W. Certainly the power of charge from grid (47242) must be less or equal than that.

This is my rule:

rule "Luna Netzladen On"
when
	Time cron "0 59 * ? * * *"
then
    val SOC =  ModbusDataLunaSOC.state as Number
    val BPreis = PreisH1.state as Number  // price 1h ahead of the dynamic tariff
    val BLimit = PreisL.state as Number  // set price limit for forced charge
    if(BPreis <= BLimit && LunaForceCharge.state == ON) {   // LunaForceCharge is just virtual switch I'm using to control the forced charging
		logInfo("pv.rules","BPreis: " + BPreis + "; BLimit: " + BLimit + "; SOC: " + SOC + "; LunaForceCharge ON")
		if(ModbusDataACLaden_SW.state != ON) {ModbusDataACLaden_SW.sendCommand(ON)}
		if(ModbusDataLunaMaxFCP.state as Number != 4000) {ModbusDataLunaMaxFCP.sendCommand(4000)}
        if(ModbusDataLunaPCG.state as Number != 4000) {ModbusDataLunaPCG.sendCommand(4000)}
		ModbusDataLunaForceTime.sendCommand(61)
		ModbusDataLunaForce.sendCommand(1)
	}else{
		logInfo("pv.rules","BPreis: " + BPreis + "; BLimit: " + BLimit + "; SOC: " + SOC + "; LunaForceCharge OFF")
		if(ModbusDataLunaForce.state != 0) {ModbusDataLunaForce.sendCommand(0)}
	}
end

BTW, when the Luna is at sleep it may take ~30min to wake her up for forced charging. So I have another rule checking that and wake her up to be ready in time.

rule "Luna Netzladen ACL"
when
    Time cron "0 29 * ? * * *"
then
    val BPreis = PreisH1.state as Number
    val BLimit = PreisL.state as Number
	val SOC = ModbusDataLunaSOC.state as Number
    if(BPreis <= BLimit && (ModbusDataLunaStatus.state == 'Sleep' || SOC < 10)) {
		if(ModbusDataACLaden_SW.state != ON) {ModbusDataACLaden_SW.sendCommand(ON)}
	}
end

Hope that helps.

Thanks. I should have asked for your config though rather than for rules, I was more aiming to understand the generic logic i.e. order and dependency of commands. Sorry for that.

Do you have to set ModbusDataACLaden_SW for every charge run ?

Does ModbusDataLunaMaxFCP get reset ? (if not you would not need to set it every time)

Reading Sun2000 Modbus Register – Debacher-Wiki there’s also register 47088, maximum SoC when charging from the grid, default 50%.
Did that ever hit you ?

And finally, what about discharging, does that also work?
What would I need to change, just use 47249 instead of 47247 ? What if both are set ??

Hi Markus,

well, as you can see I’m checking the current status of a few registers in my rule(s). What I have written earlier is the must-have register setting to force the charge. Once set I usually only need to set the ModbusDataLunaForceTime and the ModbusDataLunaForce command to start the force charge. I never touched register 47088. Also for the sequence you can see in my rule I send the commands one after another in the rule where they likely get combined into one Modbus operation by the binding?!?

To force discharge one has to set register 47249 instead of 47247 and set ModbusDataLunaForce (47100) = 2. The other registers stay the same. I don’t think there is any conflict between charge and discharge settings - unless you want to use different power settings for charge vs. discharge.

Since I’ve setup all the relevant Modbus Items I could monitor the register settings changes Huawei did while going through the Luna calibration where they force charge.

I changed the " maximum SoC when charging from the grid" to 100%.
And on the Luna Level I am switiching between SOC Target 100% when charging, and SOC Target 0% when discharging. I do not use the charge time parameter. I charge during days until I reach 100% and change the charging power according to the availbale power from the roof

But my setup is kind of special anyway: The huawei inverter and luna battery pack are an extension to my solaredge inverter without battery. And to make even more complicated I do not have a smart meter connection to the huawei inverter…

by the way… Markus or Thomas are you guys interested in building a binding for huawei solar devices based on modbus. The setup of different data points with basic modbus is pretty anoying, so I thouhgt it would be nice to have a binding. But since that would be my first binding and my java know how is very basic, some help would be nice :wink:

Hi guys,

did anyone manage to get data from the optimizers via modbus like daily yield, temperature etc?

Thanks.

Can somebody please post a full config of Huawei Sun 2000 + Luna 2000? (Things and items?)

I added Luna battery to my setup and realizing now that the values are not correct anymore. Huawei_Active_Power is obviously a sum of solar production and battery discharging?

How to get out the correct values?

Thank you!

I successfully can read registers, but I failed trying to write them. Can anyone help me?

I have a 15.8 kW east-west PV system with a 10 kW inverter (Huawei Sun KTL 10) and 10 kWh storage. At noon, the inverter is currently at its limit and cannot convert everything. I would therefore like to use the storage system so that it is charged at noon.

I use OpenHab with a Modbus TCP connection and have tried to write register 47075 accordingly (morning to 0, noon to 2000), but without success. The battery is still charged directly in the morning. Reading that register and various others works perfectly.

I have a rule that just sets the value to zero:

Modbus_Data_Maximum_Charging_Power_Value_as_Number.postUpdate(0)

Item:

  "Modbus_Data_Maximum_Charging_Power_Value_as_Number": {
    "class": "org.openhab.core.items.ManagedItemProvider$PersistedItem",
    "value": {
      "groupNames": [
        "Omvormer"
      ],
      "itemType": "Number",
      "tags": [
        "Point"
      ],
      "label": "Maximum Charging Power 2",
      "category": ""
    }
  },

Things:

  "modbus:data:9938e81931:97e8fc857d": {
    "class": "org.openhab.core.thing.internal.ThingStorageEntity",
    "value": {
      "isBridge": false,
      "channels": [
        {
...
      "label": "Modbus Data Maximum Charging Power",
      "bridgeUID": "modbus:poller:71978dd591:9938e81931",
      "configuration": {
        "readStart": "47075",
        "readTransform": "default",
        "readValueType": "uint32",
        "updateUnchangedValuesEveryMillis": 1000,
        "writeMaxTries": 3,
        "writeMultipleEvenWithSingleRegisterOrCoil": false,
        "writeStart": "47075",
        "writeTransform": "default",
        "writeType": "holding",
        "writeValueType": "int32"
      },
      "properties": {},
      "UID": "modbus:data:9938e81931:97e8fc857d",
      "thingTypeUID": "modbus:data"
    }

...

  "modbus:poller:71978dd591:9938e81931": {
    "class": "org.openhab.core.thing.internal.ThingStorageEntity",
    "value": {
      "isBridge": true,
      "channels": [],
      "label": "Regular Poll 47075 (Storage RW)",
      "bridgeUID": "modbus:tcp:71978dd591",
      "configuration": {
        "cacheMillis": 50,
        "length": 2,
        "maxTries": 3,
        "refresh": 10000,
        "start": 47075,
        "type": "holding"
      },
      "properties": {},
      "UID": "modbus:poller:71978dd591:9938e81931",
      "thingTypeUID": "modbus:poller"
    }
  },

(I did configure this via GUI, I hope I am copying the right stuff here)

When I execute the rule, the log shows that the value is set to the correct value, but after a couple of minutes it just resets to 5000 (the default).

Log:

When executing rule:

2024-05-19 00:44:00.237 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Modbus_Data_Maximum_Charging_Power_Value_as_Number' changed from 5000 to 0

After some minutes, without doing anything:

2024-05-19 00:45:26.170 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Modbus_Data_Maximum_Charging_Power_Value_as_Number' changed from 0 to 5000

It feels like the value is not actually written back via modbus TCP and at some point in time it is updated again. Any ideas what I am doing wrong?

Thanks,
Jan

It seems that “postUpdate” is wrong, it must be “sendCommand”.

From time to time I am checking the available updates of my 8KTL-M1 inverter and found recently a remarkable update of the SDongle V200R022C10SPC114.
The release notes showing as new feature :

Three-party frequency modulation supported by SDongle
The SDongle supports baud rate negotiation.

and more imprtant as resoved issues:

Fixed persistent MODBUS-TCP disconnection issue.

I did not extensively test but could adjust the “time between transactions” from 5 to 1sec without any problem, still a very stable connection.
So I would encourage everyone who has troubles with reconnections to upgrade the firmware of the sdongle.

@chris4789
I have also updated the dongle and now all pollers with refresh of 1000ms and cacheMillis of 50ms as well as the ModBus Connect with a timeBetweenTransactionsMillis of 250ms run stable over a cable connection.

image

Hi Alex,
Thanks for confirming!

May main concern was my NRGkick energy charger for my BEV which connects also by Modbus to control loading from PV excess.

Today I was brave, tried both connections together, NRGkick and OH what never worked before the update.
The OH connection died a few times for some minutes but the charger had just a short interruption.
So for me a very valuable update, now it is possible to let both connections run at the same time.

I use Modbus proxy for that exact reason. Works flawlessly.

… but even with the firmware update, OH connection is still dying, reproduceably?

Yes unfortunately, I am still playing with the modbus settings but I am afraid that a second connection does not work flawlessly.
I think I will follow Mikaels proxy suggestion, although I am not happy to have another link in the chain.

SD dongle didn’t not work with more than one tcp connection. I’ve found earlier that it might send answer for request from first connection to the second. This limitation been there in 2022, and probably is still there. It is definitely a firmware bug, and if there is physical limit it should be enforced by refusing second connection to succeed.

Hi, thanks for the tutorial.
Would like to ask if I have both SUN2000 and LUNA2000, do I need 2 ModBus connections to read both from the inverter and battery, or is it enough just connect to SUN2000?
As I understand SUN2000 comes with the WiFi dongle however I plan to install it outdoor, is there any possibility to connect it via Ethernet for ModBus?
Many thanks!