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

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