Closed gerard33 closed 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?
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.
"""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")
Closing in favor of #40
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
torequests
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 torequests
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.