Home Assistant Git Exporter
This commit is contained in:
241
config/custom_components/apsystems_ecur/__init__.py
Normal file
241
config/custom_components/apsystems_ecur/__init__.py
Normal file
@@ -0,0 +1,241 @@
|
||||
import logging
|
||||
import requests
|
||||
|
||||
import voluptuous as vol
|
||||
import traceback
|
||||
import datetime as dt
|
||||
from datetime import timedelta
|
||||
|
||||
from .APSystemsSocket import APSystemsSocket, APSystemsInvalidData
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.discovery import load_platform
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant import config_entries, exceptions
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.components.persistent_notification import (
|
||||
create as create_persistent_notification
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
UpdateFailed,
|
||||
)
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
PLATFORMS = [ "sensor", "binary_sensor", "switch" ]
|
||||
|
||||
class WiFiSet():
|
||||
ipaddr = ""
|
||||
ssid = ""
|
||||
wpa = ""
|
||||
cache = 3
|
||||
WiFiSet = WiFiSet()
|
||||
|
||||
# handle all the communications with the ECUR class and deal with our need for caching, etc
|
||||
class ECUR():
|
||||
def __init__(self, ipaddr, ssid, wpa, cache, nographs):
|
||||
self.ecu = APSystemsSocket(ipaddr, nographs)
|
||||
self.cache_count = 0
|
||||
self.data_from_cache = False
|
||||
self.querying = True
|
||||
self.inverters_online = True
|
||||
self.ecu_restarting = False
|
||||
self.cached_data = {}
|
||||
WiFiSet.ipaddr = ipaddr
|
||||
WiFiSet.ssid = ssid
|
||||
WiFiSet.wpa = wpa
|
||||
WiFiSet.cache = cache
|
||||
|
||||
def stop_query(self):
|
||||
self.querying = False
|
||||
|
||||
def start_query(self):
|
||||
self.querying = True
|
||||
|
||||
def inverters_off(self):
|
||||
headers = {'X-Requested-With': 'XMLHttpRequest'}
|
||||
url = 'http://'+ str(WiFiSet.ipaddr) + '/index.php/configuration/set_switch_all_off'
|
||||
try:
|
||||
get_url = requests.post(url, headers=headers)
|
||||
self.inverters_online = False
|
||||
_LOGGER.debug(f"Response from ECU on switching the inverters off: {str(get_url.status_code)}")
|
||||
except Exception as err:
|
||||
_LOGGER.warning(f"Attempt to switch inverters off failed with error: {err} (This switch is only compatible with ECU-R pro and ECU-C type ECU's)")
|
||||
|
||||
def inverters_on(self):
|
||||
headers = {'X-Requested-With': 'XMLHttpRequest'}
|
||||
url = 'http://'+ str(WiFiSet.ipaddr) + '/index.php/configuration/set_switch_all_on'
|
||||
try:
|
||||
get_url = requests.post(url, headers=headers)
|
||||
self.inverters_online = True
|
||||
_LOGGER.debug(f"Response from ECU on switching the inverters on: {str(get_url.status_code)}")
|
||||
except Exception as err:
|
||||
_LOGGER.warning(f"Attempt to switch inverters on failed with error: {err} (This switch is only compatible with ECU-R pro and ECU-C type ECU's)")
|
||||
|
||||
def use_cached_data(self, msg):
|
||||
# we got invalid data, so we need to pull from cache
|
||||
self.error_msg = msg
|
||||
self.cache_count += 1
|
||||
self.data_from_cache = True
|
||||
|
||||
if self.cache_count == WiFiSet.cache:
|
||||
_LOGGER.warning(f"Communication with the ECU failed after {WiFiSet.cache} repeated attempts.")
|
||||
data = {'SSID': WiFiSet.ssid, 'channel': 0, 'method': 2, 'psk_wep': '', 'psk_wpa': WiFiSet.wpa}
|
||||
_LOGGER.debug(f"Data sent with URL: {data}")
|
||||
# Determine ECU type to decide ECU restart (for ECU-C and ECU-R with sunspec only)
|
||||
if (self.cached_data.get("ecu_id", None)[0:3] == "215") or (self.cached_data.get("ecu_id", None)[0:4] == "2162"):
|
||||
url = 'http://' + str(WiFiSet.ipaddr) + '/index.php/management/set_wlan_ap'
|
||||
headers = {'X-Requested-With': 'XMLHttpRequest'}
|
||||
try:
|
||||
get_url = requests.post(url, headers=headers, data=data)
|
||||
_LOGGER.debug(f"Response from ECU on restart: {str(get_url.status_code)}")
|
||||
self.ecu_restarting = True
|
||||
except Exception as err:
|
||||
_LOGGER.warning(f"Attempt to restart ECU failed with error: {err}. Querying is stopped automatically.")
|
||||
self.querying = False
|
||||
else:
|
||||
# Older ECU-R models starting with 2160
|
||||
_LOGGER.warning("Try manually power cycling the ECU. Querying is stopped automatically, turn switch back on after restart of ECU.")
|
||||
self.querying = False
|
||||
|
||||
if self.cached_data.get("ecu_id", None) == None:
|
||||
_LOGGER.debug(f"Cached data {self.cached_data}")
|
||||
raise UpdateFailed(f"Unable to get correct data from ECU, and no cached data. See log for details, and try power cycling the ECU.")
|
||||
return self.cached_data
|
||||
|
||||
def update(self):
|
||||
data = {}
|
||||
# if we aren't actively quering data, pull data form the cache
|
||||
# this is so we can stop querying after sunset
|
||||
if not self.querying:
|
||||
_LOGGER.debug("Not querying ECU due to query=False")
|
||||
data = self.cached_data
|
||||
self.data_from_cache = True
|
||||
data["data_from_cache"] = self.data_from_cache
|
||||
data["querying"] = self.querying
|
||||
return self.cached_data
|
||||
|
||||
_LOGGER.debug("Querying ECU...")
|
||||
try:
|
||||
data = self.ecu.query_ecu()
|
||||
_LOGGER.debug("Got data from ECU")
|
||||
|
||||
# we got good results, so we store it and set flags about our cache state
|
||||
if data["ecu_id"] != None:
|
||||
self.cached_data = data
|
||||
self.cache_count = 0
|
||||
self.data_from_cache = False
|
||||
self.ecu_restarting = False
|
||||
self.error_message = ""
|
||||
else:
|
||||
msg = f"Using cached data from last successful communication from ECU. Error: no ecu_id returned"
|
||||
_LOGGER.warning(msg)
|
||||
data = self.use_cached_data(msg)
|
||||
|
||||
except APSystemsInvalidData as err:
|
||||
msg = f"Using cached data from last successful communication from ECU. Invalid data error: {err}"
|
||||
if str(err) != 'timed out':
|
||||
_LOGGER.warning(msg)
|
||||
data = self.use_cached_data(msg)
|
||||
|
||||
except Exception as err:
|
||||
msg = f"Using cached data from last successful communication from ECU. Exception error: {err}"
|
||||
_LOGGER.warning(msg)
|
||||
data = self.use_cached_data(msg)
|
||||
|
||||
data["data_from_cache"] = self.data_from_cache
|
||||
data["querying"] = self.querying
|
||||
data["restart_ecu"] = self.ecu_restarting
|
||||
_LOGGER.debug(f"Returning {data}")
|
||||
if data.get("ecu_id", None) == None:
|
||||
raise UpdateFailed(f"Somehow data doesn't contain a valid ecu_id")
|
||||
return data
|
||||
|
||||
async def update_listener(hass, config):
|
||||
# Handle options update being triggered by config entry options updates
|
||||
_LOGGER.debug(f"Configuration updated: {config.as_dict()}")
|
||||
ecu = ECUR(config.data["host"],
|
||||
config.data["SSID"],
|
||||
config.data["WPA-PSK"],
|
||||
config.data["CACHE"],
|
||||
config.data["stop_graphs"]
|
||||
)
|
||||
|
||||
async def async_setup_entry(hass, config):
|
||||
# Setup the APsystems platform """
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
host = config.data["host"]
|
||||
interval = timedelta(seconds=config.data["scan_interval"])
|
||||
# Defaults for new parameters that might not have been set yet from previous integration versions
|
||||
cache = config.data.get("CACHE", 5)
|
||||
ssid = config.data.get("SSID", "ECU-WiFi_SSID")
|
||||
wpa = config.data.get("WPA-PSK", "myWiFipassword")
|
||||
nographs = config.data.get("stop_graphs", False)
|
||||
ecu = ECUR(host, ssid, wpa, cache, nographs)
|
||||
|
||||
|
||||
async def do_ecu_update():
|
||||
return await hass.async_add_executor_job(ecu.update)
|
||||
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_method=do_ecu_update,
|
||||
update_interval=interval,
|
||||
)
|
||||
|
||||
hass.data[DOMAIN] = {
|
||||
"ecu" : ecu,
|
||||
"coordinator" : coordinator
|
||||
}
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config.entry_id,
|
||||
identifiers={(DOMAIN, f"ecu_{ecu.ecu.ecu_id}")},
|
||||
manufacturer="APSystems",
|
||||
suggested_area="Roof",
|
||||
name=f"ECU {ecu.ecu.ecu_id}",
|
||||
model=ecu.ecu.firmware,
|
||||
sw_version=ecu.ecu.firmware,
|
||||
)
|
||||
|
||||
inverters = coordinator.data.get("inverters", {})
|
||||
for uid,inv_data in inverters.items():
|
||||
model = inv_data.get("model", "Inverter")
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config.entry_id,
|
||||
identifiers={(DOMAIN, f"inverter_{uid}")},
|
||||
manufacturer="APSystems",
|
||||
suggested_area="Roof",
|
||||
name=f"Inverter {uid}",
|
||||
model=inv_data.get("model")
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(config, PLATFORMS)
|
||||
config.async_on_unload(config.add_update_listener(update_listener))
|
||||
return True
|
||||
|
||||
async def async_remove_config_entry_device(hass, config, device_entry) -> bool:
|
||||
if device_entry is not None:
|
||||
# Notify the user that the device has been removed
|
||||
create_persistent_notification(
|
||||
hass,
|
||||
title="Important notification",
|
||||
message=f"The following device was removed from the system: {device_entry}"
|
||||
)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
async def async_unload_entry(hass, config):
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(config, PLATFORMS)
|
||||
coordinator = hass.data[DOMAIN].get("coordinator")
|
||||
ecu = hass.data[DOMAIN].get("ecu")
|
||||
ecu.stop_query()
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(config.entry_id)
|
||||
return unload_ok
|
||||
Reference in New Issue
Block a user