""" Custom integration for heat pump monitoring and control via web interface. Place this code in custom_components/heat_pump_monitor/__init__.py """ import asyncio import logging import aiohttp from datetime import timedelta from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_URL, CONF_SCAN_INTERVAL, ) from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, UpdateFailed, ) import voluptuous as vol import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) DOMAIN = "heat_pump_monitor" # Default update interval (3 seconds as per your web interface) DEFAULT_SCAN_INTERVAL = 3 # Add climate to the platforms PLATFORMS = ["sensor", "binary_sensor", "climate"] # Service constants SERVICE_SET_WATER_SETPOINT = "set_water_setpoint" ATTR_TEMPERATURE = "temperature" ATTR_DURATION = "duration" # Service schema SET_WATER_SETPOINT_SCHEMA = vol.Schema({ vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), vol.Range(min=35, max=60)), vol.Optional(ATTR_DURATION, default="forever"): cv.string, }) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up heat pump monitor from a config entry.""" url = entry.data[CONF_URL] scan_interval = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) coordinator = HeatPumpDataCoordinator( hass, _LOGGER, url=url, update_interval=timedelta(seconds=scan_interval), ) await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator # Register the service async def set_water_setpoint(call: ServiceCall) -> None: """Set the water setpoint of the heat pump.""" temperature = call.data.get(ATTR_TEMPERATURE) duration = call.data.get(ATTR_DURATION, "forever") await coordinator.set_water_setpoint(temperature, duration) hass.services.async_register( DOMAIN, SERVICE_SET_WATER_SETPOINT, set_water_setpoint, schema=SET_WATER_SETPOINT_SCHEMA, ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok class HeatPumpDataCoordinator(DataUpdateCoordinator): """Class to manage fetching heat pump data.""" def __init__( self, hass: HomeAssistant, logger: logging.Logger, url: str, update_interval: timedelta, ) -> None: """Initialize.""" super().__init__( hass, logger, name=DOMAIN, update_interval=update_interval, ) self.url = url self.base_url = url.rsplit('/', 1)[0] # Remove the last part of the URL to get the base URL async def _async_update_data(self): """Fetch data from heat pump.""" try: async with aiohttp.ClientSession() as session: async with session.get(self.url) as response: if response.status != 200: raise UpdateFailed(f"Error fetching data: {response.status}") data = await response.json() # Add water setpoint to the data water_setpoint = data.get("watersetpoint", 40.0) return { "is_operating": data.get("is_operating", False), "is_heating": data.get("is_heating", False), "aux_heat": data.get("aux_heat", False), "water_mode": data.get("water_mode", "unknown"), "watersetpoint": water_setpoint, # Add water setpoint "current_temperature": data.get("current_temperature", 0.0), # Add current temperature if available } except aiohttp.ClientError as err: raise UpdateFailed(f"Error communicating with heat pump: {err}") async def set_water_setpoint(self, temperature: float, duration: str = "forever") -> bool: """Set water setpoint temperature.""" try: setpoint_url = f"{self.base_url}/index.php?Action=SetParameter" # Prepare form data form_data = { "Key": "watersetpoint", "Value": str(temperature), "manual": duration } _LOGGER.debug(f"Setting watersetpoint to {temperature} with duration {duration}") async with aiohttp.ClientSession() as session: async with session.post( setpoint_url, data=form_data, headers={"Content-Type": "application/x-www-form-urlencoded"} ) as response: if response.status != 200: _LOGGER.error(f"Failed to set water setpoint: {response.status}") return False _LOGGER.info(f"Successfully set water setpoint to {temperature}") # Force an immediate data refresh await self.async_request_refresh() return True except Exception as err: _LOGGER.error(f"Error setting water setpoint: {err}") return False class HeatPumpEntity(CoordinatorEntity): """Base class for heat pump entities.""" def __init__(self, coordinator, key=None): """Initialize the entity.""" super().__init__(coordinator) self.key = key @property def device_info(self): """Return device information.""" return { "identifiers": {(DOMAIN, "heat_pump")}, "name": "Heat Pump", "manufacturer": "Custom Heat Pump", "model": "Web Interface", "sw_version": "1.0", }