diff --git a/custom_components/rheem_eziset/__init__.py b/custom_components/rheem_eziset/__init__.py index 22e41cc..10e46e6 100644 --- a/custom_components/rheem_eziset/__init__.py +++ b/custom_components/rheem_eziset/__init__.py @@ -11,14 +11,18 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.const import CONF_HOST -from homeassistant.exceptions import ConfigEntryNotReady from .api import RheemEziSETApi -from .const import DOMAIN, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, PLATFORMS +from .const import DOMAIN, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, LOGGER, PLATFORMS from .coordinator import RheemEziSETDataUpdateCoordinator async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up this integration using UI.""" + LOGGER.debug( + "Setting up entry for device: %s", + entry.title, + ) + if hass.data.get(DOMAIN) is None: hass.data.setdefault(DOMAIN, {}) @@ -32,10 +36,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: update_interval=scan_interval ) - await coordinator.async_refresh() - - if not coordinator.last_update_success: - raise ConfigEntryNotReady + await coordinator.async_config_entry_first_refresh() hass.data[DOMAIN] = coordinator @@ -44,7 +45,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator.platforms.append(platform) hass.async_add_job(hass.config_entries.async_forward_entry_setup(entry, platform)) - entry.add_update_listener(async_reload_entry) + entry.add_update_listener(async_reload_entry) # Reload the entry on configuration changes. + return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/custom_components/rheem_eziset/api.py b/custom_components/rheem_eziset/api.py index bb92e07..91d101f 100644 --- a/custom_components/rheem_eziset/api.py +++ b/custom_components/rheem_eziset/api.py @@ -12,19 +12,22 @@ def __init__(self, host: str) -> None: self.base_url = "http://" + self.host + "/" def getInfo_data(self) -> dict: - """Create a session and get getInfo.cgi.""" - url = self.base_url + "getInfo.cgi" + """Create a session and gather sensor data.""" session = requests.Session() - response = session.get(url, verify=False) - LOGGER.debug(f"{DOMAIN} - getInfo.cgi response {response.text}") - if response.headers.get('content-type') == 'application/json': - try: - data_response: dict = response.json() - except Exception: - LOGGER.error(f"{DOMAIN} - couldn't convert response for {url} into json. Response was: {response}") - return data_response - else: - LOGGER.error(f"{DOMAIN} - received response for {url} but it doesn't appear to be json.") + + page = "getInfo.cgi" + data_responses = get_data(session=session, base_url=self.base_url, page=page) + + page = "version.cgi" + data_responses = data_responses | get_data(session=session, base_url=self.base_url, page=page) + + page = "getParams.cgi" + data_responses = data_responses | get_data(session=session, base_url=self.base_url, page=page) + + page = "heaterName.cgi" + data_responses = data_responses | get_data(session=session, base_url=self.base_url, page=page) + + return data_responses def get_XXXdata(self) -> dict: """Unused example.""" @@ -59,3 +62,30 @@ def get_XXXdata(self) -> dict: return merged_response +def get_data( + session: object, + base_url: str, + page: str, + ) -> dict: + """Get page, check for valid json responses then convert to dict format.""" + if base_url == "": + LOGGER.error(f"{DOMAIN} - api attempted to retrieve an empty base_url.") + return None + + elif page == "": + LOGGER.error(f"{DOMAIN} - api attempted to retrieve an empty base_url.") + return None + + else: + url = base_url + page + response = session.get(url, verify=False) + LOGGER.debug(f"{DOMAIN} - {page} response: {response.text}") + + if isinstance(response, object) and response.headers.get('content-type') == "application/json": + try: + data_response: dict = response.json() + except Exception: + LOGGER.error(f"{DOMAIN} - couldn't convert response for {url} into json. Response was: {response.text}") + return data_response + else: + LOGGER.error(f"{DOMAIN} - received response for {url} but it doesn't appear to be json. Response: {response.text}") diff --git a/custom_components/rheem_eziset/config_flow.py b/custom_components/rheem_eziset/config_flow.py index b6d7c09..4bea09e 100644 --- a/custom_components/rheem_eziset/config_flow.py +++ b/custom_components/rheem_eziset/config_flow.py @@ -25,10 +25,12 @@ async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" self._errors = {} - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - if user_input is not None: + # Don't allow duplicates + current_entries = self._async_current_entries() + if user_input[CONF_HOST] in current_entries: + return self.async_abort(reason="host_already_exists") + # Test connectivity valid = await self._test_host(user_input[CONF_HOST]) @@ -80,32 +82,22 @@ def __init__(self, config_entry): self.config_entry = config_entry self.options = dict(config_entry.options) - async def async_step_init(self, user_input): # pylint: disable=unused-argument - """Handle flow.""" - return await self.async_step_user() + async def async_step_init(self, user_input = None): + """Handle an option flow.""" + config = {**self.config_entry.data, **self.config_entry.options} - async def async_step_user(self, user_input=None): - """Handle flow initiated by user.""" if user_input is not None: - self.options.update(user_input) - return await self._update_options() + config = {**config, **user_input} + return self.async_create_entry(title="", data=user_input) return self.async_show_form( step_id="user", data_schema=vol.Schema( { - vol.Required( + vol.Optional( CONF_SCAN_INTERVAL, - default=self.options.get( - CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL - ), - ): vol.All(vol.Coerce(int)) + description={"suggested_value": DEFAULT_SCAN_INTERVAL} + ): vol.All(vol.Coerce(int)), } ) ) - - async def _update_options(self): - """Process options.""" - return self.async_create_entry( - title=self.config_entry.data.get(CONF_SCAN_INTERVAL), data=self.options - ) diff --git a/custom_components/rheem_eziset/const.py b/custom_components/rheem_eziset/const.py index a1fb798..86b9b52 100644 --- a/custom_components/rheem_eziset/const.py +++ b/custom_components/rheem_eziset/const.py @@ -10,6 +10,7 @@ IDPREFIX = "rheem_water_heater_" DOMAIN = "rheem_eziset" MANUFACTURER = "Rheem" +VERSION = "2023.12.1" PLATFORMS: list[Platform] = [ Platform.SENSOR, Platform.BINARY_SENSOR, @@ -19,7 +20,7 @@ # SCAN INTERVAL CONF_SCAN_INTERVAL = "scan_interval" -DEFAULT_SCAN_INTERVAL = 15 # seconds +DEFAULT_SCAN_INTERVAL = 5 # seconds # Mode Dictionary CONST_MODE_MAP = { diff --git a/custom_components/rheem_eziset/coordinator.py b/custom_components/rheem_eziset/coordinator.py index 3c9b636..af410bd 100644 --- a/custom_components/rheem_eziset/coordinator.py +++ b/custom_components/rheem_eziset/coordinator.py @@ -6,7 +6,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from.api import RheemEziSETApi +from .api import RheemEziSETApi from .const import DOMAIN, LOGGER diff --git a/custom_components/rheem_eziset/entity.py b/custom_components/rheem_eziset/entity.py index a92be8f..285470e 100644 --- a/custom_components/rheem_eziset/entity.py +++ b/custom_components/rheem_eziset/entity.py @@ -19,8 +19,9 @@ def device_info(self): """Defines the device information.""" return { "identifiers": {(DOMAIN, self.coordinator.api.host)}, - "name": NAME, + "name": self.coordinator.data.get("heaterName", NAME), "manufacturer": MANUFACTURER, + "sw_version": self.coordinator.data.get("FWversion") } @property diff --git a/custom_components/rheem_eziset/sensor.py b/custom_components/rheem_eziset/sensor.py index bef7a85..481fedd 100644 --- a/custom_components/rheem_eziset/sensor.py +++ b/custom_components/rheem_eziset/sensor.py @@ -6,7 +6,7 @@ from homeassistant.helpers.entity import EntityCategory from homeassistant.const import TIME_MINUTES, TIME_SECONDS, VOLUME_LITERS, STATE_UNAVAILABLE -from .const import ICON_TAPON, ICON_TAPOFF, ICON_WATERHEATER, CONST_MODE_MAP, CONST_STATUS_MAP, DOMAIN +from .const import ICON_TAPON, ICON_TAPOFF, ICON_WATERHEATER, CONST_MODE_MAP, CONST_STATUS_MAP, DOMAIN, LOGGER from .coordinator import RheemEziSETDataUpdateCoordinator from .entity import RheemEziSETEntity @@ -63,19 +63,26 @@ def __init__( @property def state(self): """Return the state of the sensor.""" - result = self.coordinator.data[self.key] + result = self.coordinator.data.get(self.key, STATE_UNAVAILABLE) if self.description == "Status": - if int(result) in CONST_STATUS_MAP: - return CONST_STATUS_MAP[int(result)][0] - return STATE_UNAVAILABLE + try: + result = int(result) + if int(result) in CONST_STATUS_MAP: + return CONST_STATUS_MAP[int(result)][0] + return STATE_UNAVAILABLE + except Exception: + LOGGER.error(f"{DOMAIN} - Unexpected result for status, result was {result}") + return STATE_UNAVAILABLE elif self.description == "Mode": - if int(result) in CONST_MODE_MAP: - return CONST_MODE_MAP[int(result)][0] - return STATE_UNAVAILABLE - elif result is not None: - return result + try: + result = int(result) + if int(result) in CONST_MODE_MAP: + return CONST_MODE_MAP[int(result)][0] + return STATE_UNAVAILABLE + except Exception: + LOGGER.error(f"{DOMAIN} - Unexpected result for mode, result was {result}") else: - return STATE_UNAVAILABLE + return result @property def unit_of_measurement(self): @@ -85,14 +92,14 @@ def unit_of_measurement(self): @property def icon(self): """Return the icon with processing in the case of some sensors.""" - result = self.coordinator.data[self.key] + result = self.coordinator.data.get(self.key, STATE_UNAVAILABLE) if self.description == "Flow": try: if float(result) != 0: return ICON_TAPON else: return ICON_TAPOFF - except Exception: # pylint: disable=unused-argument + except Exception: return ICON_TAPOFF elif self.description == "Status": if int(result) in CONST_STATUS_MAP: diff --git a/custom_components/rheem_eziset/translations/en.json b/custom_components/rheem_eziset/translations/en.json index 929f901..ebc5af4 100644 --- a/custom_components/rheem_eziset/translations/en.json +++ b/custom_components/rheem_eziset/translations/en.json @@ -22,7 +22,8 @@ "title": "Rheem Eziset Configuration", "description": "Please provide a scan interval to update the sensors. Warning: Read the documentation before changing this.", "data": { - "scan_interval": "15" + "scan_interval": "Sensor scan interval in seconds (Default: 15)", + "config_scan_interval": "Config scan interval in seconds (Default: 60)" } } }