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