SMA Solar WebConnect

I have also a WebConnect with only the “SMA Portal”. I don’t have the right to enable Mobius so I looked on how to extract the data directly from SMA Portal as my feed to there is working fine.

I have this under test right now. This is a python script to be executed under “AppDaemon” integration.
Don’t forget to replace xxxxxx & yyyyyy with your credential.

Probably still some debugging needed (I don’t know yet how long I can keep active my session, I try a check every 5 min and a logon once per hour.

from bs4 import BeautifulSoup
import requests
import json
import time
from datetime import datetime
import appdaemon.plugins.hass.hassapi as hass

class sma_bridge(hass.Hass):

    def initialize(self):
        self.log("Initialize sma_bridge")    

        self._loginskip = 0

        self._handler5 = None
        self.run_at_sunset( self.sunset, offset = -30 * 60)
        self.run_at_sunrise(self.sunrise, offset =   30 * 60)
        
        if self.sun_up():
            self.sunrise("")
        else:
            self.set_state("sensor.sma_power",state = 0)

        self.log("End sma_bridge")

    def sunrise(self, kwargs):
        self.log("sma_bridge: Sunrise")
        tickstart = datetime.now()
        if self._handler5 == None:
            self._handler5 = self.run_every(self.run_every_c, tickstart, 5 * 60)
    
    def sunset(self,kwargs):
        self.log("sma_bridge: Sunset")
        if self._handler5 != None:
            self.cancel_timer(self._handler5)
            self._handler5 = None
        self.set_state("sensor.sma_power",state = 0)

    def run_every_c(self, kwargs):
        myj = self.getSMAData()

        power = str(myj["PV"])

        if power.isnumeric():

            old_state = self.get_state(entity="sensor.sma_power")

            if old_state != power:
                self.set_state("sensor.sma_power",state = power, attributes = myj)
        else:
            self._session = None


    # For login: logout https://www.sunnyportal.com/Templates/Start.aspx?logout=true
    # To Fetch data: https://www.sunnyportal.com/Dashboard
    # Example 1: {"__type":"LiveDataUI","Timestamp":{"__type":"DateTime","DateTime":"2020-04-10T09:27:00","Kind":"Unspecified"},"PV":2688,"FeedIn":null,"GridConsumption":null,"DirectConsumption":null,"SelfConsumption":null,"SelfSupply":null,"TotalConsumption":null,"DirectConsumptionQuote":null,"SelfConsumptionQuote":null,"AutarkyQuote":null,"BatteryIn":null,"BatteryOut":null,"BatteryChargeStatus":null,"OperationHealth":{"__type":"StateOfHealth","Ok":1,"Warning":0,"Error":0,"Unknown":0},"BatteryStateOfHealth":null,"ModuleTemperature":null,"EnvironmentTemperature":null,"WindSpeed":null,"Insolation":null,"BatteryMode":null,"InfoMessages":[],"WarningMessages":[],"ErrorMessages":[],"Info":{}}
    # Example 2: {"__type":"LiveDataUI","Timestamp":{"__type":"DateTime","DateTime":"2020-04-10T09:47:04","Kind":"Unspecified"},"PV":null,"FeedIn":null,"GridConsumption":null,"DirectConsumption":null,"SelfConsumption":null,"SelfSupply":null,"TotalConsumption":null,"DirectConsumptionQuote":null,"SelfConsumptionQuote":null,"AutarkyQuote":null,"BatteryIn":null,"BatteryOut":null,"BatteryChargeStatus":null,"OperationHealth":null,"BatteryStateOfHealth":null,"ModuleTemperature":null,"EnvironmentTemperature":null,"WindSpeed":null,"Insolation":null,"BatteryMode":null,"InfoMessages":[],"WarningMessages":[],"ErrorMessages":[],"Info":{}}

    #############################
    #
    #       SMA Portal 
    #
    #############################
    def _Login(self):

        if self._loginskip > 0 and self._session != None:
            self._loginskip -=1
            
        else:
            self._loginskip = 12

            session = requests.session()
            url ="https://www.sunnyportal.com/Templates/Start.aspx?logout=true&ReturnUrl=%2fFixedPages%2fDashboard.aspx"

            response = session.get(url)
            myhtml = response.content
            soup = BeautifulSoup(myhtml,'html.parser')

            x = soup.find("input", {"id" : "__VIEWSTATE"} )
            viewstate = x['value']

            x = soup.find("input", {"id" : "__VIEWSTATEGENERATOR"} )
            viewstategenerator = x['value']

            parameters= {
                '__EVENTTARGET':'',
                '__EVENTARGUMENT':'',
                '__VIEWSTATE':viewstate,
                '__VIEWSTATEGENERATOR':viewstategenerator,
                'ctl00$ContentPlaceHolder1$Logincontrol1$txtUserName':'[email protected]',
                'ctl00$ContentPlaceHolder1$Logincontrol1$txtPassword':'yyyyyyyyyyyyyyyyy',
                'ctl00$ContentPlaceHolder1$Logincontrol1$LoginBtn':'Login',
                'ctl00$ContentPlaceHolder1$Logincontrol1$RedirectURL':'',
                'ctl00$ContentPlaceHolder1$Logincontrol1$RedirectPlant':'',
                'ctl00$ContentPlaceHolder1$Logincontrol1$RedirectPage':'',
                'ctl00$ContentPlaceHolder1$Logincontrol1$RedirectDevice':'',
                'ctl00$ContentPlaceHolder1$Logincontrol1$RedirectOther':'',
                'ctl00$ContentPlaceHolder1$Logincontrol1$PlantIdentifier':'',
                'ctl00$ContentPlaceHolder1$Logincontrol1$ServiceAccess':'true',
                'ctl00$ContentPlaceHolder1$Logincontrol1$MemorizePassword':'on',
                'ClientScreenWidth':2048,
                'ClientScreenHeight':1152,
                'ClientScreenAvailWidth':2048,
                'ClientScreenAvailHeight':1112,
                'ClientWindowInnerWidth':2048,
                'ClientWindowInnerHeight':1010,
                'ClientBrowserVersion':65,
                'ctl00$ContentPlaceHolder1$hiddenLanguage':'en-us'
                }

            myheaders = {
                'Accept': '*/*',
                'Accept-Encoding': 'gzip, deflate, br',
                'Accept-Language': 'en-US,en;q=0.9,fr;q=0.8',
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
                'X-Requested-With': 'XMLHttpRequest'
                }

            response = session.post(url,parameters,headers=myheaders)

            if response.status_code == 200:
                self.log ("SMA Logon :" + str(response.status_code))
                self._session = session
            else:
                self.log("Error SMA Login:" + str(response.status_code))
                self._session = None

        return self._session

    def getSMAData(self):
        session = self._Login()
        url="https://www.sunnyportal.com/Dashboard"
        myheaders = {
            'Accept': '*/*',
            'Accept-Encoding': 'gzip, deflate, br',
            'Accept-Language': 'en-US,en;q=0.9,fr;q=0.8',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
            'X-Requested-With': 'XMLHttpRequest'
            }
        
        response = session.get(url,headers=myheaders)
        if response.status_code == 200:
            myhtml = response.content
            myj = json.loads(myhtml)
            self.log("getSMAData:" + str(myj))
        else:
            self.log ("Error SMA getSMAData:" + myhtml)
            myj = {"PV": "Error"}
        return myj

i know it’s been a while since you posted your config. but i implemented it today, and it is working fine for all values, exept for PV Daly Yield

name: PV Daily Yield
unit_of_measurement: kWh
slave: 3
register: 30517
scale: 0.001
precision: 3
count: 4
data_type: uint

image
the PV total production is totaly correct, but the daily yield should be around 0.361kWh but is showing something completely different. could it be a wrong register? is there a way to find our the correct register?

i’m completely new at modbus. but you code works fine, except for that value

i anyone could help me out… thanks.

What model of inverter do you have? Download from sma site >support >downloads under your inverter range the excel file of modbus… Check there what register is the daily yield
Also check the total production register. It seems low

Andreas, thank you for your reaction. the total production is oké, the solar panels have been installed a few days ago, so that explains the low value…

i have a Sunny Boy 3.0, SB3.0-1AV-41 575.
i found the list you mean, in html format. https://files.sma.de/downloads/MODBUS-HTML_SBxx-1AV-41-V11.zip

my current register in my config file is:

    - name: PV Daily Yield
      unit_of_measurement: kWh
      slave: 3
      register: 30517
      scale: 0.001
      precision: 3
      count: 4
      data_type: uint

so the register number seems correct.

do i have to change other register values, if i would like to try register 30537.
do i have to do anything with “Number of contiguous SMA registers” = 2 and “Data type SMA” = U32

do i have to change something to “data_type: uint”

Sma registers == count so you have to Change to 2.

thanks but doesn"t seem to work, i’ll have te look into it a bit more.
when i try count 2, i have this:

    - name: PV Dagelijkse opbrengs
      unit_of_measurement: kWh
      slave: 3
      register: 30535
      count: 2
      scale: 0.01
      precision: 2
      data_type: uint

but i just keep getting -0,001kw

Weird…

your settings seem correct:
u16 ( count 1) or 32 ( count 2) or 64 (count 4) are for unsigned words
FIX0 is decimal number with no decimal space
data type is uint (unsigned integer)

try again register 30517:

    - name: PV Daily Yield
      unit_of_measurement: kWh
      slave: 3
      register: 30517
      scale: 0.001
      precision: 3
      count: 4
      data_type: uint

if that doesn’t work try sunspec registers at slave:126

thanks for all your help, but still no luck.
ik keep getting a static value when i try 30517
image

i checked the HTML list of the modbus specs for my SMA, and in the sunspecs register there is no daily yield. (see html on my wetransfer upload: WeTransfer - Send Large Files & Share Photos Online - Up to 2GB Free).

so i think for now i’ll have to live with it :slight_smile:
but realy thank you for your effort and help.

Thank you. This was very helpful for me because the SMA Solar component doesn´t seems to work for me.

Everything looked fine until after sunset when some of the values i supposed to be 0. At that time Home Assisitant shows the value -2147483648 or 42949672.95. Any ideas how to fix this? I´ve understand that this isn´t just a random numbers. I have copied your sensor config.

It’s normal because the inverter cpu is not working.You can only use template sensor in HA and filter the results

1 Like

Ok, I don´t really understand how to use the template to filter my results. I did try just to copy/paste your template without really understanding what it´s supposed to do. Should your template filter this?

  - platform: template
    sensors:
      modbus_sma_pv_power:
        entity_id: sensor.sma_power_ac
        friendly_name: 'Power Output'
        value_template: >-
            {% if states('sensor.sma_power_ac')|float < 0 or states('sensor.sma_power_ac')|float > 10000 %}
              0
            {% else %}
              {{ states('sensor.sma_power_ac') }}
            {% endif %}
        unit_of_measurement: "W"
        icon_template: mdi:flash-circle
      modbus_sma_pv_apparent_power:
        entity_id: sensor.apparent_power
        friendly_name: 'Apparent Power'
        value_template: >-
            {% if states('sensor.apparent_power')|float < 0 or states('sensor.apparent_power')|float > 10000 %}
              0
            {% else %}
              {{ states('sensor.apparent_power') }}
            {% endif %}
        unit_of_measurement: "VA"
        icon_template: mdi:flash-circle
      modbus_sma_pv_reactive_power:
        entity_id: sensor.reactive_power
        friendly_name: 'Reactive Power'
        value_template: >-
            {% if states('sensor.reactive_power')|float < 0 or states('sensor.reactive_power')|float > 10000 %}
              0
            {% else %}
              {{ states('sensor.reactive_power') }}
            {% endif %}
        unit_of_measurement: "VAr"
        icon_template: mdi:flash-circle
      modbus_sma_pv_residual:
        entity_id: sensor.sma_residual_current
        friendly_name: 'Residual Current'
        value_template: >-
            {% if states('sensor.sma_residual_current')|float < 0 or states('sensor.sma_residual_current')|float > 10000 %}
              0
            {% else %}
              {{ states('sensor.sma_residual_current') }}
            {% endif %}
        unit_of_measurement: "mA"
        icon_template: mdi:flash-circle
      modbus_sma_temperature:
        entity_id: sensor.sma_temp
        friendly_name: 'Inverter Temp'
        value_template: >-
            {% if states('sensor.sma_temp')|float < 0 or states('sensor.sma_temp')|float > 100 %}
              0
            {% else %}
              {{ states('sensor.sma_temp') }}
            {% endif %}
        unit_of_measurement: "°C"
      modbus_grid_frequency:
        entity_id: sensor.grid_frequency
        friendly_name: 'Grid Frequency'
        value_template: >-
            {% if states('sensor.grid_frequency')|float < 30 or states('sensor.grid_frequency')|float > 100 %}
              Not Measured
            {% else %}
              {{ states('sensor.grid_frequency') }}
            {% endif %}
        unit_of_measurement: "Hz"
      modbus_grid_voltage:
        entity_id: sensor.grid_voltage
        friendly_name: 'Grid Voltage'
        value_template: >-
            {% if states('sensor.grid_voltage')|float < 180 or states('sensor.grid_voltage')|float > 300 %}
              Not Measured
            {% else %}
              {{ states('sensor.grid_voltage') }}
            {% endif %}
        unit_of_measurement: "V"
      modbus_inverter_status:
        entity_id: sensor.sma_status
        friendly_name: 'Inverter Status'
        value_template: >-
            {% if is_state('sensor.sma_status', '307' ) %}
              OK
            {% elif is_state('sensor.sma_status', '303' ) %}
              Off
            {% elif is_state('sensor.sma_status', '455' ) %}
              Warning
            {% elif is_state('sensor.sma_status', '35' ) %}
              Fault
            {% endif %}
      modbus_grid_contactor:
        entity_id: sensor.sma_grid
        friendly_name: 'Grid contactor'
        value_template: >-
            {% if is_state('sensor.sma_grid', '51' ) %}
              Closed
            {% else %}
              Open
            {% endif %} 

Yes you have to use the new template sensors on lovelace eg. sensor.modbus_sma_pv_power instead of the sensor.sma_power_ac…

Alternative option is to use https://github.com/SBFspot/SBFspot
My setup available here Integration with SMA Solar STP 10000TL-20 via SBFspot

@Tom_Rubbens did you ever solve the problem with the really long number for the daily output kWh reading? im having the same issue

No, still not solved. If you ever find a solution let let know😉

Thanks for all the info in this thread :slight_smile:

I have an older SunnyBoy and the webconnect module but no HTTP access. Just enabled the modbus and can now see the values.

I also got mine working today, I have a Sunny Boy 4.0. I have the same problem, PV Daily Yield displays a fixed value. Have you been able to fix it?
I also have managed to display the DC power values for each string of panels (I have two). After looking into the modbuslist document I created the following sensors:

  • name: DC Power Input A
    unit_of_measurement: W
    slave: 3
    register: 30961
    scale: 1
    count: 2
    data_type: int
  • name: DC Power Input B
    unit_of_measurement: W
    slave: 3
    register: 30773
    scale: 1
    count: 2
    data_type: int
    I’m really happy with that, as I wanted a way to be able to quickly check what the two strings are producing.
    image

I’m also from Belgium. Have got a sunny boy 1.5
Dont have any expirience with modbus ore python.
Can i get it zorking in YAML ore do i need the python stuf?
Thanks in advance

I’ve got this working in HA. I’m also from Belgium and i’ve got a sunny boy 1.5

I am using the standard SMA integration from here https://www.home-assistant.io/integrations/sma. For some reason I do not get all values populated. Am I missing something or make changes other place where the documentation does not talk about?