robbinjanssen / home-assistant-omnik-inverter

Read the current, daily and total Wh from your Omnik Inverter via local network (no cloud!)
MIT License
55 stars 21 forks source link

Use of requests #28

Closed gerard33 closed 3 years ago

gerard33 commented 3 years ago

Hi Robbin, I have switched over from this custom integration to an Appdaemon script to get the data locally from my inverter. Mostly because that way I can use the same moment on which the data from the inverter and from DSMR is updated. One of the advantages of Appdaemon is that you can change code and directly use it, without restarting HA. I re-used a lot of your code in the Appdaemon app. Recently I switched from urlopen to requests and to me that seems to work more stable. The number of errors in the log has dropped a lot.

There are e.g. no errors with fp.close() when there is no data and also I am using a timeout. The change to requests should be rather simple. If you are interested I can share my code so you can make a separate branch to test that version. Let me know your thoughts.

robbinjanssen commented 3 years ago

Sorry for the late response. I like the idea and will happily look at your code. Since you've already done a lot for this component I could also make you co-owner of this repository? That way you can create your own branch and we could together implement and improve this?

gerard33 commented 3 years ago

I will share the relevant part of the script here so you can see which parts you can re-use. No need to add me as co-owner as I will keep using the Appdaemon script and have to much on my to-do list so wont have time to further develop this as a custom integration 😄. The script detects if no connection can be made and in that case restarts. That was the only way I could get data from the inverter again after the connection was lost.

Let me know if there are any questions regarding the script.

gerard33 commented 3 years ago
"""Omnik inverter solar AppDaemon app."""
import appdaemon.plugins.hass.hassapi as hass

from datetime import datetime, timedelta

import requests

SOLAR_SENSOR_TYPES = {
    "solar_power_current": {
        "friendly_name": "Zonne-energie huidig",
        "unit_of_measurement": "W",
        "icon": "mdi:solar-power",
    },
    "solar_power_current_kw": {
        "friendly_name": "Zonne-energie vandaag kW",
        "unit_of_measurement": "kW",
        "icon": "mdi:solar-power",
    },
    "solar_power_today": {
        "friendly_name": "Zonne-energie vandaag",
        "unit_of_measurement": "kWh",
        "icon": "mdi:solar-power",
    },
    "solar_power_total": {
        "friendly_name": "Zonne-energie totaal",
        "unit_of_measurement": "kWh",
        "icon": "mdi:solar-power",
    },
    "solar_wifi_strength": {
        "friendly_name": "Zonnepanelen Wifi Sterkte",
        "unit_of_measurement": "%",
        "icon": "mdi:wifi",
    },
}

class SolarReader(hass.Hass):
    """Solar class."""

    def initialize(self):
        """Call Initialize Solar
        Used this way so app can reset it self when there is an error reading the Omnik inverter.
        """
        self.real_initialization()

    def real_initialization(self, message_sent=False):
        """Initialize Solar."""
        self.log("Solar App started.")
        self.ip_address = self.args.get("ip_address")
        self.interval = self.args.get("interval", 30)
        self.chat_id = self.args.get("chat_id")
        self.title = self.args.get("title")
        self.debug = self.args.get("debug", False)
        self.result = None
        self.error_counter = 0
        self.message_sent = message_sent

        self.run_every(self.get_solar_data, "now", self.interval)

    def get_solar_data(self, kwargs):
        """Get the data from the Omnik inverter."""
        dataurl = f"http://{self.ip_address}/js/status.js"

        if self.debug:
            self.log(dataurl, log="solar_log", level="INFO")

        try:
            response = requests.get(dataurl, timeout=15)
        except requests.exceptions.ConnectionError as errc:
            self.log(
                f"Unable to fetch data from Omnik Inverter. Error message: {errc}",
                level="ERROR",
                log="solar_error_log",
            )
            self.error_counter += 1
            if self.error_counter == 4 and not self.message_sent:
                msg = f"De Omnik inverter is al {(self.error_counter - 1) * self.interval} seconden niet bereikbaar. Het script wordt opnieuw opgestart."
                self.call_service(
                    "telegram_bot/send_message",
                    target=self.chat_id,
                    title=self.title,
                    message=msg,
                )
                self.message_sent = True
                self.log(
                    "Error message sent via Telegram",
                    level="ERROR",
                    log="solar_error_log",
                )
                # Reset the script by starting the initialization
                # if "Errno 111" in errc:
                self.real_initialization(message_sent=True)
            # Update the energy sensors anyway
            self.update_energy_data()
            return
        except requests.exceptions.HTTPError as errh:
            self.log(f"HTTP error {errh}", level="ERROR", log="solar_error_log")
            return
        except requests.exceptions.Timeout as errt:
            self.log(
                f"There was a timeout error {errh}.",
                level="ERROR",
                log="solar_error_log",
            )
            return
        except requests.exceptions.RequestException as err:
            self.log(f"Unknown error {err}.", level="ERROR", log="solar_error_log")
            return
        else:  # only executed if no exceptions were raised in the try block
            if self.message_sent:
                msg = "De Omnik inverter is weer bereikbaar."
                self.call_service(
                    "telegram_bot/send_message",
                    target=self.chat_id,
                    title=self.title,
                    message=msg,
                )
                self.message_sent = False
            if self.error_counter != 0:
                self.error_counter = 0

        # Continue if the correct response code is returned
        if response.status_code == 200:
            # Find the power data
            matches = re.search(r'(?<=myDeviceArray\[0\]=").*?(?=";)', response.text)
            if self.debug:
                self.log(matches, log="solar_log", level="INFO")

            # Find other relevant information
            m2m_rssi = re.search(r'(?<=m2mRssi= ").*?(?=%";)', response.text)
            wan_ip = re.search(r'(?<=wanIp= ").*?(?=";)', response.text)
            wlan_mac = re.search(r'(?<=wlanMac= ").*?(?=";)', response.text)
            if self.debug:
                self.log(f"RSSI: {m2m_rssi}", log="solar_log", level="INFO")
                self.log(f"IP: {wan_ip}", log="solar_log", level="INFO")
                self.log(f"MAC: {wlan_mac}", log="solar_log", level="INFO")

            # Split the values
            if matches is not None:
                data = matches.group(0).split(",")
                self.result = [
                    data[0],
                    int(data[5]),
                    int(data[6]),
                    int(data[7]),
                    int(data[9]),
                ]
            else:
                self.log(
                    "Empty data from Omnik Inverter",
                    level="ERROR",
                    log="solar_error_log",
                )
                return

            self.rssi = self.ip_address = self.mac_address = None
            if m2m_rssi is not None:
                self.rssi = m2m_rssi.group(0)
            if wan_ip is not None:
                self.ip_address = wan_ip.group(0)
            if wlan_mac is not None:
                self.mac_address = wlan_mac.group(0)

            self.solar_power_current_state = self.result[1]
            self.solar_power_current_kw_state = self.solar_power_current_state / 1000
            self.solar_power_today_state = self.result[2] / 100
            self.solar_power_total_state = self.result[3] / 10
            self.solar_wifi_strength_state = self.rssi
            self.last_update = self.result[4]

            if self.debug:
                self.log(f"Current power: {self.solar_power_current_state} W", log="solar_log", level="INFO")
                self.log(f"Power today: {self.solar_power_today_state} kWh", log="solar_log", level="INFO")
                self.log(f"Power total: {self.solar_power_total_state} kWh", log="solar_log", level="INFO")
                self.log(f"Last update: {self.last_update} minutes ago", log="solar_log", level="INFO")
                self.log(f"RSSI: {self.rssi}%", log="solar_log", level="INFO")
                self.log(f"IP: {self.ip_address}", log="solar_log", level="INFO")
                self.log(f"MAC: {self.mac_address}", log="solar_log", level="INFO")

            self.update_solar_data()
        else:
            self.log(
                f"Excpected a 200 return but got {response.status_code}",
                level="ERROR",
                log="solar_error_log",
            )
            return

    def update_solar_data(self):
        """Update solar sensors with data."""

        for entity, attributes in sorted(SOLAR_SENSOR_TYPES.items()):
            if self.debug:
                self.log(f"Entity name: {entity}", log="solar_log", level="INFO")
                self.log(f"Attributes: {attributes}", log="solar_log", level="INFO")
                for key, value in attributes.items():
                    self.log(
                        f"Attribute: {key}, value: {value}",
                        log="solar_log",
                        level="INFO",
                    )
            if entity == "solar_power_current":
                attributes = {**attributes, "update_minutes_ago": self.last_update}
                state = self.solar_power_current_state
            if entity == "solar_power_current_kw":
                state = self.solar_power_current_kw_state
            if entity == "solar_power_today":
                state = self.solar_power_today_state
            if entity == "solar_power_total":
                state = self.solar_power_total_state
            if entity == "solar_wifi_strength":
                attributes = {
                    **attributes,
                    "ip_address": self.ip_address,
                    "mac_address": self.mac_address,
                }
                state = self.solar_wifi_strength_state
            entity = f"sensor.{entity}"
            self.set_state(entity=entity, state=state, attributes=attributes)

        self.log("Solar states updated", log="solar_log")
        self.log("---------------------", log="solar_log")
robbinjanssen commented 3 years ago

Closing in favor of #40