diff --git a/custom_components/hass_pontos/__init__.py b/custom_components/hass_pontos/__init__.py index d516355..f81f890 100644 --- a/custom_components/hass_pontos/__init__.py +++ b/custom_components/hass_pontos/__init__.py @@ -1,9 +1,12 @@ from homeassistant.core import HomeAssistant from homeassistant.config_entries import ConfigEntry -from .services import register_services +from .services import register_services +from .device import register_device from .const import DOMAIN +platforms = ['sensor', 'button', 'valve', 'select'] + async def async_setup(hass: HomeAssistant, config: dict): return True @@ -11,17 +14,28 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = entry.data - # Register sensors - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, 'sensor') - ) + # Register the device + await register_device(hass, entry) # Register services await register_services(hass) + # Register entities for each platform + hass.async_create_task( + hass.config_entries.async_forward_entry_setups(entry, platforms) + ) + return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): - await hass.config_entries.async_forward_entry_unload(entry, 'sensor') - hass.data[DOMAIN].pop(entry.entry_id) - return True + # Unload each platform + unload_ok = all( + await hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in platforms + ) + + # Remove data related to this entry if everything is unloaded successfully + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok \ No newline at end of file diff --git a/custom_components/hass_pontos/button.py b/custom_components/hass_pontos/button.py new file mode 100644 index 0000000..aca8e18 --- /dev/null +++ b/custom_components/hass_pontos/button.py @@ -0,0 +1,48 @@ +# custom_components/hass_pontos/button.py +import logging +from homeassistant.components.button import ButtonEntity +from homeassistant.util import slugify +from .const import DOMAIN +from .device import get_device_info + +LOGGER = logging.getLogger(__name__) + +async def async_setup_entry(hass, entry, async_add_entities): + # Fetch device info + device_info, _ = await get_device_info(hass, entry) + + # Instantiate button + reset_button = PontosClearAlarmsButton(hass, entry, device_info) + + # Add entities + async_add_entities([reset_button], True) + +class PontosClearAlarmsButton(ButtonEntity): + """Button to clear alarms on the Pontos Base device.""" + + def __init__(self, hass, entry, device_info): + """Initialize the button.""" + self._hass = hass + self._entry = entry + self._attr_name = f"{device_info['name']} Clear alarms" + self._attr_unique_id = slugify(f"{device_info['serial_number']}_clear_alarms") + self._device_info = device_info + + async def async_press(self): + """Handle the button press to clear alarms.""" + LOGGER.info("Clear Alarms button pressed") + await self._hass.services.async_call( + DOMAIN, + "clear_alarms", # Assuming the service name for clearing alarms is "clear_alarms" + service_data={"entry_id": self._entry.entry_id} + ) + + @property + def unique_id(self): + return self._attr_unique_id + + @property + def device_info(self): + return { + "identifiers": self._device_info['identifiers'], + } \ No newline at end of file diff --git a/custom_components/hass_pontos/const.py b/custom_components/hass_pontos/const.py index 4de5a1f..e83ce9f 100644 --- a/custom_components/hass_pontos/const.py +++ b/custom_components/hass_pontos/const.py @@ -1,4 +1,5 @@ from datetime import timedelta +from homeassistant.components.valve import STATE_OPEN, STATE_OPENING, STATE_CLOSED, STATE_CLOSING DOMAIN = "hass_pontos" CONF_IP_ADDRESS = "ip_address" @@ -43,10 +44,10 @@ } VALVE_CODES = { - "10": "Closed", - "11": "Closing", - "20": "Open", - "21": "Opening" + "10": STATE_CLOSED, + "11": STATE_CLOSING, + "20": STATE_OPEN, + "21": STATE_OPENING } SENSOR_DETAILS = { @@ -168,8 +169,12 @@ "name": "Close valve", "endpoint": "set/ab/2" }, - "clear_alarm": { + "clear_alarms": { "name": "Clear alarms", "endpoint": "clr/ala" }, -} \ No newline at end of file + "set_profile": { + "name": "Set Profile", + "endpoint": "set/prf/{profile_number}" + }, +} diff --git a/custom_components/hass_pontos/device.py b/custom_components/hass_pontos/device.py new file mode 100644 index 0000000..6ad744a --- /dev/null +++ b/custom_components/hass_pontos/device.py @@ -0,0 +1,69 @@ +import logging +import time +from homeassistant.helpers.device_registry import async_get as async_get_device_registry +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC + +from .const import DOMAIN, URL_LIST, CONF_IP_ADDRESS, FETCH_INTERVAL +from .utils import fetch_data + +LOGGER = logging.getLogger(__name__) + +# Cache to store device data +_device_cache = {} + +async def get_device_info(hass, entry): + entry_id = entry.entry_id + ip_address = entry.data[CONF_IP_ADDRESS] + + # Check if the data is already cached and not expired + if entry_id in _device_cache: + cache_entry = _device_cache[entry_id] + cache_age = time.time() - cache_entry['timestamp'] + if cache_age < FETCH_INTERVAL.total_seconds(): + LOGGER.debug(f"Using cached data for device {entry_id} (age: {cache_age} seconds)") + return cache_entry['device_info'], cache_entry['data'] + else: + LOGGER.debug(f"Cache expired for device {entry_id} (age: {cache_age} seconds)") + + LOGGER.debug(f"Fetching data for device {entry_id} from the device") + + # Fetching all relevant data from the device + data = await fetch_data(ip_address, URL_LIST) + + # Assign data to variables + mac_address = data.get("getMAC", "00:00:00:00:00:00:00:00") + serial_number = data.get("getSRN", "") + firmware_version = data.get("getVER", "") + device_type = data.get("getTYP", "") + + device_info = { + "identifiers": {(DOMAIN, "pontos_base")}, + "connections": {(CONNECTION_NETWORK_MAC, mac_address)}, + "name": "Pontos Base", + "manufacturer": "Hansgrohe", + "model": device_type, + "sw_version": firmware_version, + "serial_number": serial_number, + } + + # Cache the device info and data + _device_cache[entry_id] = { + 'device_info': device_info, + 'data': data, + 'timestamp': time.time() + } + + return device_info, data + +async def register_device(hass, entry): + entry_id = entry.entry_id + + # Create a device entry with fetched data + device_registry = async_get_device_registry(hass) + device_info, _ = await get_device_info(hass, entry) + + # Register device in the device registry + device_registry.async_get_or_create( + config_entry_id=entry_id, + **device_info + ) \ No newline at end of file diff --git a/custom_components/hass_pontos/manifest.json b/custom_components/hass_pontos/manifest.json index cc03ed4..ca9f591 100644 --- a/custom_components/hass_pontos/manifest.json +++ b/custom_components/hass_pontos/manifest.json @@ -1,6 +1,6 @@ { - "domain": "hass_pontos", - "name": "Hansgrohe Pontos", + "domain": "hass_pontos", + "name": "Hansgrohe Pontos", "codeowners": [ "@sangvikh" ], @@ -8,7 +8,7 @@ "dependencies": [], "documentation": "https://github.com/sangvikh/hass-pontos", "iot_class": "local_polling", - "issue_tracker": "https://github.com/sangvikh/hass-pontos/issues", + "issue_tracker": "https://github.com/sangvikh/hass-pontos/issues", "requirements": [], - "version": "1.0.4" -} \ No newline at end of file + "version": "2.0.0" +} diff --git a/custom_components/hass_pontos/select.py b/custom_components/hass_pontos/select.py new file mode 100644 index 0000000..f9fac1d --- /dev/null +++ b/custom_components/hass_pontos/select.py @@ -0,0 +1,100 @@ +import logging +from homeassistant.components.select import SelectEntity +from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.event import async_track_state_change_event +from homeassistant.core import callback, Event +from .device import get_device_info +from .const import DOMAIN, PROFILE_CODES + +LOGGER = logging.getLogger(__name__) + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up the custom profile select entity.""" + device_info, data = await get_device_info(hass, entry) + select_entity = PontosProfileSelect(hass, entry, device_info) + async_add_entities([select_entity], True) + +class PontosProfileSelect(SelectEntity): + """Representation of a Select entity for setting profiles.""" + + def __init__(self, hass, entry, device_info): + """Initialize the profile select entity.""" + self._hass = hass + self._entry = entry + self._attr_name = f"{device_info['name']} Profile" + self._attr_unique_id = f"{device_info['serial_number']}_profile_select" + self._sensor_unique_id = f"{device_info['serial_number']}_active_profile" + self._attr_options = [ + name if name else "Not Defined" + for name in PROFILE_CODES.values() + if name and name != "not defined" + ] + self._attr_current_option = None + self._device_info = device_info + + async def async_added_to_hass(self): + """When entity is added to hass.""" + await super().async_added_to_hass() + + # Get the entity ID of the sensor using the unique ID + entity_registry = er.async_get(self.hass) + sensor_entity_id = entity_registry.async_get_entity_id("sensor", DOMAIN, self._sensor_unique_id) + + # Fetch the initial state from the sensor entity + if sensor_entity_id: + sensor_state = self.hass.states.get(sensor_entity_id) + initial_state = sensor_state.state if sensor_state else None + LOGGER.debug(f"Fetched initial profile state from sensor: {initial_state}") + self._attr_current_option = initial_state + self.async_write_ha_state() + + # Register state change listener + LOGGER.debug(f"Registering state change listener for {sensor_entity_id}") + async_track_state_change_event( + self.hass, + sensor_entity_id, + self._sensor_state_changed + ) + else: + LOGGER.error(f"Sensor with unique ID {self._sensor_unique_id} not found") + + @callback + def _sensor_state_changed(self, event: Event) -> None: + """Handle active profile sensor state changes.""" + new_state = event.data.get('new_state') + if new_state is not None: + new_option = new_state.state + if new_option != self._attr_current_option: + LOGGER.debug(f"Profile state changed to: {new_option}") + self.set_state(new_option) + + def set_state(self, state): + """Set the valve state and update Home Assistant.""" + self._attr_current_option = state + self.async_write_ha_state() + + async def async_select_option(self, option: str): + """Handle the user selecting an option.""" + LOGGER.info(f"Setting profile to {option}") + profile_number = self.map_profile_name_to_number(option) + await self._hass.services.async_call( + DOMAIN, + "set_profile", + service_data={"profile_number": profile_number, "ip_address": self._entry.data["ip_address"]} + ) + self._attr_current_option = option + self.async_write_ha_state() + + @property + def device_info(self): + """Return device info to link this entity with the device.""" + return { + "identifiers": self._device_info['identifiers'], + } + + def map_profile_name_to_number(self, profile_name): + """Map profile name to profile number.""" + for number, name in PROFILE_CODES.items(): + if name == profile_name: + return int(number) + return None diff --git a/custom_components/hass_pontos/sensor.py b/custom_components/hass_pontos/sensor.py index 12b2c78..b98f3bd 100644 --- a/custom_components/hass_pontos/sensor.py +++ b/custom_components/hass_pontos/sensor.py @@ -1,18 +1,39 @@ -import aiohttp from homeassistant.components.sensor import SensorEntity from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.device_registry import async_get as async_get_device_registry -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.util import slugify import logging -from .const import * +from .utils import fetch_data +from .device import get_device_info +from .const import CONF_IP_ADDRESS, SENSOR_DETAILS, FETCH_INTERVAL, URL_LIST LOGGER = logging.getLogger(__name__) +async def async_setup_entry(hass, entry, async_add_entities): + ip_address = entry.data[CONF_IP_ADDRESS] + device_info, data = await get_device_info(hass, entry) + + # Instantiate and add sensors + sensors = [PontosSensor(sensor_config, device_info) for key, sensor_config in SENSOR_DETAILS.items()] + async_add_entities(sensors) + + # Update data so sensors is available immediately + for sensor in sensors: + sensor.parse_data(data) + + # Function to fetch new data and update all sensors + async def update_data(_): + data = await fetch_data(ip_address, URL_LIST) + for sensor in sensors: + sensor.parse_data(data) + + # Schedule updates using the fetch interval + async_track_time_interval(hass, update_data, FETCH_INTERVAL) + class PontosSensor(SensorEntity): - def __init__(self, sensor_config): + def __init__(self, sensor_config, device_info): self._data = None - self._attr_name = f"Pontos {sensor_config['name']}" + self._attr_name = f"{device_info['name']} {sensor_config['name']}" self._endpoint = sensor_config['endpoint'] self._attr_native_unit_of_measurement = sensor_config.get('unit', None) self._attr_device_class = sensor_config.get('device_class', None) @@ -20,16 +41,13 @@ def __init__(self, sensor_config): self._format_dict = sensor_config.get('format_dict', None) self._code_dict = sensor_config.get('code_dict', None) self._scale = sensor_config.get('scale', None) - self._attr_unique_id = f"pontos_{sensor_config['name']}" - self._device_id = None + self._attr_unique_id = slugify(f"{device_info['serial_number']}_{sensor_config['name']}") + self._device_info = device_info def set_data(self, data): self._data = data self.async_write_ha_state() - def set_device_id(self, device_id): - self._device_id = device_id - @property def unique_id(self): return self._attr_unique_id @@ -37,97 +55,35 @@ def unique_id(self): @property def device_info(self): return { - "identifiers": self._device_id, + "identifiers": self._device_info['identifiers'], } @property def state(self): return self._data - -sensors = [PontosSensor(config) for key, config in SENSOR_DETAILS.items()] - -# Fetching data -async def fetch_data(ip, url_list): - urls = [url.format(ip=ip) for url in url_list] - data = {} - async with aiohttp.ClientSession() as session: - for url in urls: - async with session.get(url) as response: - if response.status == 200: - data.update(await response.json()) - else: - LOGGER.error(f"Failed to fetch data: HTTP {response.status}") - return data - -# Parsing sensor data -def parse_data(data, sensor): - """Process, format, and validate sensor data.""" - if data is None: - return None - _data = data.get(sensor._endpoint, None) - - # Apply format replacements if format_dict is present - if sensor._format_dict is not None and _data is not None: - for old, new in sensor._format_dict.items(): - _data = _data.replace(old, new) - - # Translate alarm codes if code_dict is present - if sensor._code_dict is not None and _data is not None: - _data = sensor._code_dict.get(_data, _data) - - # Scale sensor data if scale is present - if sensor._scale is not None and _data is not None: - try: - _data = float(_data) * sensor._scale - except (ValueError, TypeError): - pass - - return _data - -async def async_setup_entry(hass, entry, async_add_entities): - config = entry.data - ip_address = config[CONF_IP_ADDRESS] - device_registry = async_get_device_registry(hass) - - # Fetching all relevant data from the device - data = await fetch_data(ip_address, URL_LIST) - - # Assign data to variables - mac_address = data.get("getMAC", "00:00:00:00:00:00:00:00") - serial_number = data.get("getSRN", "") - firmware_version = data.get("getVER", "") - device_type = data.get("getTYP", "") - - # Create a device entry with fetched data - device_info = { - "identifiers": {(DOMAIN, "pontos_base")}, - "connections": {(CONNECTION_NETWORK_MAC, mac_address)}, - "name": "Pontos Base", - "manufacturer": "Hansgrohe", - "model": device_type, - "sw_version": firmware_version, - "serial_number": serial_number, - } - - device = device_registry.async_get_or_create( - config_entry_id=entry.entry_id, - **device_info - ) - - # Assign device id to each sensor and add entities - for sensor in sensors: - sensor.set_device_id(device_info['identifiers']) - async_add_entities(sensors) - - # Update data so sensors is available immediately - for sensor in sensors: - sensor.set_data(parse_data(data, sensor)) - - # Function to fetch new data and update all sensors - async def update_data(_): - data = await fetch_data(ip_address, URL_LIST) - for sensor in sensors: - sensor.set_data(parse_data(data, sensor)) - - # Schedule updates using the fetch interval - async_track_time_interval(hass, update_data, FETCH_INTERVAL) \ No newline at end of file + + # Parsing and updating sensor data + def parse_data(self, data): + """Process, format, and validate sensor data.""" + if data is None: + return None + _data = data.get(self._endpoint, None) + + # Apply format replacements if format_dict is present + if self._format_dict is not None and _data is not None: + for old, new in self._format_dict.items(): + _data = _data.replace(old, new) + + # Translate alarm codes if code_dict is present + if self._code_dict is not None and _data is not None: + _data = self._code_dict.get(_data, _data) + + # Scale sensor data if scale is present + if self._scale is not None and _data is not None: + try: + _data = float(_data) * self._scale + except (ValueError, TypeError): + pass + + # Update sensor data + self.set_data(_data) diff --git a/custom_components/hass_pontos/services.py b/custom_components/hass_pontos/services.py index b1fdefc..e0ef9d8 100644 --- a/custom_components/hass_pontos/services.py +++ b/custom_components/hass_pontos/services.py @@ -1,3 +1,48 @@ +import logging +import aiohttp + +from .const import DOMAIN, SERVICES, BASE_URL + +LOGGER = logging.getLogger(__name__) + +async def async_send_command(hass, ip_address, endpoint, data=None): + """Helper function to send commands to the device.""" + if data: + # Replace placeholders in the endpoint with actual data + endpoint = endpoint.format(**data) + + # Construct the full URL + url = BASE_URL.format(ip=ip_address) + endpoint + + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + if response.status == 200: + LOGGER.info(f"Successfully sent command to {endpoint}.") + else: + LOGGER.error(f"Failed to send command to {endpoint}: HTTP {response.status}") + +async def async_service_handler(hass, call, service_name): + """General service handler to handle different services.""" + for entry_data in hass.data[DOMAIN].values(): + ip_address = entry_data.get("ip_address") + + # Extract any additional data from the service call + data = call.data + + # Handle the service call with dynamic data if provided + endpoint = SERVICES[service_name]["endpoint"] + await async_send_command(hass, ip_address, endpoint, data) + async def register_services(hass): - # Placeholder - pass + """Register all custom services.""" + # Register each service dynamically based on the SERVICES dictionary + for service_name in SERVICES: + async def service_handler(call, service_name=service_name): + await async_service_handler(hass, call, service_name) + + hass.services.async_register( + DOMAIN, + service_name, + service_handler, + schema=None + ) diff --git a/custom_components/hass_pontos/services.yaml b/custom_components/hass_pontos/services.yaml new file mode 100644 index 0000000..566fa6b --- /dev/null +++ b/custom_components/hass_pontos/services.yaml @@ -0,0 +1,28 @@ +open_valve: + name: Open Valve + description: Opens the valve + fields: {} + +close_valve: + name: Close Valve + description: Closes the valve + fields: {} + +clear_alarm: + name: Clear Alarms + description: Clears any active alarms + fields: {} + +set_profile: + name: Set Profile + description: Sets the profile for the device. + fields: + profile_number: + name: Profile Number + description: The profile number to set. + required: true + selector: + number: + min: 1 + max: 8 + diff --git a/custom_components/hass_pontos/utils.py b/custom_components/hass_pontos/utils.py new file mode 100644 index 0000000..ae55022 --- /dev/null +++ b/custom_components/hass_pontos/utils.py @@ -0,0 +1,16 @@ +import aiohttp +import logging +LOGGER = logging.getLogger(__name__) + +# Fetching data +async def fetch_data(ip, url_list): + urls = [url.format(ip=ip) for url in url_list] + data = {} + async with aiohttp.ClientSession() as session: + for url in urls: + async with session.get(url) as response: + if response.status == 200: + data.update(await response.json()) + else: + LOGGER.error(f"Failed to fetch data: HTTP {response.status}") + return data diff --git a/custom_components/hass_pontos/valve.py b/custom_components/hass_pontos/valve.py new file mode 100644 index 0000000..85d5559 --- /dev/null +++ b/custom_components/hass_pontos/valve.py @@ -0,0 +1,119 @@ +import logging +from homeassistant.components.valve import ValveEntity, ValveEntityFeature, ValveDeviceClass +from homeassistant.helpers.event import async_track_state_change_event +from homeassistant.helpers import entity_registry as er +from homeassistant.util import slugify +from homeassistant.core import callback, Event +from .device import get_device_info +from .const import DOMAIN + +LOGGER = logging.getLogger(__name__) + +async def async_setup_entry(hass, entry, async_add_entities): + # Get device info + device_info, data = await get_device_info(hass, entry) + + # Instantiate the PontosValve entity + valve_entity = PontosValve(hass, entry, device_info) + + # Add the entity to Home Assistant + async_add_entities([valve_entity], True) + +class PontosValve(ValveEntity): + """Representation of the Pontos Valve entity.""" + + def __init__(self, hass, entry, device_info): + """Initialize the Pontos Valve.""" + self._hass = hass + self._entry = entry + self._attr_name = f"{device_info['name']} Water supply" + self._attr_unique_id = slugify(f"{device_info['serial_number']}_water_supply") + self._attr_reports_position = False + self._attr_device_class = ValveDeviceClass.WATER + self._state = None + self._device_info = device_info + self._sensor_unique_id = slugify(f"{device_info['serial_number']}_valve_status") + + async def async_added_to_hass(self): + """When entity is added to hass.""" + await super().async_added_to_hass() + + # Get the entity ID of the sensor using the unique ID + entity_registry = er.async_get(self.hass) + sensor_entity_id = entity_registry.async_get_entity_id("sensor", DOMAIN, self._sensor_unique_id) + + # Fetch the initial state from the sensor entity + if sensor_entity_id: + sensor_state = self.hass.states.get(sensor_entity_id) + initial_state = sensor_state.state if sensor_state else None + LOGGER.debug(f"Fetched initial valve state from sensor: {initial_state}") + self.set_state(initial_state) + + # Register state change listener + LOGGER.debug(f"Registering state change listener for {sensor_entity_id}") + async_track_state_change_event( + self.hass, + sensor_entity_id, + self._sensor_state_changed + ) + else: + LOGGER.error(f"Sensor with unique ID {self._sensor_unique_id} not found") + + def set_state(self, state): + """Set the valve state and update Home Assistant.""" + self._state = state + self.async_write_ha_state() + + @callback + def _sensor_state_changed(self, event: Event) -> None: + new_state = event.data.get('new_state') + if new_state is not None: + self.set_state(new_state.state) + + @property + def state(self): + """Return the current state of the valve.""" + return self._state + + @property + def supported_features(self): + """Return the features supported by this valve.""" + return ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE + + @property + def unique_id(self): + """Return the unique ID of the valve.""" + return self._attr_unique_id + + @property + def device_info(self): + """Return device info to link this entity with the device.""" + return { + "identifiers": self._device_info['identifiers'], + } + + def open_valve(self) -> None: + """Synchronously open the valve.""" + # Execute the async function within the event loop + self._hass.add_job(self.async_open) + + def close_valve(self) -> None: + """Synchronously close the valve.""" + # Execute the async function within the event loop + self._hass.add_job(self.async_close) + + async def async_open(self) -> None: + """Asynchronously open the valve.""" + await self._hass.services.async_call( + DOMAIN, + "open_valve", + service_data={"entry_id": self._entry.entry_id} + ) + + async def async_close(self) -> None: + """Asynchronously close the valve.""" + await self._hass.services.async_call( + DOMAIN, + "close_valve", + service_data={"entry_id": self._entry.entry_id} + ) diff --git a/readme.md b/readme.md index 2eb631b..583a3d7 100644 --- a/readme.md +++ b/readme.md @@ -2,22 +2,47 @@ HACS integration for Hansgrohe Pontos -# Installation +## Features + +* Adds sensors for water consumption, water pressure, water temperature +++ +* Opening/Closing of water valve +* Clearing alarms + +## Installation + +Installation via HACS is the recommended method + +### HACS +Note: This integration is not yet included in HACS 1. Install HACS if you haven't already (see [installation guide](https://hacs.xyz/docs/configuration/basic/)). 2. Add custom repository https://github.com/sangvikh/hass-pontos as "Integration" in the settings tab of HACS. 3. Find and install Hansgrohe Pontos integration in HACS's "Integrations" tab. 4. Restart Home Assistant. -5. Go to your integrations page and click Add Integration and look for Hansgrohe Pontos. -6. Set up sensor using IP address of your pontos, fixed ip is reccomended +5. Go to your integrations page, click Add Integration and look for Hansgrohe Pontos. +6. Set up sensor using the IP address of your pontos, fixed ip is recommended. + +### Manual installation + +1. Clone repository or download as zip +2. Move the custom_components/hass_pontos folder to the custom_components directory of your Home Assistant installation +3. Restart Home Assistant. +5. Go to your integrations page, click Add Integration and look for Hansgrohe Pontos. +6. Set up sensor using the IP address of your pontos, fixed ip is recommended. -# Known issues +### Restful integration +The sensors and services can be added using restful integration. This is a bit limited and not recommended. -- Removing and then adding integration does not work without a restart inbetween. Sensors are not unregistered properly. +1. Copy the contents of restful.yaml into the configuration.yaml of your Home Assistant integration. +2. Find and replace IP address with the address of your Pontos. +3. Restart Home Assistant. # TODO -- [ ] Add services -- [ ] Add water valve button +- [x] Add services +- [x] Add water valve button +- [x] Add profile selection - [ ] Read profile names +- [ ] Update sensors on service calls +- [x] Select active profile - [ ] Include in HACS \ No newline at end of file