Hi all,
I’d like to share how I integrated my Sunny Boy SMA solar inverter into openHAB - in case somebody likes to do similar. In combination with the astro binding (sun rise, sun set) it can look like this:
You can ask your self if you start your dish washer now or maybe rather time it for tomorrow morning. Or even start to automate this. Have fun if you like it.
Basically my solution consists out of three things:
- a python script to communicate with the inverter which delivers some data in a json format. It builds on the code by hdo in his git repository. I only have experience with the Sonnyboy inverter from SMA - therefor I don’t have any solution for other inverter brands. However github is full of examples - maybe you’re lucky for your brand.
- a entry in the thing file to run the python script regularly
- a rule to transform the json result into items. The rest should be straight forward.
sma.py - the python script
#!/usr/bin/python
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 = '0000' # default is '0000'
code_login = 0xfffd040d
code_total_today = 0x54000201
code_spot_ac_power = 0x51000201
src_serial = 2130422522 # change this to your serial number
dst_serial = 4294967295
comm_port = 9522
comm_dst = '192.168.1.12' # 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' % (struct.pack('<I', src_serial).encode('hex'), get_encoded_pw(user_pw))
cmd_logout = '534d4100000402a00000000100220010606508a0ffffffffffff00037800%s000300000000d7840e01fdffffffffff00000000' % (struct.pack('<I', src_serial).encode('hex'))
cmd_query_total_today = '534d4100000402a00000000100260010606509a0ffffffffffff0000780036abfb7e000000000000f180000200540001260022ff260000000000'
cmd_query_spot_ac_power = '534d4100000402a00000000100260010606509e0ffffffffffff0000780036abfb7e00000000000081f0000200510001260022ff260000000000'
sma_data = {}
query_list = []
rea = 0
class MulticastClientUDP(DatagramProtocol):
def datagramReceived(self, datagram, address):
global sma_data, data_available
data = datagram.encode('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 = cmd.decode('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()
You have to make this script executable (chmod +x sma.py). If the script works well you should receive a output like {“spotacpower”: 450, “total”: 9543056, “today”: 21078}. If not debugging can be tricky. The dfault password should be the 0000. Try to use SBFSpot from SMA to get more information from your SMA inverter.
The entry in the things file:
(change the path to your script)
Thing exec:command:sunnyboy [command="/home/me/sma.py", interval=301, timeout=5, autorun=true]
The rule
rule "Solaranlage"
when
Item SunnyBoy_Out received update
then
logInfo("Sonnyboy ", SunnyBoy_Out.state.toString.trim )
var String data = SunnyBoy_Out.state.toString
var String s_Total = transform("JSONPATH", "$.total", data)
var String s_Today = transform("JSONPATH", "$.today", data)
var String s_Spot = transform("JSONPATH", "$.spotacpower", data)
Solar_Total.postUpdate(new BigDecimal(s_Total) / 1000)
Solar_Today.postUpdate( (Float::parseFloat(s_Today) as Number ) )
Solar_Spot.postUpdate( (Float::parseFloat(s_Spot) as Number ) )
Solar_CO2.postUpdate( (Float::parseFloat(s_Today) as Number )*0.00067 )
end
And finally entries in items…:
Number Solar_Spot "Solarstrom jetzt [%.0f W]" <solar> (gDG,gMapDB)
Number Solar_Today "Solarstrom heute [%.0f Wh]" <solar> (gDG,gMapDB)
Number Solar_CO2 "CO2-Einsparung heute [%.1f kg]" <leaf> (gDG,gMapDB)
Number Solar_Total "Solarstrom Total [%.0f kWh]" <solar> (gDG,gMapDB)
…and sitemap:
Frame label="Solar"
{
Text item=Solar_Spot
Text item=Solar_Today
Text item=Solar_Total
Text item=Solar_CO2
}