diff --git a/custom_components/rheem_eziset/api.py b/custom_components/rheem_eziset/api.py index 91d101f..30709eb 100644 --- a/custom_components/rheem_eziset/api.py +++ b/custom_components/rheem_eziset/api.py @@ -1,5 +1,6 @@ """All API calls belong here.""" import requests +import time from .const import LOGGER, DOMAIN @@ -9,83 +10,98 @@ class RheemEziSETApi: def __init__(self, host: str) -> None: """Initialise the basic parameters.""" self.host = host - self.base_url = "http://" + self.host + "/" + self.base_url = f"http://{host}/" def getInfo_data(self) -> dict: """Create a session and gather sensor data.""" session = requests.Session() page = "getInfo.cgi" - data_responses = get_data(session=session, base_url=self.base_url, page=page) + data_responses = self.get_data(session=session, page=page) page = "version.cgi" - data_responses = data_responses | get_data(session=session, base_url=self.base_url, page=page) + data_responses |= self.get_data(session=session, 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) + data_responses |= self.get_data(session=session, page=page) return data_responses - def get_XXXdata(self) -> dict: - """Unused example.""" - url = self.base_url + "users/login" + def set_temp( + self, + temp: int + ): + """Set temperature""" session = requests.Session() - response = session.get(url, verify=False) - - # login with password - url = self.base_url + "users/login" - data = {"_method": "POST", "STLoginPWField": "", "function": "save"} - response = session.post(url, headers=self.headers, data=data, verify=False) - LOGGER.debug(f"{DOMAIN} - login response {response.text}") - - # actualize data request - url = self.base_url + "home/actualizedata" - response = session.post(url, headers=self.headers, verify=False) - LOGGER.debug(f"{DOMAIN} - actualizedata response {response.text}") - data_response: dict = response.json() - - # actualize signals request - url = self.base_url + "home/actualizesignals" - response = session.post(url, headers=self.headers, verify=False) - LOGGER.debug(f"{DOMAIN} - actualizesignals response {response.text}") - signal_response: dict = response.json() - - # logout - url = self.base_url + "users/logout" - response = session.get(url, verify=False) - - merged_response = data_response | signal_response - LOGGER.debug(f"{DOMAIN} - merged_response {merged_response}") - 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 + + # Attempt to take control + page = "ctrl.cgi?sid=0&heatingCtrl=1" + + sid = 0 + loops = 0 + + data_response = self.get_data(session=session,page=page) + sid = data_response.get("sid", 0) + loops += 1 + + result = data_response.get("heatingCtrl") + if result != 1: + # Something wrong happened. Log error and hand back control. + LOGGER.error(f"{DOMAIN} - Error when retrieving {page}. Result was: {data_response}") + page = f"ctrl.cgi?sid={sid}&heatingCtrl=0" + data_response = self.get_data(session=session,page=page) + return + + # Set temperature + + page = f"set.cgi?sid={sid}&setTemp={temp}" + data_response = self.get_data(session=session,page=page) + + result = data_response.get("reqtemp") + if int(result) != temp: + # Something wrong happened. Log error and hand back control. + LOGGER.error(f"{DOMAIN} - Error when retrieving {page}. Result was: {data_response}") + page = f"ctrl.cgi?sid={sid}&heatingCtrl=0" + data_response = self.get_data(session=session,page=page) + return + + # Per @bajarrr API seems to need a wait here before the session is ended, otherwise new temperature is not applied." + time.sleep(0.15) + + # Release control + page = f"ctrl.cgi?sid={sid}&heatingCtrl=0" + data_response = self.get_data(session=session,page=page) + result = data_response.get("sid") + if int(result) != 0: + # Something wrong happened. Log error. + LOGGER.error(f"{DOMAIN} - Error when retrieving {page}. Result was: {data_response}") + + def get_data( + self, + session: object, + page: str, + ) -> dict: + """Get page, check for valid json responses then convert to dict format.""" + + base_url = self.base_url + 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 page.") + return None + else: - LOGGER.error(f"{DOMAIN} - received response for {url} but it doesn't appear to be json. Response: {response.text}") + url = base_url + page + response = session.get(url, timeout=6.1) + 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/const.py b/custom_components/rheem_eziset/const.py index 86b9b52..b598ef9 100644 --- a/custom_components/rheem_eziset/const.py +++ b/custom_components/rheem_eziset/const.py @@ -15,7 +15,7 @@ Platform.SENSOR, Platform.BINARY_SENSOR, # Platform.NUMBER, -# Platform.WATER_HEATER, + Platform.WATER_HEATER, ] # SCAN INTERVAL diff --git a/custom_components/rheem_eziset/water_heater.py b/custom_components/rheem_eziset/water_heater.py new file mode 100644 index 0000000..1ac799b --- /dev/null +++ b/custom_components/rheem_eziset/water_heater.py @@ -0,0 +1,109 @@ +"""Water heater platform for rheem_eziset.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.components.water_heater import WaterHeaterEntity, WaterHeaterEntityFeature, STATE_GAS, STATE_OFF +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature, CONF_HOST, PRECISION_WHOLE + +from .api import RheemEziSETApi +from .const import DOMAIN, LOGGER +from .coordinator import RheemEziSETDataUpdateCoordinator +from .entity import RheemEziSETEntity + +async def async_setup_entry(hass, entry, async_add_devices): + """Add water heater for passed config_entry in HA.""" + coordinator = hass.data[DOMAIN] + + water_heater = [ + RheemEziSETWaterHeater( + coordinator, entry + ) + ] + + async_add_devices(water_heater, True) + +class RheemEziSETWaterHeater(RheemEziSETEntity, WaterHeaterEntity): + """rheem_eziset Water Heater class.""" + + def __init__( + self, + coordinator: RheemEziSETDataUpdateCoordinator, + entry: ConfigEntry, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator, entry) + self._attr_current_operation = STATE_GAS + self._attr_operation_list: [STATE_GAS, STATE_OFF] + self._attr_precision = PRECISION_WHOLE + self._attr_supported_features = WaterHeaterEntityFeature.TARGET_TEMPERATURE + self._attr_target_temperature = self.coordinator.data.get("tempMin") + self._attr_current_temperature = None + self._attr_has_entity_name = True + self.entry = entry + + @property + def name(self): + """Return a name.""" + return "Water Heater" + + @property + def unique_id(self): + """Return a unique id.""" + return f"{ConfigEntry.entry_id}-water-heater" + + @property + def extra_state_attributes(self): + """Return the optional entity specific state attributes.""" + data = {"target_temp_step": PRECISION_WHOLE} + return data + + @property + def precision(self) -> float: + """Return the precision of the system.""" + return PRECISION_WHOLE + + @property + def temperature_unit(self) -> str: + """Return the unit of measurement used by the platform.""" + return UnitOfTemperature.CELSIUS + + @property + def current_operation(self): + """Return the state of the sensor.""" + return STATE_GAS + + @property + def supported_features(self): + """Return the Supported features of the water heater.""" + return self._attr_supported_features + + @property + def min_temp(self): + """Return the minimum temperature that can be set.""" + return self.coordinator.data.get("tempMin") + + @property + def max_temp(self): + """Return the maximum temperature that can be set.""" + return self.coordinator.data.get("tempMax") + + @property + def current_temperature(self): + """Return the current temperature .""" + return self.coordinator.data.get("temp") + + @property + def target_temperature(self): + """Return the target temperature .""" + return self._attr_target_temperature + + def set_temperature(self, **kwargs): + """Set the target temperature of the water heater.""" + api = RheemEziSETApi(host=self.entry.data.get(CONF_HOST)) + temp = kwargs.get(ATTR_TEMPERATURE) + self._attr_target_temperature = temp + api.set_temp(temp) + + + +