Repository: home-assistant/core
Pull request: Update Aseko to support new API
<!--
  You are amazing! Thanks for contributing to our project!
  Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
-->
## Breaking change
<!--
  If your PR contains a breaking change for existing users, it is important
  to tell them what breaks, how to make it work again and why we did this.
  This piece of text is published with the release notes, so it helps if you
  write it towards our users, not us.
  Note: Remove this section if this PR is NOT a breaking change.
-->
Aseko released a new app, website and API. Some entities might not be available anymore.

## Proposed change
<!--
  Describe the big picture of your changes here to communicate to the
  maintainers why we should accept this pull request. If it fixes a bug
  or resolves a feature request, be sure to link to that issue in the
  additional information section.
-->

See breaking change

Updated dependency: https://github.com/milanmeu/aioaseko/releases/tag/v1.0.0


## Type of change
<!--
  What type of change does your PR introduce to Home Assistant?
  NOTE: Please, check only 1! box!
  If your PR requires multiple boxes to be checked, you'll most likely need to
  split it into multiple PRs. This makes things easier and faster to code review.
-->

- [x] Dependency upgrade
- [x] Bugfix (non-breaking change which fixes an issue)
- [ ] New integration (thank you!)
- [x] New feature (which adds functionality to an existing integration)
- [ ] Deprecation (breaking change to happen in the future)
- [x] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests

## Additional information
<!--
  Details are important, and help maintainers processing your PR.
  Please be sure to fill out additional details, if applicable.
-->

- This PR fixes or closes issue: fixes #125657 
- This PR is related to issue: 
- Link to documentation pull request: 

## Checklist
<!--
  Put an `x` in the boxes that apply. You can also fill these out after
  creating the PR. If you're unsure about any of them, don't hesitate to ask.
  We're here to help! This is simply a reminder of what we are going to look
  for before merging your code.
-->

- [x] The code change is tested and works locally.
- [x] Local tests pass. **Your PR cannot be merged unless tests pass**
- [x] There is no commented out code in this PR.
- [x] I have followed the [development checklist][dev-checklist]
- [x] I have followed the [perfect PR recommendations][perfect-pr]
- [x] The code has been formatted using Ruff (`ruff format homeassistant tests`)
- [x] Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]

If the code communicates with devices, web services, or third-party tools:

- [x] The [manifest file][manifest-docs] has all fields filled out correctly.  
      Updated and included derived files by running: `python3 -m script.hassfest`.
- [x] New or updated dependencies have been added to `requirements_all.txt`.  
      Updated by running `python3 -m script.gen_requirements_all`.
- [x] For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

<!--
  This project is very active and we have a high turnover of pull requests.

  Unfortunately, the number of incoming pull requests is higher than what our
  reviewers can review and merge so there is a long backlog of pull requests
  waiting for review. You can help here!
  
  By reviewing another pull request, you will help raise the code quality of
  that pull request and the final review will be faster. This way the general
  pace of pull request reviews will go up and your wait time will go down.
  
  When picking a pull request to review, try to choose one that hasn't yet
  been reviewed.

  Thanks for helping out!
-->

To help with the load of incoming pull requests:

- [ ] I have reviewed two other [open pull requests][prs] in this repository.

[prs]: https://github.com/home-assistant/core/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+-label%3Awaiting-for-upstream+sort%3Acreated-desc+review%3Anone+-status%3Afailure

<!--
  Thank you for contributing <3

  Below, some useful links you could explore:
-->
[dev-checklist]: https://developers.home-assistant.io/docs/development_checklist/
[manifest-docs]: https://developers.home-assistant.io/docs/creating_integration_manifest/
[quality-scale]: https://developers.home-assistant.io/docs/integration_quality_scale_index/
[docs-repository]: https://github.com/home-assistant/home-assistant.io
[perfect-pr]: https://developers.home-assistant.io/docs/review-process/#creating-the-perfect-pr


Problem statement:
Add support for ASIN AQUA Home after major update.
### The problem

This morning Aseko Pool Live was update to a new platform and the old seems to have beem immediately shut down without any transition phase.

### What version of Home Assistant Core has the issue?

core-2024.9.1

### What was the last working version of Home Assistant Core?

core-2024.9.1

### What type of installation are you running?

Home Assistant OS

### Integration causing the issue

Aseko Live Pool

### Link to integration documentation on our website

_No response_

### Diagnostics information

_No response_

### Example YAML snippet

_No response_

### Anything in the logs that might be useful for us?

_No response_

### Additional information

_No response_

Hints from issue discussion:
Hey there @milanmeu, mind taking a look at this issue as it has been labeled with an integration (`aseko_pool_live`) you are listed as a [code owner](https://github.com/home-assistant/core/blob/dev/CODEOWNERS#L142) for? Thanks!

<details>
<summary>Code owner commands</summary>


Code owners of `aseko_pool_live` can trigger bot actions by commenting:

- `@home-assistant close` Closes the issue.
- `@home-assistant rename Awesome new title` Renames the issue.
- `@home-assistant reopen` Reopen the issue.
- `@home-assistant unassign aseko_pool_live` Removes the current integration label and assignees on the issue, add the integration domain after the command.
- `@home-assistant add-label needs-more-information` Add a label (needs-more-information, problem in dependency, problem in custom component) to the issue.
- `@home-assistant remove-label needs-more-information` Remove a label (needs-more-information, problem in dependency, problem in custom component) on the issue.

</details>


<sub><sup>(message by CodeOwnersMention)</sup></sub>

---

[aseko_pool_live documentation](https://www.home-assistant.io/integrations/aseko_pool_live)
[aseko_pool_live source](https://github.com/home-assistant/core/tree/dev/homeassistant/components/aseko_pool_live)
<sub><sup>(message by IssueLinks)</sup></sub>
I have the same problem. Why was the issue closed?
> I have the same problem. Why was the issue closed?

Still open. Issue 125695 was closed because it’s the same as this one. 

@milanmeu thanks for your efforts!
I'm working on a fix.
Great - let us know if you need some testing done!
@Winnetoux  #125967 The ASIN AQUA Oxygen is supported and tested, but not all sensors may be available in the new release.

Commit message: Update Aseko to support new API

Patch to review:
diff --git a/homeassistant/components/aseko_pool_live/__init__.py b/homeassistant/components/aseko_pool_live/__init__.py
index 5773b3eb5b9137..a1d5d28913ac66 100644
--- a/homeassistant/components/aseko_pool_live/__init__.py
+++ b/homeassistant/components/aseko_pool_live/__init__.py
@@ -4,13 +4,12 @@
 
 import logging
 
-from aioaseko import APIUnavailable, InvalidAuthCredentials, MobileAccount
+from aioaseko import Aseko, AsekoNotLoggedIn
 
 from homeassistant.config_entries import ConfigEntry
 from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform
 from homeassistant.core import HomeAssistant
-from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
-from homeassistant.helpers.aiohttp_client import async_get_clientsession
+from homeassistant.exceptions import ConfigEntryAuthFailed
 
 from .const import DOMAIN
 from .coordinator import AsekoDataUpdateCoordinator
@@ -22,28 +21,17 @@
 
 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     """Set up Aseko Pool Live from a config entry."""
-    account = MobileAccount(
-        async_get_clientsession(hass),
-        username=entry.data[CONF_EMAIL],
-        password=entry.data[CONF_PASSWORD],
-    )
+    aseko = Aseko(entry.data[CONF_EMAIL], entry.data[CONF_PASSWORD])
 
     try:
-        units = await account.get_units()
-    except InvalidAuthCredentials as err:
+        user = await aseko.login()
+    except AsekoNotLoggedIn as err:
         raise ConfigEntryAuthFailed from err
-    except APIUnavailable as err:
-        raise ConfigEntryNotReady from err
-
-    hass.data.setdefault(DOMAIN, {})[entry.entry_id] = []
-
-    for unit in units:
-        coordinator = AsekoDataUpdateCoordinator(hass, unit)
-        await coordinator.async_config_entry_first_refresh()
-        hass.data[DOMAIN][entry.entry_id].append((unit, coordinator))
 
+    coordinator = AsekoDataUpdateCoordinator(hass, aseko)
+    await coordinator.async_config_entry_first_refresh()
+    hass.data.setdefault(DOMAIN, {})[entry.entry_id] = (user.user_id, coordinator)
     await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
-
     return True
 
 
@@ -51,7 +39,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     """Unload a config entry."""
     if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
         hass.data[DOMAIN].pop(entry.entry_id)
-
     return unload_ok
 
 
diff --git a/homeassistant/components/aseko_pool_live/binary_sensor.py b/homeassistant/components/aseko_pool_live/binary_sensor.py
index 799535657697c6..10fd16b91f3430 100644
--- a/homeassistant/components/aseko_pool_live/binary_sensor.py
+++ b/homeassistant/components/aseko_pool_live/binary_sensor.py
@@ -8,7 +8,6 @@
 from aioaseko import Unit
 
 from homeassistant.components.binary_sensor import (
-    BinarySensorDeviceClass,
     BinarySensorEntity,
     BinarySensorEntityDescription,
 )
@@ -25,26 +24,19 @@
 class AsekoBinarySensorEntityDescription(BinarySensorEntityDescription):
     """Describes an Aseko binary sensor entity."""
 
-    value_fn: Callable[[Unit], bool]
+    value_fn: Callable[[Unit], bool | None]
 
 
-UNIT_BINARY_SENSORS: tuple[AsekoBinarySensorEntityDescription, ...] = (
+BINARY_SENSORS: tuple[AsekoBinarySensorEntityDescription, ...] = (
     AsekoBinarySensorEntityDescription(
-        key="water_flow",
-        translation_key="water_flow",
-        value_fn=lambda unit: unit.water_flow,
+        key="water_flow_to_probes",
+        translation_key="water_flow_to_probes",
+        value_fn=lambda unit: unit.water_flow_to_probes,
     ),
     AsekoBinarySensorEntityDescription(
-        key="has_alarm",
-        translation_key="alarm",
-        value_fn=lambda unit: unit.has_alarm,
-        device_class=BinarySensorDeviceClass.SAFETY,
-    ),
-    AsekoBinarySensorEntityDescription(
-        key="has_error",
-        translation_key="error",
-        value_fn=lambda unit: unit.has_error,
-        device_class=BinarySensorDeviceClass.PROBLEM,
+        key="heating",
+        translation_key="heating",
+        value_fn=lambda unit: unit.heating,
     ),
 )
 
@@ -55,33 +47,25 @@ async def async_setup_entry(
     async_add_entities: AddEntitiesCallback,
 ) -> None:
     """Set up the Aseko Pool Live binary sensors."""
-    data: list[tuple[Unit, AsekoDataUpdateCoordinator]] = hass.data[DOMAIN][
+    data: tuple[str, AsekoDataUpdateCoordinator] = hass.data[DOMAIN][
         config_entry.entry_id
     ]
-    async_add_entities(
-        AsekoUnitBinarySensorEntity(unit, coordinator, description)
-        for unit, coordinator in data
-        for description in UNIT_BINARY_SENSORS
-    )
+    user_id, coordinator = data
+    units = coordinator.data.values()
+    for unit in units:
+        async_add_entities(
+            AsekoBinarySensorEntity(unit, user_id, coordinator, description)
+            for description in BINARY_SENSORS
+            if description.value_fn(unit) is not None
+        )
 
 
-class AsekoUnitBinarySensorEntity(AsekoEntity, BinarySensorEntity):
-    """Representation of a unit water flow binary sensor entity."""
+class AsekoBinarySensorEntity(AsekoEntity, BinarySensorEntity):
+    """Representation of an Aseko binary sensor entity."""
 
     entity_description: AsekoBinarySensorEntityDescription
 
-    def __init__(
-        self,
-        unit: Unit,
-        coordinator: AsekoDataUpdateCoordinator,
-        entity_description: AsekoBinarySensorEntityDescription,
-    ) -> None:
-        """Initialize the unit binary sensor."""
-        super().__init__(unit, coordinator)
-        self.entity_description = entity_description
-        self._attr_unique_id = f"{self._unit.serial_number}_{entity_description.key}"
-
     @property
-    def is_on(self) -> bool:
+    def is_on(self) -> bool | None:
         """Return the state of the sensor."""
         return self.entity_description.value_fn(self._unit)
diff --git a/homeassistant/components/aseko_pool_live/config_flow.py b/homeassistant/components/aseko_pool_live/config_flow.py
index ce6de3683d5545..c0edee694bedcf 100644
--- a/homeassistant/components/aseko_pool_live/config_flow.py
+++ b/homeassistant/components/aseko_pool_live/config_flow.py
@@ -6,12 +6,11 @@
 import logging
 from typing import Any
 
-from aioaseko import APIUnavailable, InvalidAuthCredentials, WebAccount
+from aioaseko import Aseko, AsekoAPIError, AsekoInvalidCredentials
 import voluptuous as vol
 
 from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
 from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_UNIQUE_ID
-from homeassistant.helpers.aiohttp_client import async_get_clientsession
 
 from .const import DOMAIN
 
@@ -34,15 +33,12 @@ class AsekoConfigFlow(ConfigFlow, domain=DOMAIN):
 
     async def get_account_info(self, email: str, password: str) -> dict:
         """Get account info from the mobile API and the web API."""
-        session = async_get_clientsession(self.hass)
-
-        web_account = WebAccount(session, email, password)
-        web_account_info = await web_account.login()
-
+        aseko = Aseko(email, password)
+        user = await aseko.login()
         return {
             CONF_EMAIL: email,
             CONF_PASSWORD: password,
-            CONF_UNIQUE_ID: web_account_info.user_id,
+            CONF_UNIQUE_ID: user.user_id,
         }
 
     async def async_step_user(
@@ -58,9 +54,9 @@ async def async_step_user(
                 info = await self.get_account_info(
                     user_input[CONF_EMAIL], user_input[CONF_PASSWORD]
                 )
-            except APIUnavailable:
+            except AsekoAPIError:
                 errors["base"] = "cannot_connect"
-            except InvalidAuthCredentials:
+            except AsekoInvalidCredentials:
                 errors["base"] = "invalid_auth"
             except Exception:
                 _LOGGER.exception("Unexpected exception")
@@ -122,9 +118,9 @@ async def async_step_reauth_confirm(
                 info = await self.get_account_info(
                     user_input[CONF_EMAIL], user_input[CONF_PASSWORD]
                 )
-            except APIUnavailable:
+            except AsekoAPIError:
                 errors["base"] = "cannot_connect"
-            except InvalidAuthCredentials:
+            except AsekoInvalidCredentials:
                 errors["base"] = "invalid_auth"
             except Exception:
                 _LOGGER.exception("Unexpected exception")
diff --git a/homeassistant/components/aseko_pool_live/coordinator.py b/homeassistant/components/aseko_pool_live/coordinator.py
index a7f2d5ad5acea3..eb7ccf9ec42266 100644
--- a/homeassistant/components/aseko_pool_live/coordinator.py
+++ b/homeassistant/components/aseko_pool_live/coordinator.py
@@ -5,34 +5,31 @@
 from datetime import timedelta
 import logging
 
-from aioaseko import Unit, Variable
+from aioaseko import Aseko, Unit
 
 from homeassistant.core import HomeAssistant
 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
 
+from .const import DOMAIN
+
 _LOGGER = logging.getLogger(__name__)
 
 
-class AsekoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Variable]]):
+class AsekoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Unit]]):
     """Class to manage fetching Aseko unit data from single endpoint."""
 
-    def __init__(self, hass: HomeAssistant, unit: Unit) -> None:
+    def __init__(self, hass: HomeAssistant, aseko: Aseko) -> None:
         """Initialize global Aseko unit data updater."""
-        self._unit = unit
-
-        if self._unit.name:
-            name = self._unit.name
-        else:
-            name = f"{self._unit.type}-{self._unit.serial_number}"
+        self._aseko = aseko
 
         super().__init__(
             hass,
             _LOGGER,
-            name=name,
+            name=DOMAIN,
             update_interval=timedelta(minutes=2),
         )
 
-    async def _async_update_data(self) -> dict[str, Variable]:
+    async def _async_update_data(self) -> dict[str, Unit]:
         """Fetch unit data."""
-        await self._unit.get_state()
-        return {variable.type: variable for variable in self._unit.variables}
+        units = await self._aseko.get_units()
+        return {unit.serial_number: unit for unit in units}
diff --git a/homeassistant/components/aseko_pool_live/entity.py b/homeassistant/components/aseko_pool_live/entity.py
index 6f0979da2e7c6a..1c89c1a6732d3a 100644
--- a/homeassistant/components/aseko_pool_live/entity.py
+++ b/homeassistant/components/aseko_pool_live/entity.py
@@ -3,6 +3,7 @@
 from aioaseko import Unit
 
 from homeassistant.helpers.device_registry import DeviceInfo
+from homeassistant.helpers.entity import EntityDescription
 from homeassistant.helpers.update_coordinator import CoordinatorEntity
 
 from .const import DOMAIN
@@ -14,20 +15,46 @@ class AsekoEntity(CoordinatorEntity[AsekoDataUpdateCoordinator]):
 
     _attr_has_entity_name = True
 
-    def __init__(self, unit: Unit, coordinator: AsekoDataUpdateCoordinator) -> None:
+    def __init__(
+        self,
+        unit: Unit,
+        user_id: str,
+        coordinator: AsekoDataUpdateCoordinator,
+        description: EntityDescription,
+    ) -> None:
         """Initialize the aseko entity."""
         super().__init__(coordinator)
+        self.entity_description = description
         self._unit = unit
-
-        if self._unit.type == "Remote":
-            self._device_model = "ASIN Pool"
-        else:
-            self._device_model = f"ASIN AQUA {self._unit.type}"
-        self._device_name = self._unit.name if self._unit.name else self._device_model
-
+        self._attr_unique_id = (
+            f"{user_id}_{self._unit.serial_number}_{self.entity_description.key}"
+        )
         self._attr_device_info = DeviceInfo(
-            name=self._device_name,
-            identifiers={(DOMAIN, str(self._unit.serial_number))},
-            manufacturer="Aseko",
-            model=self._device_model,
+            identifiers={(DOMAIN, f"{user_id}_{self._unit.serial_number}")},
+            serial_number=self._unit.serial_number,
+            name=(
+                self._unit.name
+                if (self._unit.name is not None and self._unit.name != "")
+                else self._unit.serial_number
+            ),
+            manufacturer=(
+                self._unit.brand_name.primary
+                if self._unit.brand_name is not None
+                else None
+            ),
+            model=(
+                self._unit.brand_name.secondary
+                if self._unit.brand_name is not None
+                else None
+            ),
+            configuration_url=f"https://aseko.cloud/unit/{self._unit.serial_number}",
         )
+
+    def _handle_coordinator_update(self) -> None:
+        self._unit = self.coordinator.data[self._unit.serial_number]
+        return super()._handle_coordinator_update()
+
+    @property
+    def available(self) -> bool:
+        """Return True if entity is available."""
+        return super().available and self._unit.online
diff --git a/homeassistant/components/aseko_pool_live/icons.json b/homeassistant/components/aseko_pool_live/icons.json
index 2f8a77fc417c34..d494252cd89cd9 100644
--- a/homeassistant/components/aseko_pool_live/icons.json
+++ b/homeassistant/components/aseko_pool_live/icons.json
@@ -1,16 +1,28 @@
 {
   "entity": {
     "binary_sensor": {
-      "water_flow": {
+      "water_flow_to_probes": {
         "default": "mdi:waves-arrow-right"
+      },
+      "heating": {
+        "default": "mdi:heat-wave"
       }
     },
     "sensor": {
-      "free_chlorine": {
-        "default": "mdi:flask"
+      "air_temperature": {
+        "default": "mdi:thermometer-lines"
+      },
+      "cl_free": {
+        "default": "mdi:pool"
+      },
+      "redox": {
+        "default": "mdi:pool"
+      },
+      "salinity": {
+        "default": "mdi:pool"
       },
       "water_temperature": {
-        "default": "mdi:coolant-temperature"
+        "default": "mdi:pool-thermometer"
       }
     }
   }
diff --git a/homeassistant/components/aseko_pool_live/manifest.json b/homeassistant/components/aseko_pool_live/manifest.json
index a340408ad7116b..628a973218813b 100644
--- a/homeassistant/components/aseko_pool_live/manifest.json
+++ b/homeassistant/components/aseko_pool_live/manifest.json
@@ -6,5 +6,5 @@
   "documentation": "https://www.home-assistant.io/integrations/aseko_pool_live",
   "iot_class": "cloud_polling",
   "loggers": ["aioaseko"],
-  "requirements": ["aioaseko==0.2.0"]
+  "requirements": ["aioaseko==1.0.0"]
 }
diff --git a/homeassistant/components/aseko_pool_live/sensor.py b/homeassistant/components/aseko_pool_live/sensor.py
index a4ddea9ad89c4c..25c5221ea12817 100644
--- a/homeassistant/components/aseko_pool_live/sensor.py
+++ b/homeassistant/components/aseko_pool_live/sensor.py
@@ -2,77 +2,107 @@
 
 from __future__ import annotations
 
-from aioaseko import Unit, Variable
+from collections.abc import Callable
+from dataclasses import dataclass
+
+from aioaseko import Unit
 
 from homeassistant.components.sensor import (
     SensorDeviceClass,
     SensorEntity,
+    SensorEntityDescription,
     SensorStateClass,
 )
 from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import UnitOfElectricPotential, UnitOfTemperature
 from homeassistant.core import HomeAssistant
 from homeassistant.helpers.entity_platform import AddEntitiesCallback
+from homeassistant.helpers.typing import StateType
 
 from .const import DOMAIN
 from .coordinator import AsekoDataUpdateCoordinator
 from .entity import AsekoEntity
 
 
+@dataclass(frozen=True, kw_only=True)
+class AsekoSensorEntityDescription(SensorEntityDescription):
+    """Describes an Aseko sensor entity."""
+
+    value_fn: Callable[[Unit], StateType]
+
+
+SENSORS: list[AsekoSensorEntityDescription] = [
+    AsekoSensorEntityDescription(
+        key="air_temperature",
+        translation_key="air_temperature",
+        device_class=SensorDeviceClass.TEMPERATURE,
+        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
+        state_class=SensorStateClass.MEASUREMENT,
+        value_fn=lambda unit: unit.air_temperature,
+    ),
+    AsekoSensorEntityDescription(
+        key="cl_free",
+        translation_key="cl_free",
+        native_unit_of_measurement="mg/l",
+        state_class=SensorStateClass.MEASUREMENT,
+        value_fn=lambda unit: unit.cl_free,
+    ),
+    AsekoSensorEntityDescription(
+        key="ph",
+        device_class=SensorDeviceClass.PH,
+        state_class=SensorStateClass.MEASUREMENT,
+        value_fn=lambda unit: unit.ph,
+    ),
+    AsekoSensorEntityDescription(
+        key="redox",
+        translation_key="redox",
+        native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
+        state_class=SensorStateClass.MEASUREMENT,
+        value_fn=lambda unit: unit.redox,
+    ),
+    AsekoSensorEntityDescription(
+        key="salinity",
+        translation_key="salinity",
+        native_unit_of_measurement="kg/m³",
+        state_class=SensorStateClass.MEASUREMENT,
+        value_fn=lambda unit: unit.salinity,
+    ),
+    AsekoSensorEntityDescription(
+        key="water_temperature",
+        translation_key="water_temperature",
+        device_class=SensorDeviceClass.TEMPERATURE,
+        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
+        state_class=SensorStateClass.MEASUREMENT,
+        value_fn=lambda unit: unit.water_temperature,
+    ),
+]
+
+
 async def async_setup_entry(
     hass: HomeAssistant,
     config_entry: ConfigEntry,
     async_add_entities: AddEntitiesCallback,
 ) -> None:
     """Set up the Aseko Pool Live sensors."""
-    data: list[tuple[Unit, AsekoDataUpdateCoordinator]] = hass.data[DOMAIN][
+    data: tuple[str, AsekoDataUpdateCoordinator] = hass.data[DOMAIN][
         config_entry.entry_id
     ]
+    user_id, coordinator = data
+    units = coordinator.data.values()
+    for unit in units:
+        async_add_entities(
+            AsekoSensorEntity(unit, user_id, coordinator, description)
+            for description in SENSORS
+            if getattr(unit, description.key) is not None
+        )
+
+
+class AsekoSensorEntity(AsekoEntity, SensorEntity):
+    """Representation of an Aseko unit sensor entity."""
 
-    async_add_entities(
-        VariableSensorEntity(unit, variable, coordinator)
-        for unit, coordinator in data
-        for variable in unit.variables
-    )
-
-
-class VariableSensorEntity(AsekoEntity, SensorEntity):
-    """Representation of a unit variable sensor entity."""
-
-    _attr_state_class = SensorStateClass.MEASUREMENT
-
-    def __init__(
-        self, unit: Unit, variable: Variable, coordinator: AsekoDataUpdateCoordinator
-    ) -> None:
-        """Initialize the variable sensor."""
-        super().__init__(unit, coordinator)
-        self._variable = variable
-
-        translation_key = {
-            "Air temp.": "air_temperature",
-            "Cl free": "free_chlorine",
-            "Water temp.": "water_temperature",
-        }.get(self._variable.name)
-        if translation_key is not None:
-            self._attr_translation_key = translation_key
-        else:
-            self._attr_name = self._variable.name
-
-        self._attr_unique_id = f"{self._unit.serial_number}{self._variable.type}"
-        self._attr_native_unit_of_measurement = self._variable.unit
-
-        self._attr_icon = {
-            "rx": "mdi:test-tube",
-            "waterLevel": "mdi:waves",
-        }.get(self._variable.type)
-
-        self._attr_device_class = {
-            "airTemp": SensorDeviceClass.TEMPERATURE,
-            "waterTemp": SensorDeviceClass.TEMPERATURE,
-            "ph": SensorDeviceClass.PH,
-        }.get(self._variable.type)
+    entity_description: AsekoSensorEntityDescription
 
     @property
-    def native_value(self) -> int | None:
+    def native_value(self) -> StateType:
         """Return the state of the sensor."""
-        variable = self.coordinator.data[self._variable.type]
-        return variable.current_value
+        return self.entity_description.value_fn(self._unit)
diff --git a/homeassistant/components/aseko_pool_live/strings.json b/homeassistant/components/aseko_pool_live/strings.json
index 7f77b9ec69b4bc..284279292eb390 100644
--- a/homeassistant/components/aseko_pool_live/strings.json
+++ b/homeassistant/components/aseko_pool_live/strings.json
@@ -26,20 +26,26 @@
   },
   "entity": {
     "binary_sensor": {
-      "water_flow": {
-        "name": "Water flow"
+      "water_flow_to_probes": {
+        "name": "Water flow to probes"
       },
-      "alarm": {
-        "name": "Alarm"
+      "heating": {
+        "name": "Heating"
       }
     },
     "sensor": {
       "air_temperature": {
         "name": "Air temperature"
       },
-      "free_chlorine": {
+      "cl_free": {
         "name": "Free chlorine"
       },
+      "redox": {
+        "name": "Redox potential"
+      },
+      "salinity": {
+        "name": "Salinity"
+      },
       "water_temperature": {
         "name": "Water temperature"
       }
diff --git a/requirements_all.txt b/requirements_all.txt
index ee7704f5f46deb..57d3480ac8a53e 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -192,7 +192,7 @@ aioapcaccess==0.4.2
 aioaquacell==0.2.0
 
 # homeassistant.components.aseko_pool_live
-aioaseko==0.2.0
+aioaseko==1.0.0
 
 # homeassistant.components.asuswrt
 aioasuswrt==1.4.0
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index b7e3e897817b39..0605b59859c7c7 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -180,7 +180,7 @@ aioapcaccess==0.4.2
 aioaquacell==0.2.0
 
 # homeassistant.components.aseko_pool_live
-aioaseko==0.2.0
+aioaseko==1.0.0
 
 # homeassistant.components.asuswrt
 aioasuswrt==1.4.0
diff --git a/tests/components/aseko_pool_live/conftest.py b/tests/components/aseko_pool_live/conftest.py
new file mode 100644
index 00000000000000..f3bbddb2cab11e
--- /dev/null
+++ b/tests/components/aseko_pool_live/conftest.py
@@ -0,0 +1,20 @@
+"""Aseko Pool Live conftest."""
+
+from datetime import datetime
+
+from aioaseko import User
+import pytest
+
+
+@pytest.fixture
+def user() -> User:
+    """Aseko User fixture."""
+    return User(
+        user_id="a_user_id",
+        created_at=datetime.now(),
+        updated_at=datetime.now(),
+        name="John",
+        surname="Doe",
+        language="any_language",
+        is_active=True,
+    )
diff --git a/tests/components/aseko_pool_live/test_config_flow.py b/tests/components/aseko_pool_live/test_config_flow.py
index e4dedf36da471b..de1bf0912f82dc 100644
--- a/tests/components/aseko_pool_live/test_config_flow.py
+++ b/tests/components/aseko_pool_live/test_config_flow.py
@@ -2,7 +2,7 @@
 
 from unittest.mock import patch
 
-from aioaseko import AccountInfo, APIUnavailable, InvalidAuthCredentials
+from aioaseko import AsekoAPIError, AsekoInvalidCredentials, User
 import pytest
 
 from homeassistant import config_entries
@@ -23,7 +23,7 @@ async def test_async_step_user_form(hass: HomeAssistant) -> None:
     assert result["errors"] == {}
 
 
-async def test_async_step_user_success(hass: HomeAssistant) -> None:
+async def test_async_step_user_success(hass: HomeAssistant, user: User) -> None:
     """Test we get the form."""
     result = await hass.config_entries.flow.async_init(
         DOMAIN, context={"source": config_entries.SOURCE_USER}
@@ -31,8 +31,8 @@ async def test_async_step_user_success(hass: HomeAssistant) -> None:
 
     with (
         patch(
-            "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login",
-            return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"),
+            "homeassistant.components.aseko_pool_live.config_flow.Aseko.login",
+            return_value=user,
         ),
         patch(
             "homeassistant.components.aseko_pool_live.async_setup_entry",
@@ -60,13 +60,13 @@ async def test_async_step_user_success(hass: HomeAssistant) -> None:
 @pytest.mark.parametrize(
     ("error_web", "reason"),
     [
-        (APIUnavailable, "cannot_connect"),
-        (InvalidAuthCredentials, "invalid_auth"),
+        (AsekoAPIError, "cannot_connect"),
+        (AsekoInvalidCredentials, "invalid_auth"),
         (Exception, "unknown"),
     ],
 )
 async def test_async_step_user_exception(
-    hass: HomeAssistant, error_web: Exception, reason: str
+    hass: HomeAssistant, user: User, error_web: Exception, reason: str
 ) -> None:
     """Test we get the form."""
     result = await hass.config_entries.flow.async_init(
@@ -74,8 +74,8 @@ async def test_async_step_user_exception(
     )
 
     with patch(
-        "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login",
-        return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"),
+        "homeassistant.components.aseko_pool_live.config_flow.Aseko.login",
+        return_value=user,
         side_effect=error_web,
     ):
         result2 = await hass.config_entries.flow.async_configure(
@@ -93,13 +93,13 @@ async def test_async_step_user_exception(
 @pytest.mark.parametrize(
     ("error_web", "reason"),
     [
-        (APIUnavailable, "cannot_connect"),
-        (InvalidAuthCredentials, "invalid_auth"),
+        (AsekoAPIError, "cannot_connect"),
+        (AsekoInvalidCredentials, "invalid_auth"),
         (Exception, "unknown"),
     ],
 )
 async def test_get_account_info_exceptions(
-    hass: HomeAssistant, error_web: Exception, reason: str
+    hass: HomeAssistant, user: User, error_web: Exception, reason: str
 ) -> None:
     """Test we handle config flow exceptions."""
     result = await hass.config_entries.flow.async_init(
@@ -107,8 +107,8 @@ async def test_get_account_info_exceptions(
     )
 
     with patch(
-        "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login",
-        return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"),
+        "homeassistant.components.aseko_pool_live.config_flow.Aseko.login",
+        return_value=user,
         side_effect=error_web,
     ):
         result2 = await hass.config_entries.flow.async_configure(
@@ -123,7 +123,7 @@ async def test_get_account_info_exceptions(
     assert result2["errors"] == {"base": reason}
 
 
-async def test_async_step_reauth_success(hass: HomeAssistant) -> None:
+async def test_async_step_reauth_success(hass: HomeAssistant, user: User) -> None:
     """Test successful reauthentication."""
 
     mock_entry = MockConfigEntry(
@@ -139,10 +139,16 @@ async def test_async_step_reauth_success(hass: HomeAssistant) -> None:
     assert result["step_id"] == "reauth_confirm"
     assert result["errors"] == {}
 
-    with patch(
-        "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login",
-        return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"),
-    ) as mock_setup_entry:
+    with (
+        patch(
+            "homeassistant.components.aseko_pool_live.config_flow.Aseko.login",
+            return_value=user,
+        ),
+        patch(
+            "homeassistant.components.aseko_pool_live.async_setup_entry",
+            return_value=True,
+        ) as mock_setup_entry,
+    ):
         result = await hass.config_entries.flow.async_configure(
             result["flow_id"],
             {CONF_EMAIL: "aseko@example.com", CONF_PASSWORD: "passw0rd"},
@@ -156,13 +162,13 @@ async def test_async_step_reauth_success(hass: HomeAssistant) -> None:
 @pytest.mark.parametrize(
     ("error_web", "reason"),
     [
-        (APIUnavailable, "cannot_connect"),
-        (InvalidAuthCredentials, "invalid_auth"),
+        (AsekoAPIError, "cannot_connect"),
+        (AsekoInvalidCredentials, "invalid_auth"),
         (Exception, "unknown"),
     ],
 )
 async def test_async_step_reauth_exception(
-    hass: HomeAssistant, error_web: Exception, reason: str
+    hass: HomeAssistant, user: User, error_web: Exception, reason: str
 ) -> None:
     """Test we get the form."""
 
@@ -176,8 +182,8 @@ async def test_async_step_reauth_exception(
     result = await mock_entry.start_reauth_flow(hass)
 
     with patch(
-        "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login",
-        return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"),
+        "homeassistant.components.aseko_pool_live.config_flow.Aseko.login",
+        return_value=user,
         side_effect=error_web,
     ):
         result2 = await hass.config_entries.flow.async_configure(


Code review comments:
diff_hunk: @@ -55,33 +47,25 @@ async def async_setup_entry(
     async_add_entities: AddEntitiesCallback,
 ) -> None:
     """Set up the Aseko Pool Live binary sensors."""
-    data: list[tuple[Unit, AsekoDataUpdateCoordinator]] = hass.data[DOMAIN][
+    data: tuple[str, AsekoDataUpdateCoordinator] = hass.data[DOMAIN][
         config_entry.entry_id
     ]
-    async_add_entities(
-        AsekoUnitBinarySensorEntity(unit, coordinator, description)
-        for unit, coordinator in data
-        for description in UNIT_BINARY_SENSORS
-    )
+    user_id, coordinator = data
+    units = coordinator.data.values()
+    for unit in units:
+        async_add_entities(
+            AsekoBinarySensorEntity(unit, user_id, coordinator, description)
+            for description in BINARY_SENSORS
+            if description.value_fn(unit) is not None
+        )
path: homeassistant/components/aseko_pool_live/binary_sensor.py
text: @user1:
```suggestion
    async_add_entities(
        AsekoBinarySensorEntity(unit, user_id, coordinator, description)
        for description in BINARY_SENSORS
        for unit in units
        if description.value_fn(unit) is not None
    )
```

diff_hunk: @@ -14,20 +15,46 @@ class AsekoEntity(CoordinatorEntity[AsekoDataUpdateCoordinator]):
 
     _attr_has_entity_name = True
 
-    def __init__(self, unit: Unit, coordinator: AsekoDataUpdateCoordinator) -> None:
+    def __init__(
+        self,
+        unit: Unit,
+        user_id: str,
+        coordinator: AsekoDataUpdateCoordinator,
+        description: EntityDescription,
+    ) -> None:
         """Initialize the aseko entity."""
         super().__init__(coordinator)
+        self.entity_description = description
         self._unit = unit
-
-        if self._unit.type == "Remote":
-            self._device_model = "ASIN Pool"
-        else:
-            self._device_model = f"ASIN AQUA {self._unit.type}"
-        self._device_name = self._unit.name if self._unit.name else self._device_model
-
+        self._attr_unique_id = (
+            f"{user_id}_{self._unit.serial_number}_{self.entity_description.key}"
+        )
path: homeassistant/components/aseko_pool_live/entity.py
text: @user1:
why do we add the user id now?

@author:
A unit may be connected to multiple accounts/ConfigEntries. The previous implementation returned an error because the intergration did not return unique ids in this rare case. https://discord.com/channels/330944238910963714/1283929610509090837

@author:
Reverted for now. Migration in another PR.

diff_hunk: @@ -14,20 +15,46 @@ class AsekoEntity(CoordinatorEntity[AsekoDataUpdateCoordinator]):
 
     _attr_has_entity_name = True
 
-    def __init__(self, unit: Unit, coordinator: AsekoDataUpdateCoordinator) -> None:
+    def __init__(
+        self,
+        unit: Unit,
+        user_id: str,
+        coordinator: AsekoDataUpdateCoordinator,
+        description: EntityDescription,
+    ) -> None:
         """Initialize the aseko entity."""
         super().__init__(coordinator)
+        self.entity_description = description
         self._unit = unit
-
-        if self._unit.type == "Remote":
-            self._device_model = "ASIN Pool"
-        else:
-            self._device_model = f"ASIN AQUA {self._unit.type}"
-        self._device_name = self._unit.name if self._unit.name else self._device_model
-
+        self._attr_unique_id = (
+            f"{user_id}_{self._unit.serial_number}_{self.entity_description.key}"
+        )
         self._attr_device_info = DeviceInfo(
-            name=self._device_name,
-            identifiers={(DOMAIN, str(self._unit.serial_number))},
-            manufacturer="Aseko",
-            model=self._device_model,
+            identifiers={(DOMAIN, f"{user_id}_{self._unit.serial_number}")},
+            serial_number=self._unit.serial_number,
+            name=(
+                self._unit.name
+                if (self._unit.name is not None and self._unit.name != "")
+                else self._unit.serial_number
+            ),
path: homeassistant/components/aseko_pool_live/entity.py
text: @user1:
```suggestion
            name=unit.name or unit.serial_number,
```

diff_hunk: @@ -14,20 +15,46 @@ class AsekoEntity(CoordinatorEntity[AsekoDataUpdateCoordinator]):
 
     _attr_has_entity_name = True
 
-    def __init__(self, unit: Unit, coordinator: AsekoDataUpdateCoordinator) -> None:
+    def __init__(
+        self,
+        unit: Unit,
+        user_id: str,
+        coordinator: AsekoDataUpdateCoordinator,
+        description: EntityDescription,
+    ) -> None:
         """Initialize the aseko entity."""
         super().__init__(coordinator)
+        self.entity_description = description
         self._unit = unit
-
-        if self._unit.type == "Remote":
-            self._device_model = "ASIN Pool"
-        else:
-            self._device_model = f"ASIN AQUA {self._unit.type}"
-        self._device_name = self._unit.name if self._unit.name else self._device_model
-
+        self._attr_unique_id = (
+            f"{user_id}_{self._unit.serial_number}_{self.entity_description.key}"
+        )
         self._attr_device_info = DeviceInfo(
-            name=self._device_name,
-            identifiers={(DOMAIN, str(self._unit.serial_number))},
-            manufacturer="Aseko",
-            model=self._device_model,
+            identifiers={(DOMAIN, f"{user_id}_{self._unit.serial_number}")},
+            serial_number=self._unit.serial_number,
+            name=(
+                self._unit.name
+                if (self._unit.name is not None and self._unit.name != "")
+                else self._unit.serial_number
+            ),
+            manufacturer=(
+                self._unit.brand_name.primary
+                if self._unit.brand_name is not None
+                else None
+            ),
+            model=(
+                self._unit.brand_name.secondary
+                if self._unit.brand_name is not None
+                else None
+            ),
+            configuration_url=f"https://aseko.cloud/unit/{self._unit.serial_number}",
         )
+
+    def _handle_coordinator_update(self) -> None:
+        self._unit = self.coordinator.data[self._unit.serial_number]
+        return super()._handle_coordinator_update()
path: homeassistant/components/aseko_pool_live/entity.py
text: @user2:
```suggestion
    @user1
    def unit(self) -> Unit:
        return self.coordinator.data[self._unit_serial_number]
```
This way you can always call `self.unit`

diff_hunk: @@ -14,20 +15,46 @@ class AsekoEntity(CoordinatorEntity[AsekoDataUpdateCoordinator]):
 
     _attr_has_entity_name = True
 
-    def __init__(self, unit: Unit, coordinator: AsekoDataUpdateCoordinator) -> None:
+    def __init__(
+        self,
+        unit: Unit,
+        user_id: str,
+        coordinator: AsekoDataUpdateCoordinator,
+        description: EntityDescription,
+    ) -> None:
         """Initialize the aseko entity."""
         super().__init__(coordinator)
+        self.entity_description = description
         self._unit = unit
-
-        if self._unit.type == "Remote":
-            self._device_model = "ASIN Pool"
-        else:
-            self._device_model = f"ASIN AQUA {self._unit.type}"
-        self._device_name = self._unit.name if self._unit.name else self._device_model
-
+        self._attr_unique_id = (
+            f"{user_id}_{self._unit.serial_number}_{self.entity_description.key}"
+        )
         self._attr_device_info = DeviceInfo(
-            name=self._device_name,
-            identifiers={(DOMAIN, str(self._unit.serial_number))},
-            manufacturer="Aseko",
-            model=self._device_model,
+            identifiers={(DOMAIN, f"{user_id}_{self._unit.serial_number}")},
path: homeassistant/components/aseko_pool_live/entity.py
text: @user1:
why do we include the user id now?

@author:
Same as above. In this case the identifier doesn't need to be unique, but I think it's better to keep de devices separate. The device name can differ between account/ConfigEntry and will therefore change depending on which ConfigEntry is loaded first.

@author:
Reverted for now. Migration in another PR.

diff_hunk: @@ -14,20 +15,46 @@ class AsekoEntity(CoordinatorEntity[AsekoDataUpdateCoordinator]):
 
     _attr_has_entity_name = True
 
-    def __init__(self, unit: Unit, coordinator: AsekoDataUpdateCoordinator) -> None:
+    def __init__(
+        self,
+        unit: Unit,
+        user_id: str,
+        coordinator: AsekoDataUpdateCoordinator,
+        description: EntityDescription,
+    ) -> None:
         """Initialize the aseko entity."""
         super().__init__(coordinator)
+        self.entity_description = description
         self._unit = unit
-
-        if self._unit.type == "Remote":
-            self._device_model = "ASIN Pool"
-        else:
-            self._device_model = f"ASIN AQUA {self._unit.type}"
-        self._device_name = self._unit.name if self._unit.name else self._device_model
-
+        self._attr_unique_id = (
+            f"{user_id}_{self._unit.serial_number}_{self.entity_description.key}"
+        )
         self._attr_device_info = DeviceInfo(
-            name=self._device_name,
-            identifiers={(DOMAIN, str(self._unit.serial_number))},
-            manufacturer="Aseko",
-            model=self._device_model,
+            identifiers={(DOMAIN, f"{user_id}_{self._unit.serial_number}")},
+            serial_number=self._unit.serial_number,
+            name=(
+                self._unit.name
+                if (self._unit.name is not None and self._unit.name != "")
+                else self._unit.serial_number
+            ),
+            manufacturer=(
+                self._unit.brand_name.primary
+                if self._unit.brand_name is not None
+                else None
+            ),
+            model=(
+                self._unit.brand_name.secondary
+                if self._unit.brand_name is not None
+                else None
+            ),
+            configuration_url=f"https://aseko.cloud/unit/{self._unit.serial_number}",
         )
+
+    def _handle_coordinator_update(self) -> None:
+        self._unit = self.coordinator.data[self._unit.serial_number]
+        return super()._handle_coordinator_update()
+
+    @property
+    def available(self) -> bool:
+        """Return True if entity is available."""
+        return super().available and self._unit.online
path: homeassistant/components/aseko_pool_live/entity.py
text: @user2:
```suggestion
    @user1
    def available(self) -> bool:
        """Return True if entity is available."""
        return super().available and self._user_serial_number in self.coordinator.data and self.unit.online
```