Example on how to access data of a Sunny Boy SMA solar inverter

thanks

maybe you will have some luck, but all rpi3 I’ve tried had frozing bluetooth after like 6-10 requests. rpi3+ does not have those issues at all.
you can definitely try to run sbfspot with built-in bt and if it will work for whole day, you’ll have quite big chance it will run reliably since then, if not … well disable onboard BT and go dongle :wink:

are you running somewhere some sql database for something? as sbfspot is way easier to use that way.
if not, it’s not end of the world.

My Rpi´s are all 3B models, one is actually 3B+ (the one running my main openhab server).

Hmailserver (my mail server) uses Microsoft SQL server compact 3.5 ENU. But I have no idea how to add databases to it. It was all done by Hmailserver installer.

I can organise you a Bluetooth dongle for the C2 if you want one?

https://www.odroid.co.uk/odroid-bluetooth-module-2

Thanks Start. But I think I´ll try go with the LAN connection which is already there, (not sure if it will conflict with the inverter having connection to the SMA Smart Energy Meter at the same time). If it does, a BT dongle might come in hand.

Hi all,
Sounds interesting, but I lost how to being able to gather information from SMA SunnyBoy with Speedwire WebConnect? Do I have to run the Python Script, or simply Mqtt, which is running on my OpenHab to connect to Worx Landroid anyway. I believr/understood I have to do some configuration with Mqtt? But fail to get it working/configured properly.

My OpenHab is running on a Windows Machine :slight_smile:
Regards,
Joerg

It’s certainly interestesting, but is there a simple way to obtain data from the standard modbus binding ?

In my case, there is a connection and I receive data from address 30775 , but I have an issue with the format.

Kind regards,
Pieter

Yep.

Typical problems include register numbering versus addressing. Historically, modbus register number 1 is found at address 0. It is often unclear which variation any given document talks about.
openHAB modbus binding always talks addresses; so sometimes if register NN looks funny it is worth checking register NN-1.

Then modbus has “table” addressing - traditionally, different types of modbus register are distinguished by the numbering i.e. “discrete” are 10001 to 19999, “input” are 30001 to 39999.
The table numbers are not used in the protocol layer - “input 30775” would be configured in the binding as “Input type, 774”. Probably?!

Table number use limits the address range, so “extended addressing” is also used. That just means that 30775 means 30775 (or maybe 30774) and that’s what you’d put in the binding config.

32-bit (or 64-bit etc.) values can highlight another issue. Modbus protocol only knows 16-bit registers, to make a 32-bit value we must use two registers. Usually NN and NN+1 - but which is the “high” part and which the “low”? There is no rule and not even a convention, is it A+B or B+A?
The binding provides two versions of datatypes i.e. int32 and int32swap. It’s up to you to find out which your device uses.

For 32-bit words, they can be signed or unsigned integers. Furthermore, they can be float numbers - at least there is a convention for that using IEEE encoding. But the word-swap dilemma still applies.

So its a bit of a nightmare. “Address 30775” might be 30774 or 775 or 774, the format might be int32 or or uint32 or float32 or int32swap or uint32swap or float32swap.
The manufacturer docs may give more help, sometimes you have to trial and error.

This has so little to do with reading data from a Sunny Boy, you’d be much better off starting a new thread about this control method.

I recognize the formatting/parsing challenges, but have been able to setup a connection to my SMA Sunny Boy over the WiFi via the standard Modbus binding.

I do still have some parsing challenges, where my current config has a Modbus poller for each data field eg. I have 1 poller setup on address 30775, with length 2, type ‘input register’ to pull current power in kWh. Setup of the data field uses the same address 30775, with type 32 bit unsigned integer.

This works, but I get parsing errors if I extend the poller to a length >2 and try to read multiple data fields. Not ideal, but workable.

The bigger issue I have is that the modbus data turns into garbage at night, when the panels are not generating any energy. When I connect to the SMA webpage I do see normal values. Any idea what is causing this and how to solve?

There’s a FAQ here about that
https://www.sma.de/en/products/monitoring-control/modbus-protocol-interface.html

We might be able to help you deal with it gracefully, if you spell out what the problem it causes you is.

Show us working setup and docs for your model (they seem to vary)

hi rossko57, thanks for your quick response. I did not see this in the FAQ and CPU shutdown in case of low voltage explains the behavior as indeed I see NaN values on a few registers.

I managed to use a quick & dirty javascript transformation to “fix” this:

(function(i) {
    if(i == -2147483648) return 0;
    return i;
})(input)

I did some further tests on my earlier issue to read multiple registers at the same time and this does seem to work correctly now. I think that I got confused as I was trying to read multiple Daily Yield registers before (30535-30539) but these always return NaN even though according to the Modbus docs for my SB3.6-1AV-40 they should work (Total Yield at 30529-30533 works fine).

thanks for the help!

1 Like

Hi all,

There is a python library for home assistant that can read values from a webconnect supported inverter.

It has a basic python script example too:

DEBUG:pysma:grid_power  = 1582 W
DEBUG:pysma:voltage_l1  = 243.19 V
DEBUG:pysma:Sensor voltage_l2: No successful value decoded yet: {'1': [{'val': None}]}
DEBUG:pysma:Sensor voltage_l3: No successful value decoded yet: {'1': [{'val': None}]}
DEBUG:pysma:pv_power    = 1582 W
DEBUG:pysma:pv_gen_meter        = 332.824 kWh
DEBUG:pysma:total_yield = 332.824 kWh
WARNING:pysma:Sensor 6400_00262200: Not found in {'6400_00462500': {'1': [{'val': 0}]}, '6100_00465700': {'1': [{'val': 4998}]}, '6400_00260100': {'1': [{'val': 332824}]}, '6100_00464900': {'1': [{'val': None}]}, '6100_40463700': {'1': [{'val': None}]}, '6400_00462400': {'1': [{'val': 0}]}, '6100_00464800': {'1': [{'val': 24319}]}, '6100_40463600': {'1': [{'val': None}]}, '6100_40263F00': {'1': [{'val': 1582}]}, '6400_0046C300': {'1': [{'val': 332824}]}, '6100_00464A00': {'1': [{'val': None}]}, '6100_0046C200': {'1': [{'val': 1582}]}, '6180_08214800': {'1': [{'val': [{'tag': 307}]}]}}
DEBUG:pysma:Sensor grid_power_supplied: No successful value decoded yet: {'1': [{'val': None}]}
DEBUG:pysma:Sensor grid_power_absorbed: No successful value decoded yet: {'1': [{'val': None}]}
WARNING:pysma:Sensor 6100_00543100: Not found in {'6400_00462500': {'1': [{'val': 0}]}, '6100_00465700': {'1': [{'val': 4998}]}, '6400_00260100': {'1': [{'val': 332824}]}, '6100_00464900': {'1': [{'val': None}]}, '6100_40463700': {'1': [{'val': None}]}, '6400_00462400': {'1': [{'val': 0}]}, '6100_00464800': {'1': [{'val': 24319}]}, '6100_40463600': {'1': [{'val': None}]}, '6100_40263F00': {'1': [{'val': 1582}]}, '6400_0046C300': {'1': [{'val': 332824}]}, '6100_00464A00': {'1': [{'val': None}]}, '6100_0046C200': {'1': [{'val': 1582}]}, '6180_08214800': {'1': [{'val': [{'tag': 307}]}]}}
WARNING:pysma:Sensor 6400_00543A00: Not found in {'6400_00462500': {'1': [{'val': 0}]}, '6100_00465700': {'1': [{'val': 4998}]}, '6400_00260100': {'1': [{'val': 332824}]}, '6100_00464900': {'1': [{'val': None}]}, '6100_40463700': {'1': [{'val': None}]}, '6400_00462400': {'1': [{'val': 0}]}, '6100_00464800': {'1': [{'val': 24319}]}, '6100_40463600': {'1': [{'val': None}]}, '6100_40263F00': {'1': [{'val': 1582}]}, '6400_0046C300': {'1': [{'val': 332824}]}, '6100_00464A00': {'1': [{'val': None}]}, '6100_0046C200': {'1': [{'val': 1582}]}, '6180_08214800': {'1': [{'val': [{'tag': 307}]}]}}
               grid_power           1582 W
                frequency          49.98 Hz
               voltage_l1         243.19 V
               voltage_l2
               voltage_l3
                 pv_power           1582 W
             pv_gen_meter        332.824 kWh
              total_yield        332.824 kWh
              daily_yield
      grid_power_supplied
      grid_power_absorbed
         grid_total_yield            0.0 kWh
      grid_total_absorbed            0.0 kWh
      current_consumption
        total_consumption
                   status            307

I have made a fork of this project and am testing mqtt support. This way you can send values from the pysma library to any mqtt broker and pick them up with Openhab. Will post the code soon here when done.

Please try my code:

Example rules to calculate daytotals (not provided by SMA webinterface).

You will need to create 2 additional items not bound to a binding.

rule1: Write the total yield provided by webconnect to the item

rule “solar-rule1”
when
Time cron “00 59 23 1/1 * ? *”
then
Yield.sendCommand(Solar_Total_Yield_kWh.state as DecimalType)
end

rule2: subtract the Yield of the previous day from the new total yield provided by webconnect as soon as it changes and write to new item.

rule “solar-rule2”
when
Item Solar_Total_Yield_kWh changed
then
Yield_DayTotal.postUpdate((Solar_Total_Yield_kWh.state as DecimalType) - (Yield.state as DecimalType))
end

Optional: you can write the total earnings to influx each day

Strategies {
everyDay : “56 59 23 1/1 * ? *”
}
Items {
Yield_DayTotal : strategy = everyDay, restoreOnStartup
}

Thanks for sharing. Can I request to inform from where you get the ID for a total today or spot_ac_power?
How can I get the ID for other sensors and parameters?

I got the same error as Superwutz since I migrated towards OH3.

File "sma.py", line 32, in <module>
    cmd_login =                '534d4100000402a000000001003a001060650ea0ffffffffffff00017800%s00010000000004800c04fdff07000000840300004c20cb5100000000%s00000000' % (struct.pack('<I', src_serial).encode('hex'), get_encoded_pw(user_pw))
AttributeError: 'bytes' object has no attribute 'encode'

The “encode” function doesn’t exist in python 3.
Did anyone rewrite this script for python 3? I am not familiar enough with python to do it myself.

Well I’m also on openhab 3 and got the modbus working but the valeus are off so I’m still missing something there.
Will post it as soon as I have it working true the modbus.

Did you ever got the script working on OH3?

Thanks!

I did manage to get it working. It isn’t the most beautiful programming (I don’t master the language) but the result is there so good enough for me.

#!/usr/bin/python
import codecs
import struct
import sys
import time
import json
from struct import *
from twisted.web import server, resource
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor
from twisted.application.internet import MulticastServer

user_pw = 'password'  # default is '0000'
code_login = 0xfffd040d
code_total_today = 0x54000201
code_spot_ac_power = 0x51000201
src_serial = 123456789  # change this to your serial number
dst_serial = 4294967295
comm_port = 9522
comm_dst = 'xxx.xxx.xxx.xxx'  # change this to your IP


def get_encoded_pw(password):
    # user=0x88, install=0xBB
    encpw = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88]
    for index in range(min(len(encpw), len(password))):
        encpw[index] = encpw[index] + ord(password[index])

    ret = ""
    for ch in encpw:
        ret = ret + hex(ch).replace('0x', '')
    return ret


cmd_login = '534d4100000402a000000001003a001060650ea0ffffffffffff00017800%s00010000000004800c04fdff07000000840300004c20cb5100000000%s00000000' % (codecs.decode(codecs.encode((struct.pack('<I', src_serial)),'hex')), get_encoded_pw(user_pw))
cmd_logout = '534d4100000402a00000000100220010606508a0ffffffffffff00037800%s000300000000d7840e01fdffffffffff00000000' % (codecs.decode(codecs.encode((struct.pack('<I', src_serial)),'hex')))
cmd_query_total_today = '534d4100000402a00000000100260010606509a0ffffffffffff00007800%s000000000000f180000200540001260022ff260000000000' % (codecs.decode(codecs.encode((struct.pack('<I', src_serial)),'hex')))
cmd_query_spot_ac_power = '534d4100000402a00000000100260010606509e0ffffffffffff00007800%s00000000000081f0000200510001260022ff260000000000' % (codecs.decode(codecs.encode((struct.pack('<I', src_serial)),'hex')))

sma_data = {}
query_list = []
rea = 0


class MulticastClientUDP(DatagramProtocol):

    def datagramReceived(self, datagram, address):
        global sma_data, data_available
        data = codecs.encode(datagram,'hex')
        code = get_code(datagram)

        if code == code_login:
            send_command(cmd_query_total_today)

        if len(datagram) <= 58:
            print("data access failed")
            reactor.stop()
            exit
        if code == code_total_today:
            total = get_long_value_at(datagram, 62)
            today = get_long_value_at(datagram, 78)
            sma_data['total'] = total
            sma_data['today'] = today
            send_command(cmd_query_spot_ac_power)
        if code == code_spot_ac_power:
            value = get_long_value_at(datagram, 62)
            if value == 0x80000000:
                value = 0
            sma_data['spotacpower'] = value
            output_data = json.dumps(sma_data)
            print(output_data)
            reactor.stop()


def send_command(cmd):
    data = codecs.decode(cmd,'hex')
    rea.write(data, (comm_dst, comm_port))


def get_code(data):
    c = unpack('I', data[42:46])
    return c[0]


def get_long_value_at(data, index):
    v = unpack('I', data[index:index + 4])
    return v[0]


def callfunc(x):
    reactor.stop()


rea = reactor.listenUDP(0, MulticastClientUDP())
_DelayedCallObj = reactor.callLater(5, callfunc, "callfunc called after 4 sec")
send_command(cmd_login)
reactor.run()
2 Likes

Hi
Just though I would cross link this thread to my new post, in case anybody finds this thread first and what I have done is of any use to them.

I missed this thread.

Here’s how I approiached this issue by means of SBFspot and the openHAB REST API:

In essence, I rely on SBFspot to get the inverter data through bluetooth and store it in a SQLite database. Then I query this SQLite database and post relevant data to openHAB through the REST API.

1 Like