Modbus binding with Sunny Boy SMA inverter

Ahh okay, didnt know such devices exsisted.

Yeah, It makes me wonder about my own setup, (which works rock steady). I use the same ID for each device in the bridge setup (things file). ID 3.

OK, In the end I’m not disappointed and I have learned a lot from you guys.
Many thanks for your help, I appreciate this.

1 Like

I couldn’t say :smiley: you’ll just need to read the docs carefully.

I think I might have things to say about minor efficiency improvements in Things setup later … when you guys have had a play and got the data you you want, please post a things file and I’ll be happy to review later. No rush, play first.

My setup have been running for months now… How many things files do you want? :laughing:

A sample. Don’t you bung all modbus in one file?

No, I have two things file, one for each inverter. It gave me an better overview :slight_smile:

Bridge modbus:tcp:inverter1 [ host="10.4.28.248", port=502, id=3, connectMaxTries=3] {

// SMA Inverter Device Type
	Bridge poller DevType [ start=30053, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data DevType [ readStart="30053", readValueType="int32" ]
	   	 
  	 }

// SMA Inverter Status
	Bridge poller SMA_Status [ start=30201, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data SMA_Status [ readStart="30201", readValueType="uint32" ]

  	 }

// SMA Inverter Grid_Contactor
	Bridge poller Grid_Contactor [ start=30217, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data Grid_Contactor [ readStart="30217", readValueType="uint32" ]
	    
  	 }

// SMA Inverter Day Yield
	Bridge poller Day_Yield [ start=30517, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data Day_Yield [ readStart="30517", readValueType="uint64", readTransform="JS(divide1000.js)" ]
	    
   	 }

// Total Yield
	Bridge poller Total_Yield [ start=30529, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data Total_Yield [ readStart="30529", readValueType="uint32", readTransform="JS(divide1000.js)" ]
	    
 	 }

// SMA Inverter Power Section West
	Bridge poller power_a [ start=30773, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data power_a [ readStart="30773", readValueType="int32", readTransform="JS(smalimit.js)" ]
	   	 
  	 }

// SMA Inverter Power Total 
	Bridge poller Active_Power [ start=30775, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data active_power [ readStart="30775", readValueType="int32", readTransform="JS(smalimit.js)" ]
	    
  	 }

// SMA Inverter Power Section East
	Bridge poller power_b [ start=30961, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data power_b [ readStart="30961", readValueType="int32", readTransform="JS(smalimit.js)" ]
	   	 
   	 }

// SMA Inverter Operation
	Bridge poller SMA_Status_Operate [ start=40029, length=4, refresh=5000, type="holding" ] {
    
	    	 Thing data SMA_Status_Operate [ readStart="40029", readValueType="uint32" ]
	    
   	 }

}
Bridge modbus:tcp:inverter2 [ host="10.4.28.249", port=502, id=3, connectMaxTries=3] {


// SMA SunnyBoy Storage 2.5
	Bridge poller device_typ [ start=30053, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data device_typ [ readStart="30053", readValueType="int32"]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Condition
	Bridge poller status [ start=30201, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data status [ readStart="30201", readValueType="int32"]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 GridRelay on/off
	Bridge poller GridRelay [ start=30217, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data GridRelay [ readStart="30217", readValueType="int32"]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Total Yield.
	Bridge poller TotalYield [ start=30529, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data TotalYield [ readStart="30529", readValueType="int32", readTransform="JS(divide1000.js)" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Daily Yield
	Bridge poller DayYield [ start=30535, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data DayYield [ readStart="30535", readValueType="int32", readTransform="JS(divide1000.js)" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Power
	Bridge poller Power [ start=30775, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data Power [ readStart="30775", readValueType="int32" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 PowerL1
	Bridge poller PowerL1 [ start=30777, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data PowerL1 [ readStart="30777", readValueType="int32", readTransform="JS(divide1000.js)" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Grid Voltage L1
	Bridge poller GVoltageL1 [ start=30783, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data GVoltageL1 [ readStart="30783", readValueType="int32", readTransform="JS(divide100.js)" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Current battery state of charge
	Bridge poller CurrentBatStatCharge [ start=30845, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data CurrentBatStatCharge [ readStart="30845", readValueType="int32" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Current battery capacity
	Bridge poller CurrentBatcapacity [ start=30847, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data CurrentBatcapacity [ readStart="30847", readValueType="int32" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 PowerDrawn
	Bridge poller PowerDrawn [ start=30865, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data PowerDrawn [ readStart="30865", readValueType="int32" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Power grid feed-in
	Bridge poller PowerGridFeedIn [ start=30867, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data PowerGridFeedIn [ readStart="30867", readValueType="int32" ]
	   	 
  	 }


// SMA SunnyBoy Storage 2.5 Battery oper. status
	Bridge poller BatteryStatus [ start=30955, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data BatteryStatus [ readStart="30955", readValueType="int32" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Status battery application area:
	Bridge poller BatteryAppArea [ start=31057, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data BatteryAppArea [ readStart="31057", readValueType="int32" ]
	   	 
  	 }


// SMA SunnyBoy Storage 2.5
	Bridge poller PowerDrawnGridL1 [ start=31265, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data PowerDrawnGridL1 [ readStart="31265", readValueType="int32" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5
	Bridge poller PowerDrawnGridL2 [ start=31267, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data PowerDrawnGridL2 [ readStart="31267", readValueType="int32" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5
	Bridge poller PowerDrawnGridL3 [ start=31269, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data PowerDrawnGridL3 [ readStart="31269", readValueType="int32" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Present battery charge
	Bridge poller PresBatCharge [ start=31393, length=4, refresh=5000, type="input" ] {
	    
	    	 Thing data PresBatCharge [ readStart="31393", readValueType="int32" ]
	   	 
  	 }

// SMA SunnyBoy Storage 2.5 Present battery discharge
	Bridge poller PresBattDischarge [ start=31395, length=2, refresh=5000, type="input" ] {
	    
	    	 Thing data PresBattDischarge [ readStart="31395", readValueType="int32" ]
	   	 
  	 }

}

And to make things “worse” I have just bought a ethernet to use with my Nilan ventilation, as I have gotten sick and tired of all the USB troubble with Linux/Rpi… So my plan is to use this, insted of the RS485 UBS dongle.
https://www.waveshare.com/rs485-to-eth-for-eu.htm

I would like to add the JavaScript transform function to divide some values by 1000.

Is placing this JS file in the ‘/etc/openhab2/transform folder’ and installing the ‘JavaScript Transformation’ all I have to do ?

(function(i) {
    return i / 1000;
})(input)

Yes, yours is just like mine.
You´ll have to add it to the data as well, (look at my things file above where I use it).
And yes, it is placed inside the /etc/openhab2/transformation/ folder.

Okay, makes sense, and no difference to how it works.

You have many pollers, each fetching only one bit of information. It’s terribly inefficient, and makes me queasy.
Ideally, you would compress those into just a few pollers fetching bigger chunks of information.
But … I am now reminded … These Sunny Boy things are “badly behaved” as Modbus devices and only let you read 4 registers at a time, is that right?

If that is so, you are stuck with many pollers.
We can reduce the impact of those a little bit.

Polling frequency
The pollers have individual refresh settings. They need not all be the same. Review your settings, can you increase times between polls? Example, do you need to fetch daily totals every 5 seconds … or is once a minute enough?

TCP reconnects
By default, the binding creates a TCP connection for each poll (or write), then closes it again. There’s an overhead workload cost to that for everyone involved, and note that many poller Things will invoke many open/closes.

There is a reason for that default behaviour - some slaves allow only a few, perhaps just one, TCP connection at a time. So us grabbing a connection and holding onto it can be “antisocial behaviour” as we lock out other possible users.

This might well be a problem in this kind of system, where Modbus could be used internally to chatter between modules or remote controls/displays. You’ll have to try it and see.

Okay, so to keep a connection open for use by many polls we use the reconnectAfterMillis= parameter. You might try setting to 60000, for ten minutes. That keeps one TCP connection for the given time, then closes it and makes a new one.
This parameter is set on the TCP Bridge thing, so it is shared by all the owned pollers. Makes sense, they all share the same connection.

Each TCP Bridge has its own setting, so you can treat different devices in different ways…

1 Like

I dont know if they only read 4 registers at a time. But I´m willing to give it a try, cause all these pollers are really making me sick as well…In PaperUI I have to scroll down so much :slight_smile:
I just need to know how to compress them cause I didnt know this would be an option.

You´re right… I dont need to poll every 5 sec for daily yield. And there may be others registers I dont need to poll as well. I just took 5 sec as a kinda default setting :slight_smile:

Will the polling still be at 5 sec then?
I could give it a try.

Me neither. I think @Nanna_Agesen may know more on this?

Okay, example poller.
You have now -

    Bridge poller status [ start=30201, length=4, refresh=5000, type="input" ] {    
	    	 Thing data status [ readStart="30201", readValueType="int32"]
  	 }

	Bridge poller GridRelay [ start=30217, length=4, refresh=5000, type="input" ]{	    
	    	 Thing data GridRelay [ readStart="30217", readValueType="int32"]	   	 
  	 }

In a well-behaved environment, you could use instead -

    Bridge poller status [ start=30201, length=20, refresh=5000, type="input" ] {    
	    	 Thing data status [ readStart="30201", readValueType="int32"]
	    	 Thing data GridRelay [ readStart="30217", readValueType="int32"]	   	 
  	 }

Note -this changes the channel links needed in your Items
One poll fetches a big block of registers, including both wanted data items. You ignore the data you don’t want.
This relies on the slave working with arbitrary length requests, which is in doubt here.
It also relies on the slave not being offended by requests for any registers inbetween that it may not have defined by the maker. Mostly they don’t mind, and supply zeroes for undefined registers.

A slave that does not like a request should respond with a reject exception code like 02.
Badly behaved slaves may just not respond at all, giving you an irrecoverable timeout instead.

Yes, the whole idea of holding a TCP connection open for a slave is that many polls or writes can use the same connection without further work.

How do you calculate the length=20? (thats a part I have failed to understand completely).

I think my Nilan (RTU modbus serial) is acting strange if I dont get the lenght correct. But I have no tried with the SMA inverters. I used Nana´s example long time ago. And then I just worked out from the aspect - If it works, dont change it! :smiley:

Okay, will give it a try one of these upcoming days.

It’s a straightforward count of 16-bit registers. Remember, Modbus protocol knows nothing about 32-bit or 64-bit words etc., that is just an interpretation we place upon the 16-bit registers we actually get.
A poller is a protocol thing, the interpretation is done in the data thing.

So, you had start at 30201 and count 4. And start at 30217 and count 4. (that’s 30217 to 30220 inclusive).
To include the same registers in one hit, 30201 to 30220, you would specify start 30201 length 20

There is a limit, Modbus has a maximum packet size and the most you can fit into a packet is just over 120 registers.

Ahh, it was just too simple for me to comprehend :rofl:

Nice to know as I could end up in that field…

Modbus is like DNA - the complexity is created from the simplest building blocks.

1 Like

Do you refer to issue [modbus] binding malfunction with SMA solar inverter · Issue #5347 · openhab/openhab-addons · GitHub

1 Like

Yes, that’ll be it, looking back at the 2018 issue too.
File SMA under “devices likely to misbehave”. Might be cured by SMA in any particular model or recent version, of course, so worth a try.

Hmm Its a Tripower 6000TL I have. Older than the issue :frowning:

Seem like it runs fine when I use the “compress” trick you showed above. This is my “test” things file for now:

Bridge modbus:tcp:inverter1 [ host="10.4.28.248", port=502, id=3, connectMaxTries=3] {

// SMA Inverter Status
	Bridge poller SMA_Status           [ start=30201, length=20, refresh=5000, type="input" ] {
	    
               Thing data SMA_Status       [ readStart="30201", readValueType="uint32" ]
               Thing data Grid_Contactor   [ readStart="30217", readValueType="uint32" ]
  	 }

// SMA Inverter Yield
	Bridge poller Yield                [ start=30517, length=16, refresh=5000, type="input" ] {
	    
               Thing data Day_Yield        [ readStart="30517", readValueType="uint64", readTransform="JS(divide1000.js)" ]
               Thing data Total_Yield      [ readStart="30529", readValueType="uint32", readTransform="JS(divide1000.js)" ]
   
   	 }
}

Okay, that’s good news.

The SMA “quirk” may come down to it wanting registers read in multiples of four or something, we have no way to guess. There may well be inconsistency amongst models.

I’d still advise using unique Thing UID (names)
It does work where one is a Bridge and one is an ordinary Thing, but it’s a risk to your own sanity as well.