Ankündigung

Einklappen
Keine Ankündigung bisher.

Beitrag: Plugin zum Lesen von SMA-Wechselrichtern (Sunnyboy 5000TL-21 getestet)

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

    Beitrag: Plugin zum Lesen von SMA-Wechselrichtern (Sunnyboy 5000TL-21 getestet)

    Hi!

    Anbei ein Plgin für smarthome.py, welches über ein am PC angeschlossenes Bluetooth-Dongle einen SMA-Wechselrichter auslesen kann. Unterstützt werden erstmal nur
    - Tagesertrag
    - Gesamtertrag
    - momentane Einspeiseleistung
    - weitere (Neben-)Sachen wie Seriennummer, BT-Adresse, Signalstärke der Bluetooth-Verbindung ...

    plugin.conf
    Code:
    [sma]
        class_name = SMA
        class_path = plugins.sma
        bt_addr = 00:80:25:xx:xx:xx
        password = 0000
        update_cycle = 60
    bt_addr => Bluetooth-Adresse des Wechselrichters (per hcitool scan)
    password => Wechselrichter-Passwort - Standard ist 0000
    update_cycle => Wie häufig werden (alle!) Werte neu gelesen (60s ist ganz ok)

    Beispielitems:
    Code:
    [Wechselrichter]
      [[Einspeiseleistung]]
        type = num
        visu = yes
        history = true
        sma = "AC_POWER"
        knx_dpt = 12
        knx_send = 5/4/50
        knx_reply = 5/4/50
      [[Tagesertrag]]
        type = num
        visu = yes
        history = true
        sma = "DAY_YIELD"
        knx_dpt = 12
        knx_send = 5/4/51
        knx_reply = 5/4/51
      [[Gesamtertrag]]
        type = num
        visu = yes
        history = true
        sma = "TOTAL_YIELD"
        knx_dpt = 12
        knx_send = 5/4/52
        knx_reply = 5/4/52
      [[Seriennummer]]
        type = num
        visu = yes
        sma = "INV_SERIAL"
        knx_dpt = 12
        knx_send = 5/4/53
        knx_reply = 5/4/53
      [[MAC_Adresse]]
        type = str
        visu = yes
        sma = "INV_ADDRESS"
        knx_dpt = 16000
        knx_send = 5/4/54
        knx_reply = 5/4/54
      [[Letzte_Aktualisierung]]
        type = str
        visu = yes
        sma = "LAST_UPDATE"
        knx_dpt = 16000
        knx_send = 5/4/55
        knx_reply = 5/4/55
    __init__.py (im Ordner /plugins/sma/)
    Code:
    #!/usr/bin/env python
    # vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
    #########################################################################
    # Copyright 2013 Robert Budde                        robert@projekt131.de
    #########################################################################
    #  This software is based on Stuart Pittaway's "NANODE SMA PV MONITOR"
    #  https://github.com/stuartpittaway/nanodesmapvmonitor
    #
    #  SMA-Plugin for SmartHome.py.   http://smarthome.sourceforge.net/
    #
    #  This plugin is free software: you can redistribute it and/or modify
    #  it under the terms of the GNU General Public License as published by
    #  the Free Software Foundation, either version 3 of the License, or
    #  (at your option) any later version.
    #
    #  This plugin is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this plugin. If not, see <http://www.gnu.org/licenses/>.
    #########################################################################
    
    from bluetooth import *
    import struct
    import logging
    import time
    from datetime import datetime
    from dateutil import tz
    
    BCAST_ADDR = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF]
    ZERO_ADDR = [0x00,0x00,0x00,0x00,0x00,0x00]
    
    SMANET2_HDR = [0x7E,0xFF,0x03,0x60,0x65]
    
    FCSTAB = [ 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, \
               0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, \
               0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, \
               0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, \
               0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, \
               0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, \
               0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, \
               0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, \
               0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, \
               0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, \
               0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, \
               0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, \
               0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, \
               0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, \
               0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, \
               0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, \
               0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, \
               0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, \
               0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, \
               0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, \
               0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, \
               0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, \
               0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, \
               0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, \
               0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, \
               0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, \
               0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, \
               0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, \
               0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, \
               0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, \
               0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, \
               0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 ]
    
    TOTAL_YIELD = (0x2601, 0xA009, 0x54)
    DAY_YIELD   = (0x2622, 0xA009, 0x54)
    AC_POWER    = (0x263F, 0xA109, 0x51)
    
    logger = logging.getLogger('')
    
    class SMA():
    
        def __init__(self, smarthome, bt_addr, password="0000", update_cycle="60"):
            self._sh = smarthome
            self._val = {}
            self._inv_bt_addr = bt_addr
            self._inv_password = password
            self._inv_last_read_datetime = datetime.fromtimestamp(0, tz.tzlocal())
            self._inv_last_read = self._inv_last_read_datetime.strftime("%d.%m.%Y %H:%M:%S")
            self._own_bt_addr_le = BCAST_ADDR
            self._btsocket = BluetoothSocket( RFCOMM )
            if not (is_valid_address(self._inv_bt_addr)):
                logger.warning("sma: inverter bluetooth address is invalid: %s" % self._inv_bt_addr) 
                return
            self._inv_bt_name = lookup_name(self._inv_bt_addr, timeout=5)
            if (self._inv_bt_name == None):
                logger.warning("sma: inverter bluetooth name could not be looked up")
                self._inv_bt_name = "unknown" 
                    
            self._btsocket.connect((self._inv_bt_addr, 1))
    
            logger.debug("sma: via bluetooth connected to %s (%s)" % (self._inv_bt_name, self._inv_bt_addr)) 
            self._inv_connect()  
            self._inv_login()
            self._sh.scheduler.add('sma.update', self._update_values, prio=5, cycle=int(update_cycle))
    
        def _update_values(self):
            #logger.warning("sma: signal strength = %d%%" % self._inv_get_bt_signal_strength())
    
            value = self._inv_get_3byte_value(AC_POWER)
            if (value >= 0):
                logger.debug("sma: current AC power = %dW" % value)
                if 'AC_POWER' in self._val:
                    for item in self._val['AC_POWER']['items']:
                        item(value, 'SMA', self._inv_bt_addr)
            else:
                logger.warning("sma: could not read current AC power!")        
    
            value = self._inv_get_3byte_value(DAY_YIELD)
            if (value >= 0):
                logger.debug("sma: day yield = %dWh" % value)
                if 'DAY_YIELD' in self._val:
                    for item in self._val['DAY_YIELD']['items']:
                        item(value, 'SMA', self._inv_bt_addr)
            else:
                logger.warning("sma: could not read day yield!")        
    
            value = self._inv_get_3byte_value(TOTAL_YIELD)
            if (value >= 0):
                logger.debug("sma: total yield = %dWh" % value)
                if 'TOTAL_YIELD' in self._val:
                    for item in self._val['TOTAL_YIELD']['items']:
                        item(value, 'SMA', self._inv_bt_addr)
            else:
                logger.warning("sma: could not read total yield!")        
    
            logger.debug("sma: last read = %s" % self._inv_last_read)
            if 'LAST_UPDATE' in self._val:
                for item in self._val['LAST_UPDATE']['items']:
                    item(self._inv_last_read, 'SMA', self._inv_bt_addr)
    
        def run(self):
            self.alive = True
    
        def stop(self):
            self.alive = False
            try:
                self._btsocket.close()
                self._btsocket = False
            except:
                pass
    
        def parse_item(self, item):
            if 'sma' in item.conf:
                sma_value = item.conf['sma']
                logger.debug("sma: {0} connected to value of {1}".format(item, sma_value))
                if not sma_value in self._val:
                    self._val[sma_value] = {'items': [item], 'logics': []}
                else:
                    if not item in self._val[sma_value]['items']:
                        self._val[sma_value]['items'].append(item)
    
                if (sma_value == 'OWN_ADDRESS'):
                    item(self._own_bt_addr, 'SMA', self._inv_bt_addr)
                if (sma_value == 'INV_ADDRESS'):
                    item(self._inv_bt_addr, 'SMA', self._inv_bt_addr)
                if (sma_value == 'INV_SERIAL'):
                    item(self._inv_serial, 'SMA', self._inv_bt_addr)
    
            # return None to indicate "read-only"
            return None
    
    
        def parse_logic(self, logic):
            if 'sma' in item.conf:
                sma_value = item.conf['sma']
                logger.debug("sma: {0} connected to value of {1}".format(item, sma_value))
                if not sma_value in self._val:
                    self._val[sma_value] = {'items': [], 'logics': [item]}
                else:
                    if not item in self._val[sma_value]['logics']:
                        self._val[sma_value]['logics'].append(item)
    
    
        # receive function both for single and multi-part messages
        def _recv_msg(self, timeout=1.0):
            # wait for sfd
            recv_char = 0
            self._btsocket.settimeout(timeout)
            while (recv_char != chr(0x7E)):
                recv_char = self._btsocket.recv(1)
                if (recv_char == None):
                    logger.warning("sma: rx: could not receive SFD within %ds" % timeout)
                    return []
            msg = [ord(recv_char)]
                
            # get level 1 length and validate
            while (len(msg) < 4):
                recv_char = self._btsocket.recv(1)
                if (recv_char == None):
                    logger.warning("sma: rx: could not receive complete length field within %ds" % timeout)
                    return []        
                msg += [ord(recv_char)]          
            if ((msg[1] ^ msg[2] ^ msg[3]) != 0x7E):
                logger.warning("sma: rx: length fields invalid")
                return []        
            length = (msg[2] << 8) + msg[1]
            if (length < 18):
                logger.warning("sma: rx: length to small: %d" % length)
                return []
                
            # get remaining characters
            while (len(msg) < length):
                # by reading as much as possible we are also receiving follow-up msgs (to clear stucked msgs - no better solution yet)!
                recv_char = self._btsocket.recv(1000)
                if (recv_char == None):
                    logger.warning("sma: rx: could not receive msg body within %ds" % timeout)
                    return []        
                msg += [ord(i) for i in recv_char];
                #logger.debug("sma: rx: len=%d/%d" % (len(msg), length))
                              
            if (len(msg) > length):
                logger.warning("sma: rx: dismissing follow-up data: %s" % ' '.join(['0x%02x' % b for b in msg[length::]]))            
                msg = msg[0:length]                 
            
            # check src and dst addr and check
            if (msg[4:10] != self._inv_bt_addr_le):
                logger.warning("sma: rx: unknown src addr")
                return []
            if (msg[10:16] != self._own_bt_addr_le) and (msg[10:16] != ZERO_ADDR) and (msg[10:16] != BCAST_ADDR):
                logger.warning("sma: rx: wrong dst addr")
                return []
                
            # 18 (SMANET1) + 4 (SMANET2+ HDR) + 2 + 6 (SRC) + 2 + 6 (DST) + 6 + 1 (CNT) + 2 (CRC) + 1 (TAIL)
            if (length >= 48) and (msg[18:23] == SMANET2_HDR):
                #logger.debug("sma: smanet2+ msg")
                # remove escape characters after SMANET2+ HDR
                i = 22
                while (i < length):                
                    if (msg[i] == 0x7d):
                        del msg[i]
                        msg[i] ^= 0x20
                        length -= 1
                    i += 1        
                #logger.debug("sma: escape chars removed")
                crc = self._calc_crc16(msg[19:-3])
                if (((crc>>8) != msg[-2]) or ((crc&0xFF) != msg[-3])):
                    logger.warning("sma: crc: crc16 error - %04x" % crc)
                    logger.warning("sma: crc: len=%d %s\n" % (len(msg), ' '.join(['0x%02x' % b for b in msg])))
                    return []
    
            #print "rx: len=%d %s\n" % (len(msg), ' '.join(['0x%02x' % b for b in msg]))                  
            return msg 
    
    
        def _recv_msg_with_cmdcode(self, cmdcodes_expected):
            retries = 10
            while (retries > 0):
                retries -= 1
                msg = self._recv_msg()
                # get cmdcode
                if (msg != []) and (((msg[17] << 8) + msg[16]) in cmdcodes_expected):
                    break
            if (retries == 0):
                logger.warning("sma: recv msg with cmdcode - retries used up!")
                return []
            return msg
    
        
        def _send_msg(self, msg):
            if (len(msg) >= 0x3a):
                # calculate crc starting with byte 19 and append with LE byte-oder
                crc = self._calc_crc16(msg[19::])
                msg += [crc & 0xff, (crc >> 8) & 0xff]
                # add escape sequences starting with byte 19
                msg = msg[0:19] + self._add_escapes(msg[19::])
                # add msg delimiter
                msg += [0x7e]
            # set length fields - msg[1] is exact overall length, msg[3] = 0x73-msg[1]
            msg[1] = len(msg) & 0xff
            msg[2] = (len(msg) >> 8) & 0xff
            msg[3] = msg[1] ^ msg[2] ^ 0x7e
            #print "tx: len=%d %s\n" % (len(msg), ' '.join(['0x%02x' % b for b in msg]))
            send = ''
            for i in msg:
                send += chr(i)
            self._btsocket.send(send)
    
        
        def _calc_crc16(self, msg):
            crc = 0xFFFF
            for i in msg:
              crc = (crc>>8) ^ FCSTAB[(crc ^ i) & 0xFF]
            crc ^= 0xFFFF      
            #print("crc16 = %x") % crc
            return crc
    
             
        def _check_crc(self, msg):
            crc = self._calc_crc16(msg[19:-3])
            if (((crc>>8) != msg[-2]) or ((crc&0xFF) != msg[-3])):
                logger.debug("sma: crc: crc16 error - %04x" % crc)
                logger.debug("sma: crc: len=%d %s\n" % (len(msg), ' '.join(['0x%02x' % b for b in msg])))
                return False
            return True
    
        
        def _add_escapes(self, msg):
            escaped = []
            for i in msg:
                if (i == 0x7d) or (i == 0x7e) or (i == 0x11) or (i == 0x12) or (i == 0x13):
                    escaped += [0x7d, i^0x20]
                else:
                    escaped += [i];                 
            return escaped
    
    
        def _inv_connect(self):
            self._send_count = 0
            self._inv_bt_addr_le = [int(x, 16) for x in self._inv_bt_addr.split(':')]
            self._inv_bt_addr_le = self._inv_bt_addr_le[::-1]
            # receive broadcast-msg from inverter
            msg = self._recv_msg_with_cmdcode([0x0002]);
            
            # extract net-id from the 0x0002 msg
            self._net_id = msg[22]
            
            # reply with wildcard src addr
            msg[4:10] = ZERO_ADDR
            msg[10:16] = self._inv_bt_addr_le
            self._send_msg(msg)
        
            # receive msg from inverter
            msg = self._recv_msg_with_cmdcode([0x000a]);
            # receive msg from inverter
            msg = self._recv_msg_with_cmdcode([0x0005,0x000c]);
            # receive msg from inverter
            msg = self._recv_msg_with_cmdcode([0x0005]);
            
            # extract own bluetooth addr
            self._own_bt_addr_le = msg[26:32]
            logger.debug("sma: own bluetooth address: %s" % ':'.join(['%02x' % b for b in self._own_bt_addr_le[::-1]]))
            
            # first SMA net2 msg
            retries = 10
            while (retries > 0):
                retries -= 1
                # level1
                cmdcode = 0x0001
                msg = [0x7E,0,0,0] + self._own_bt_addr_le + self._inv_bt_addr_le + [cmdcode&0xFF,(cmdcode>>8)&0xFF]
                # sma-net2 level
                ctrl = 0xA009
                self._send_count += 1
                if (self._send_count > 75):
                    self._send_count = 1 
                msg += SMANET2_HDR + [ctrl&0xFF,(ctrl>>8)&0xFF] + BCAST_ADDR + [0x00,0x00] + self._inv_bt_addr_le + [0x00] + [0x00] + [0,0,0,0] + [self._send_count]  
                msg += [0x80,0x00,0x02,0x00] + [0x00] + [0x00,0x00,0x00,0x00] + [0x00,0x00,0x00,0x00]
                # send msg to inverter
                self._send_msg(msg)
                # receive msg from inverter
                msg = self._recv_msg_with_cmdcode([0x0001]);
                if (msg != []):
                    break;
                    
            if (retries == 0):
                logger.warning("sma: connect - retries used up!")
                return
    
            # second SMA net2 msg
            cmdcode = 0x0001
            msg = [0x7E, 0x00, 0x00, 0x00] + self._own_bt_addr_le + self._inv_bt_addr_le + [cmdcode&0xFF,(cmdcode>>8)&0xFF]
            # sma-net2 level
            ctrl = 0xA008
            self._send_count += 1
            if (self._send_count > 75):
                self._send_count = 1 
            msg += SMANET2_HDR + [ctrl&0xFF,(ctrl>>8)&0xFF] + BCAST_ADDR + [0x00,0x03] + self._inv_bt_addr_le + [0x00] + [0x03] + [0,0,0,0] + [self._send_count]  
            msg += [0x80,0x0E,0x01,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF]
            # send msg
            self._send_msg(msg)
    
    
        def _inv_login(self):
            timestamp_utc = int(time.time())
            password_pattern = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88]
            password_pattern[0:len(self._inv_password)] = [((0x88 + ord(char)) & 0xff) for char in self._inv_password]
            
            retries = 10
            while (retries > 0):
                retries -= 1
                # level1
                cmdcode = 0x0001
                msg = [0x7E,0,0,0] + self._own_bt_addr_le + self._inv_bt_addr_le + [cmdcode&0xFF,(cmdcode>>8)&0xFF]
                # sma-net2 level
                ctrl = 0xA00E
                self._send_count += 1
                if (self._send_count > 75):
                    self._send_count = 1 
                msg += SMANET2_HDR + [ctrl&0xFF,(ctrl>>8)&0xFF] + BCAST_ADDR + [0x00,0x01] + self._inv_bt_addr_le + [0x00] + [0x01] + [0,0,0,0] + [self._send_count]  
                msg += [0x80, 0x0C, 0x04, 0xFD, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x84, 0x03, 0x00, 0x00]
                msg += [timestamp_utc & 0xff, (timestamp_utc >> 8) & 0xff, (timestamp_utc >> 16) & 0xff, (timestamp_utc >> 24) & 0xff]
                msg += [0x00, 0x00, 0x00, 0x00] + password_pattern
                # send msg to inverter
                self._send_msg(msg)
                # send msg to inverter
                self._send_msg(msg)
                # receive msg from inverter
                msg = self._recv_msg_with_cmdcode([0x0001]);
                if (msg != []):
                    break;
    
            if (retries == 0):
                logger.warning("sma: login - retries used up!")
                return
                
            # extract serial
            self._inv_serial = (msg[38] << 24) + (msg[37] << 16) + (msg[36] << 8) + msg[35]
            logger.debug("sma: inverter serial = %d" % self._inv_serial)
    
    
        def _inv_get_bt_signal_strength(self):
            cmdcode = 0x0003
            msg = [0x7E,0,0,0] + self._own_bt_addr_le + self._inv_bt_addr_le + [cmdcode&0xFF,(cmdcode>>8)&0xFF]
            msg += [0x05, 0x00]
            self._send_msg(msg)
            msg = self._recv_msg_with_cmdcode([0x0004])
            # extract signal strength
            return ((msg[22] * 100.0)/0xff)
    
        def _inv_get_value(self, value_set):
            retries = 10
            while (retries > 0):
                retries -= 1
                # level1
                cmdcode = 0x0001
                msg = [0x7E,0,0,0] + self._own_bt_addr_le + self._inv_bt_addr_le + [cmdcode&0xFF,(cmdcode>>8)&0xFF]
                # sma-net2 level
                self._send_count += 1
                if (self._send_count > 75):
                    self._send_count = 1 
                msg += SMANET2_HDR + [value_set[1]&0xFF,(value_set[1]>>8)&0xFF] + BCAST_ADDR + [0x00,0x00] + self._inv_bt_addr_le + [0x00] + [0x00] + [0,0,0,0] + [self._send_count]  
                msg += [0x80,0x00,0x02,0x00] + [value_set[2]] + [0x00] + [value_set[0]&0xFF,(value_set[0]>>8)&0xFF] + [0x00, 0xFF] + [value_set[0]&0xFF,(value_set[0]>>8)&0xFF] + [0x00]    
                # send msg to inverter
                self._send_msg(msg)
                #print "tx: len=%d %s\n" % (len(msg), ' '.join(['0x%02x' % b for b in msg]))
    
                # receive msg from inverter
                msg = self._recv_msg_with_cmdcode([cmdcode]);
                if (len(msg) >= 63):
                    # extract valuetype
                    recv_value_code = (msg[61]<<8)+msg[60]
                    if (len(msg) >= 63) and (recv_value_code == value_set[0]):
                        break
                    else:
                        logger.warning("sma: recv unrequested msg!")
                        logger.warning("sma: len=%d %s\n" % (len(msg), ' '.join(['0x%02x' % b for b in msg])))
    
            if (retries == 0):
                logger.warning("sma: get value - retries used up!")
                return []
             
            # extract time(utc)
            self._inv_last_read_timestamp_utc = (msg[66]<<24)+(msg[65]<<16)+(msg[64]<<8)+msg[63]
            self._inv_last_read_datetime = datetime.fromtimestamp(self._inv_last_read_timestamp_utc, tz.tzlocal())
            self._inv_last_read = self._inv_last_read_datetime.strftime("%d.%m.%Y %H:%M:%S")
            return msg    
        
        def _inv_get_3byte_value(self, value_set):
            msg = self._inv_get_value(value_set)    
            if (msg):
                return (msg[69]<<16)+(msg[68]<<8)+msg[67]
            else:
                return -1 
    
    if __name__ == '__main__':
        logging.basicConfig(level=logging.DEBUG)
        myplugin = Plugin('SMA')
        myplugin.run()
    Das Plugin sollte halbwegs stabil laufen, allerdings wären ein paar try-except-Blöcke ganz sinnvoll. Zudem gibt es noch mehr Datenfelder zu lesen - da hab ich bisher keine Muße für gehabt. Interessant wären wohl noch (zwei) Strings auf der DC-Seite.

    Grüße
    Robert

    #2
    Vielen Dank für das Plugin. Habe es auf meinem Raspberry Pi installiert.
    Ich kann nur sagen das es auch wunderbar mit dem SMA Sunny Tripower 8000TL-10 funktioniert.

    mfg
    Sebastian

    Kommentar


      #3
      Hi Sebastian,

      vielen Dank für die Rückmeldung. Freut mich sehr.

      Je nach dem, wie aktuell deine sh.py Version ist (develop), sind mittlerweile auch die Spannung, Ströme und DC-Leistungen der Strings einzeln verfügbar. Ebenso die Netzfrequenz und noch ein paar andere Sachen.

      Viele Grüße
      Robert

      Kommentar


        #4
        Beitrag: Plugin zum Lesen von SMA-Wechselrichtern (Sunnyboy 5000TL-21 getestet)

        Hmm, vielleicht ne blöde Frage, aber kann man das auch irgendwie auf nem Wiregate laufen lassen?
        Danach suche ich schon länger, hab meine beiden SMA WR immer noch nicht vernünftig eingebunden...

        Kommentar


          #5
          Hallo,

          das geht momentan nicht, da auf dem WG die aktuelle Python-Version nicht unterstützt wird.

          Bis bald

          Marcus

          Kommentar


            #6
            Was ich noch ganz toll finden würde, wäre eine Uploadfunktion zu pvoutput.org, so wie bei dem Tool Smaspot.

            Mit diesem Plugin ist es nun ja auch kein Problem mehr den Home Manager von SMA zu ersetzen. Der hat ja bekanntlich keine knx Unterstützung. Somit ist dann natürlich die Ansteuerung von normal Haushaltsgeräten in Bezug auf die momentan erzeugte Leistung ein Kinderspiel.

            Mfg
            Sebastian

            Kommentar


              #7
              Beitrag: Plugin zum Lesen von SMA-Wechselrichtern (Sunnyboy 5000TL-21 getestet)

              Hi Marcus,

              danke für die Antwort!
              Dann kommt wohl auf Dauer wohl 'nen Rasperry Pi hinzu, die kleinen Büchsen kosten ja nicht die Welt!
              Kannst Du mir vielleicht noch kurz schildern was ich brauche um den an den Bus zu bekommen? Schließlich wollen diese Daten ja auch in die Visu...

              Danke & Gruss,
              Hannatz

              Kommentar


                #8
                Hallo Hannatz,

                wenn Du ein WG hast, benötigst Du nichts weiter.

                Bis bald

                Marcus

                Kommentar


                  #9
                  Beitrag: Plugin zum Lesen von SMA-Wechselrichtern (Sunnyboy 5000TL-21 getestet)

                  Super, das hört man gerne!!

                  Kommentar


                    #10
                    Probleme mit SMA Plugin

                    Hallo,

                    ich wollte das sma plugin auch mal testen. Bekomme aber leider nur die Seriennummer und die Macadresse übergeben.
                    Bekomme folgendes im logfile:
                    WARNING Main Wechselrichter.Einspeiseleistung deprecated history attribute. Use sqlite as keyword instead. -- __init__.py:_parse_item:114
                    DEBUG Main sma: Wechselrichter.Einspeiseleistung connected to field AC_POWER) -- __init__.py:_parse_item:253
                    WARNING Main Wechselrichter.Tagesertrag deprecated history attribute. Use sqlite as keyword instead. -- __init__.py:_parse_item:114

                    Die Verbindung kommt zu Stande:
                    INFO sma sma: via bluetooth connected to xx:xx:xx:xx:xx
                    INFO sma sma: inverter serial = xxxxxxxx __init__.py:_inv_login:505

                    Die items Objekte sind die aus der Anleitung.

                    Wenn ich das Tool smaspot benutze bekomme ich alle Daten ausgelesen.

                    Versionen von sh.py ist die aktuelle raspberry Distribution.
                    Mit der dev Version ändert sich auch nix.

                    Was kann ich jetzt noch testen.


                    Danke

                    ip_freak

                    Kommentar


                      #11
                      AC_POWER)

                      Item Definitionen des Wechselrichtern komplett hier posten.

                      hirtory = true durch sqlite = true ersetzen.

                      Kommentar


                        #12
                        Hallo zusammen,
                        ich habe das gleiche Problem wie ip_freak.
                        Verbindungsaufbau funktioniert Seriennummer wird in der Visu angezeigt.
                        was ich bisher weiter rausgefunden habe ist das der timestamp_utc immer 0 ist .
                        Deshalb wird das zyklische Lesen der anderen Werte glaube ich gar nicht gestartet.

                        Habe ich den Code bis hierhin richtig verstanden?
                        Python ist leider noch ein wenig ungewohnt für mich ...

                        Gruß,
                        Nils

                        Kommentar


                          #13
                          Läuft jetzt bei mir !
                          Die Namen der Konstanten in der readme.md passen nicht...

                          Code:
                            [[Einspeiseleistung]]
                              type = num
                              sma = "AC_POWER" => AC_P_TOTAL
                            [[Tagesertrag]]
                              type = num
                              sma = "DAY_YIELD" => E_DAY
                            [[Gesamtertrag]]
                              type = num
                              sma = "TOTAL_YIELD" => E_TOTAL

                          Kommentar


                            #14
                            Sma

                            Super nilsen,

                            Das hatt bei mir auch das Problem behoben.

                            Hätte da aber noch eine Frage zu der Auswertung.

                            Wie macht ihr das ich zb. eine Tages Wochen Monatsstatistik oder Plot bekomme?


                            Vielen Dank

                            ip_freak

                            Kommentar


                              #15
                              Sorry Leute,

                              hatte tatsächlich vergessen die Doku zu aktualisieren...

                              Ist nun geschehen.

                              Welche Wechselrichter habt ihr, damit ich dass in die Kompatiblitätsliste eintragen kann?

                              Grüße
                              Robert

                              Kommentar

                              Lädt...
                              X