Skip to content

Commit

Permalink
Implement the water heater
Browse files Browse the repository at this point in the history
  • Loading branch information
illuzn committed Dec 15, 2023
1 parent f67e4f6 commit 017c64a
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 66 deletions.
146 changes: 81 additions & 65 deletions custom_components/rheem_eziset/api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""All API calls belong here."""
import requests
import time

from .const import LOGGER, DOMAIN

Expand All @@ -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}")
2 changes: 1 addition & 1 deletion custom_components/rheem_eziset/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
Platform.SENSOR,
Platform.BINARY_SENSOR,
# Platform.NUMBER,
# Platform.WATER_HEATER,
Platform.WATER_HEATER,
]

# SCAN INTERVAL
Expand Down
109 changes: 109 additions & 0 deletions custom_components/rheem_eziset/water_heater.py
Original file line number Diff line number Diff line change
@@ -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)




0 comments on commit 017c64a

Please sign in to comment.