Compare commits

..

28 Commits

Author SHA1 Message Date
d91b3edb40 Use unique_id instead of mac address for #22 2023-04-15 22:05:02 +02:00
83c5e3479e Add hob support #22 2023-04-15 04:34:12 +02:00
b0cd020941 Bump versions 2023-04-15 00:32:47 +02:00
593842144a Add zip release for download count 2023-04-12 23:59:09 +02:00
9a6e1155f9 Add useful links 2023-04-12 23:58:10 +02:00
365a3af171 Clean up sessions correct, fixes #19 2023-04-12 19:28:04 +02:00
6de6ff375c Bump version to v0.5.0 2023-04-12 02:11:41 +02:00
89d2fd4af1 Read out device data via ui 2023-04-11 22:08:47 +02:00
92add01a59 Bump pyhon version 2023-04-11 01:03:54 +02:00
4a685e67e0 Try to fix login issues #11 2023-04-10 20:35:22 +02:00
6303843116 Add python check action 2023-04-10 19:59:04 +02:00
907bc44533 Reformat with black 2023-04-10 19:51:16 +02:00
4901be4050 Update badges, bump version 2023-04-10 18:50:51 +02:00
c8189414b8 Use pyhon v0.6.1 2023-04-10 17:02:16 +02:00
799ac67d94 Use info.md for hacs 2023-04-10 08:17:27 +02:00
7e9202ef38 New pyhOn version 2023-04-10 07:09:54 +02:00
e1a2af70e9 Bump to v0.4.0 2023-04-08 06:15:33 +02:00
c78aeb1fbb Merge pull request #14 from MiguelAngelLV/oven
Add oven support
2023-04-08 04:46:45 +02:00
67f280512d Merge branch 'main' into oven 2023-04-08 04:45:56 +02:00
6093b57f76 Merge branch 'main' into oven 2023-04-08 04:44:47 +02:00
287e895f4d Merge branch 'main' into oven 2023-04-08 04:33:55 +02:00
d3dc1b9f45 Merge pull request #13 from alexandre-leites/main
Added WD device type sensors.
2023-04-07 20:56:44 +02:00
116f9d5470 Add oven support 2023-04-07 13:52:55 +02:00
05e9d835b1 Added WD device type sensors. 2023-04-05 23:11:19 +02:00
135d6cafed Update to for hacs default integration 2023-04-02 02:44:13 +02:00
077bded6dd Improve washing machine sensors 2023-04-02 02:44:13 +02:00
0d575f65f9 Merge pull request #7 from drudgebg/main
Adding time selection for some of the TD programs
2023-03-22 22:33:19 +01:00
34e888f6d6 Adding time selection for some of the TD programs 2023-03-22 09:25:30 +02:00
17 changed files with 742 additions and 160 deletions

38
.github/workflows/python_check.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: Python check
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pylint black
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics
# - name: Analysing the code with pylint
# run: |
# pylint --max-line-length 88 $(git ls-files '*.py')
- name: Check black style
run: |
black . --check

25
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Release
on:
release:
types: [published]
jobs:
release-zip:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: ZIP Component Dir
run: |
cd ${{ github.workspace }}/custom_components/hon
zip -r haier_hon.zip ./
- name: Upload zip to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ github.workspace }}/custom_components/hon/haier_hon.zip
asset_name: haier_hon.zip
tag: ${{ github.ref }}
overwrite: true

View File

@ -1,33 +1,60 @@
# Haier hOn # Haier hOn
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg)](https://github.com/hacs/integration) [![hacs_badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg)](https://hacs.xyz)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/Andre0512/hon?color=green)](https://github.com/Andre0512/hon/releases/latest)
Home Assistant component supporting devices of Haier's mobile app **hOn**. ![GitHub](https://img.shields.io/github/license/Andre0512/hon?color=red)
[![Home Assistant installs](https://img.shields.io/badge/dynamic/json?color=blue&label=usage&suffix=%20installs&cacheSeconds=15600&url=https://analytics.home-assistant.io/custom_integrations.json&query=$.hon.total)](https://analytics.home-assistant.io/)
Home Assistant integration for Haier hOn: support for Haier/Candy/Hoover home appliances like washing machines.
## Supported Appliances ## Supported Appliances
- Washing Machine
- Tumble Dryer - Tumble Dryer
- Washer Dryer
- Washing Machine
- Oven
- Hob
## Installation ## Installation
#### Installing via HACS **Method 1:** [![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=Andre0512&repository=hon&category=integration)
1. You need to have installed [HACS](https://hacs.xyz/)
2. Go to HACS->Integrations **Method 2:** [HACS](https://hacs.xyz/) > Integrations > Add Integration > **Haier hOn** > Install
3. Add this repo (`https://github.com/Andre0512/hon.git`) into your HACS custom repositories
4. Search for Haier hOn and Download it **Method 3:** Manually copy `hon` folder from [latest release](https://github.com/Andre0512/hon/releases/latest) to `config/custom_components` folder.
5. Restart your HomeAssistant
6. Go to Settings->Devices & Services _Restart Home Assistant_
7. Shift reload your browser
8. Click Add Integration ## Configuration
9. Search for Haier hOn
10. Type your username used in the hOn App and hit submit **Method 1**: [![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=hon)
**Method 2**: Settings > Devices & Services > Add Integration > **Haier hOn**
_If the integration is not in the list, you need to clear the browser cache._
## Contribute ## Contribute
Any kind of contribution is welcome! Any kind of contribution is welcome!
#### Add appliances or additional attributes ### Read out device data
If you want to make a request for adding new appliances or additional attributes and don't want to use the command line, here is how you can read out your device data.
For every device exists a hidden button which can be used to log all info of your appliance.
1. Enable the "Log Device Info" button
_This button can be found in the diagnostic section of your device or in the entity overview if "show disabled entities" is enabled._
2. Press the button
3. Go to Settings > System > Logs, click _load full logs_ and scroll down
_The formatting is messy if you not load full logs_
4. Here you can find all data which can be read out via the api
```yaml
data:
appliance:
applianceId: 12-34-56-78-90-ab#2022-10-25T19:47:11Z
applianceModelId: 1569
...
```
5. Copy this data and create a [new issue](https://github.com/Andre0512/hon/issues/new) with your request
### Add appliances or additional attributes
1. Install [pyhOn](https://github.com/Andre0512/pyhOn) 1. Install [pyhOn](https://github.com/Andre0512/pyhOn)
```commandline ```commandline
$ pip install pyhOn $ pip install pyhOn
``` ```
2. Use the commandline tool to read out all appliance data from your account 2. Use the command line tool to read out all appliance data from your account
```commandline ```commandline
$ pyhOn $ pyhOn
User for hOn account: user.name@example.com User for hOn account: user.name@example.com
@ -62,12 +89,15 @@ Any kind of contribution is welcome!
5. Create a [pull request](https://github.com/Andre0512/hon/pulls) 5. Create a [pull request](https://github.com/Andre0512/hon/pulls)
#### Tips and Tricks #### Tips and Tricks
- If you want to have some states humanreadable, have a look at the `translation_key` parameter of the `EntityDescription` - If you want to have some states humanreadable, have a look at the `translation_key` parameter of the `EntityDescription`.
- If you need to implement some more logic, create a pull request to the underlying library. There we collect special requirements in the `appliances` directory. - If you need to implement some more logic, create a pull request to the underlying library. There we collect special requirements in the `appliances` directory.
- Use [pyhOn's translate command](https://github.com/Andre0512/pyhOn#translation) to read out the official translations
## Tested Devices ## Tested Devices
- Haier WD90-B14TEAM5 - Haier WD90-B14TEAM5
- Haier HD80-A3959 - Haier HD80-A3959
- Haier HWO60SM2F3XH
- Hoover H-WASH 500
## About this Repo ## About this Repo
The existing integrations missed some features from the app I liked to have in HomeAssistant. The existing integrations missed some features from the app I liked to have in HomeAssistant.

View File

@ -1,7 +1,7 @@
import logging import logging
import voluptuous as vol import voluptuous as vol
from pyhon import HonConnection from pyhon import Hon
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
@ -28,8 +28,9 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
session = aiohttp_client.async_get_clientsession(hass) session = aiohttp_client.async_get_clientsession(hass)
hon = HonConnection(entry.data["email"], entry.data["password"], session) hon = await Hon(
await hon.setup() entry.data["email"], entry.data["password"], session=session
).create()
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.unique_id] = hon hass.data[DOMAIN][entry.unique_id] = hon
hass.data[DOMAIN]["coordinators"] = {} hass.data[DOMAIN]["coordinators"] = {}

View File

@ -1,10 +1,13 @@
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from pyhon import HonConnection from pyhon import Hon
from homeassistant.components.binary_sensor import BinarySensorEntityDescription, BinarySensorDeviceClass, \ from homeassistant.components.binary_sensor import (
BinarySensorEntity BinarySensorEntityDescription,
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback from homeassistant.core import callback
from .const import DOMAIN from .const import DOMAIN
@ -19,7 +22,9 @@ class HonBinarySensorEntityDescriptionMixin:
@dataclass @dataclass
class HonBinarySensorEntityDescription(HonBinarySensorEntityDescriptionMixin, BinarySensorEntityDescription): class HonBinarySensorEntityDescription(
HonBinarySensorEntityDescriptionMixin, BinarySensorEntityDescription
):
pass pass
@ -27,15 +32,22 @@ BINARY_SENSORS: dict[str, tuple[HonBinarySensorEntityDescription, ...]] = {
"WM": ( "WM": (
HonBinarySensorEntityDescription( HonBinarySensorEntityDescription(
key="attributes.lastConnEvent.category", key="attributes.lastConnEvent.category",
name="Connection", name="Remote Control",
device_class=BinarySensorDeviceClass.CONNECTIVITY, device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED", on_value="CONNECTED",
icon="mdi:remote",
), ),
HonBinarySensorEntityDescription( HonBinarySensorEntityDescription(
key="doorLockStatus", key="doorLockStatus",
name="Door Lock",
device_class=BinarySensorDeviceClass.LOCK,
on_value="0",
),
HonBinarySensorEntityDescription(
key="doorStatus",
name="Door", name="Door",
device_class=BinarySensorDeviceClass.DOOR, device_class=BinarySensorDeviceClass.DOOR,
on_value="0", on_value="1",
), ),
), ),
"TD": ( "TD": (
@ -51,29 +63,130 @@ BINARY_SENSORS: dict[str, tuple[HonBinarySensorEntityDescription, ...]] = {
device_class=BinarySensorDeviceClass.DOOR, device_class=BinarySensorDeviceClass.DOOR,
on_value="1", on_value="1",
), ),
) ),
"WD": (
HonBinarySensorEntityDescription(
key="attributes.lastConnEvent.category",
name="Remote Control",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED",
icon="mdi:remote",
),
HonBinarySensorEntityDescription(
key="startProgram.prewash",
name="Pre Wash",
),
HonBinarySensorEntityDescription(
key="extraRinse1",
name="Extra Rinse 1",
),
HonBinarySensorEntityDescription(
key="extraRinse2",
name="Extra Rinse 2",
),
HonBinarySensorEntityDescription(
key="extraRinse3",
name="Extra Rinse 3",
),
HonBinarySensorEntityDescription(
key="goodNight",
name="Good Night Mode",
),
HonBinarySensorEntityDescription(
key="acquaplus",
name="Acqua Plus",
),
HonBinarySensorEntityDescription(
key="anticrease",
name="Anti-Crease",
),
),
"OV": (
HonBinarySensorEntityDescription(
key="attributes.lastConnEvent.category",
name="Connection",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED",
icon="mdi:wifi",
),
HonBinarySensorEntityDescription(
key="attributes.parameters.remoteCtrValid",
name="Remote Control",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="1",
icon="mdi:remote",
),
HonBinarySensorEntityDescription(
key="attributes.parameters.onOffStatus",
name="On",
device_class=BinarySensorDeviceClass.RUNNING,
on_value="1",
icon="mdi:power-cycle",
),
),
"IV": (
HonBinarySensorEntityDescription(
key="attributes.lastConnEvent.category",
name="Connection",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED",
icon="mdi:wifi",
),
HonBinarySensorEntityDescription(
key="attributes.parameters.remoteCtrValid",
name="Remote Control",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="1",
icon="mdi:remote",
),
HonBinarySensorEntityDescription(
key="attributes.parameters.onOffStatus",
name="On",
device_class=BinarySensorDeviceClass.RUNNING,
on_value="1",
icon="mdi:power-cycle",
),
HonBinarySensorEntityDescription(
key="hotStatus",
name="Hot Status",
device_class=BinarySensorDeviceClass.HEAT,
on_value="1",
),
HonBinarySensorEntityDescription(
key="hobLockStatus",
name="Hob Lock",
device_class=BinarySensorDeviceClass.LOCK,
on_value="0",
),
),
} }
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None: async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id] hon: Hon = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"] coordinators = hass.data[DOMAIN]["coordinators"]
appliances = [] appliances = []
for device in hon.devices: for device in hon.appliances:
if device.mac_address in coordinators: if device.unique_id in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address] coordinator = hass.data[DOMAIN]["coordinators"][device.unique_id]
else: else:
coordinator = HonCoordinator(hass, device) coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator hass.data[DOMAIN]["coordinators"][device.unique_id] = coordinator
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
if descriptions := BINARY_SENSORS.get(device.appliance_type): if descriptions := BINARY_SENSORS.get(device.appliance_type):
for description in descriptions: for description in descriptions:
if not device.get(description.key): if not device.get(description.key):
_LOGGER.info("Can't setup %s", description.key) _LOGGER.warning(
"[%s] Can't setup %s", device.appliance_type, description.key
)
continue continue
appliances.extend([ appliances.extend(
HonBinarySensorEntity(hass, coordinator, entry, device, description)] [
HonBinarySensorEntity(
hass, coordinator, entry, device, description
)
]
) )
async_add_entities(appliances) async_add_entities(appliances)
@ -92,9 +205,15 @@ class HonBinarySensorEntity(HonEntity, BinarySensorEntity):
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
return self._device.get(self.entity_description.key, "") == self.entity_description.on_value return (
self._device.get(self.entity_description.key, "")
== self.entity_description.on_value
)
@callback @callback
def _handle_coordinator_update(self): def _handle_coordinator_update(self):
self._attr_native_value = self._device.get(self.entity_description.key, "") == self.entity_description.on_value self._attr_native_value = (
self._device.get(self.entity_description.key, "")
== self.entity_description.on_value
)
self.async_write_ha_state() self.async_write_ha_state()

View File

@ -1,53 +1,69 @@
from pyhon import HonConnection import logging
from pyhon.device import HonDevice import urllib
from urllib.parse import quote
from homeassistant.components.button import ButtonEntityDescription, ButtonEntity from homeassistant.components.button import ButtonEntityDescription, ButtonEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from pyhon import Hon
from pyhon.appliance import HonAppliance
from homeassistant.const import EntityCategory
from .const import DOMAIN from .const import DOMAIN
from .hon import HonCoordinator, HonEntity from .hon import HonCoordinator, HonEntity
_LOGGER = logging.getLogger(__name__)
BUTTONS: dict[str, tuple[ButtonEntityDescription, ...]] = { BUTTONS: dict[str, tuple[ButtonEntityDescription, ...]] = {
"WM": ( "OV": (
# ButtonEntityDescription( ButtonEntityDescription(
# key="pauseProgram", key="startProgram",
# name="Pause Program", name="Start Program",
# icon="mdi:pause", icon="mdi:power-cycle",
# ), ),
# ButtonEntityDescription( ButtonEntityDescription(
# key="resumeProgram", key="stopProgram",
# name="Resume Program", name="Stop Program",
# icon="mdi:play-pause", icon="mdi:power-off",
# ), ),
),
"IV": (
ButtonEntityDescription(
key="startProgram",
name="Start Program",
icon="mdi:pot-steam",
),
), ),
} }
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None: async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id] hon: Hon = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"] coordinators = hass.data[DOMAIN]["coordinators"]
appliances = [] appliances = []
for device in hon.devices: for device in hon.appliances:
if device.mac_address in coordinators: if device.unique_id in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address] coordinator = hass.data[DOMAIN]["coordinators"][device.unique_id]
else: else:
coordinator = HonCoordinator(hass, device) coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator hass.data[DOMAIN]["coordinators"][device.unique_id] = coordinator
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
if descriptions := BUTTONS.get(device.appliance_type): if descriptions := BUTTONS.get(device.appliance_type):
for description in descriptions: for description in descriptions:
if not device.commands.get(description.key): if not device.commands.get(description.key):
continue continue
appliances.extend([ appliances.extend(
HonButtonEntity(hass, coordinator, entry, device, description)] [HonButtonEntity(hass, coordinator, entry, device, description)]
) )
appliances.extend([HonFeatureRequestButton(hass, coordinator, entry, device)])
async_add_entities(appliances) async_add_entities(appliances)
class HonButtonEntity(HonEntity, ButtonEntity): class HonButtonEntity(HonEntity, ButtonEntity):
def __init__(self, hass, coordinator, entry, device: HonDevice, description) -> None: def __init__(
self, hass, coordinator, entry, device: HonAppliance, description
) -> None:
super().__init__(hass, entry, coordinator, device) super().__init__(hass, entry, coordinator, device)
self._coordinator = coordinator self._coordinator = coordinator
@ -57,3 +73,18 @@ class HonButtonEntity(HonEntity, ButtonEntity):
async def async_press(self) -> None: async def async_press(self) -> None:
await self._device.commands[self.entity_description.key].send() await self._device.commands[self.entity_description.key].send()
class HonFeatureRequestButton(HonEntity, ButtonEntity):
def __init__(self, hass, coordinator, entry, device: HonAppliance) -> None:
super().__init__(hass, entry, coordinator, device)
self._device = device
self._attr_unique_id = f"{super().unique_id}_log_device_info"
self._attr_icon = "mdi:information"
self._attr_name = "Log Device Info"
self._attr_entity_category = EntityCategory.DIAGNOSTIC
self._attr_entity_registry_enabled_default = False
async def async_press(self) -> None:
_LOGGER.error("Device Info:\n" + self._device.diagnose)

View File

@ -20,8 +20,12 @@ class HonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
if user_input is None: if user_input is None:
return self.async_show_form(step_id="user", data_schema=vol.Schema( return self.async_show_form(
{vol.Required(CONF_EMAIL): str, vol.Required(CONF_PASSWORD): str})) step_id="user",
data_schema=vol.Schema(
{vol.Required(CONF_EMAIL): str, vol.Required(CONF_PASSWORD): str}
),
)
self._email = user_input[CONF_EMAIL] self._email = user_input[CONF_EMAIL]
self._password = user_input[CONF_PASSWORD] self._password = user_input[CONF_PASSWORD]

View File

@ -1,7 +1,7 @@
import logging import logging
from datetime import timedelta from datetime import timedelta
from pyhon.device import HonDevice from pyhon.appliance import HonAppliance
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -15,30 +15,37 @@ _LOGGER = logging.getLogger(__name__)
class HonEntity(CoordinatorEntity): class HonEntity(CoordinatorEntity):
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__(self, hass, entry, coordinator, device: HonDevice) -> None: def __init__(self, hass, entry, coordinator, device: HonAppliance) -> None:
super().__init__(coordinator) super().__init__(coordinator)
self._hon = hass.data[DOMAIN][entry.unique_id] self._hon = hass.data[DOMAIN][entry.unique_id]
self._hass = hass self._hass = hass
self._device = device self._device = device
self._attr_unique_id = self._device.mac_address self._attr_unique_id = self._device.unique_id
@property @property
def device_info(self): def device_info(self):
return DeviceInfo( return DeviceInfo(
identifiers={(DOMAIN, self._device.mac_address)}, identifiers={(DOMAIN, self._device.unique_id)},
manufacturer=self._device.get("brand", ""), manufacturer=self._device.get("brand", ""),
name=self._device.nick_name if self._device.nick_name else self._device.model_name, name=self._device.nick_name
if self._device.nick_name
else self._device.model_name,
model=self._device.model_name, model=self._device.model_name,
sw_version=self._device.get("fwVersion", ""), sw_version=self._device.get("fwVersion", ""),
) )
class HonCoordinator(DataUpdateCoordinator): class HonCoordinator(DataUpdateCoordinator):
def __init__(self, hass, device: HonDevice): def __init__(self, hass, device: HonAppliance):
"""Initialize my coordinator.""" """Initialize my coordinator."""
super().__init__(hass, _LOGGER, name=device.mac_address, update_interval=timedelta(seconds=30)) super().__init__(
hass,
_LOGGER,
name=device.unique_id,
update_interval=timedelta(seconds=30),
)
self._device = device self._device = device
async def _async_update_data(self): async def _async_update_data(self):

View File

@ -6,7 +6,6 @@
"documentation": "https://github.com/Andre0512/hon/", "documentation": "https://github.com/Andre0512/hon/",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"issue_tracker": "https://github.com/Andre0512/hon/issues", "issue_tracker": "https://github.com/Andre0512/hon/issues",
"requirements": ["pyhOn==0.4.1"], "requirements": ["pyhOn==0.8.0b3"],
"version": "0.3.0" "version": "0.6.0-beta.2"
} }

View File

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from pyhon import HonConnection from pyhon import Hon
from pyhon.parameter import HonParameterRange from pyhon.parameter import HonParameterRange
from homeassistant.components.number import ( from homeassistant.components.number import (
@ -8,7 +8,7 @@ from homeassistant.components.number import (
NumberEntityDescription, NumberEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTime from homeassistant.const import UnitOfTime, UnitOfTemperature
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
@ -22,20 +22,20 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = {
name="Delay Time", name="Delay Time",
icon="mdi:timer-plus", icon="mdi:timer-plus",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTime.MINUTES native_unit_of_measurement=UnitOfTime.MINUTES,
), ),
NumberEntityDescription( NumberEntityDescription(
key="startProgram.rinseIterations", key="startProgram.rinseIterations",
name="Rinse Iterations", name="Rinse Iterations",
icon="mdi:rotate-right", icon="mdi:rotate-right",
entity_category=EntityCategory.CONFIG entity_category=EntityCategory.CONFIG,
), ),
NumberEntityDescription( NumberEntityDescription(
key="startProgram.mainWashTime", key="startProgram.mainWashTime",
name="Main Wash Time", name="Main Wash Time",
icon="mdi:clock-start", icon="mdi:clock-start",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTime.MINUTES native_unit_of_measurement=UnitOfTime.MINUTES,
), ),
), ),
"TD": ( "TD": (
@ -44,55 +44,103 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = {
name="Delay time", name="Delay time",
icon="mdi:timer-plus", icon="mdi:timer-plus",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTime.MINUTES native_unit_of_measurement=UnitOfTime.MINUTES,
), ),
NumberEntityDescription( NumberEntityDescription(
key="startProgram.dryLevel", key="startProgram.dryLevel",
name="Dry level", name="Dry level",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
icon="mdi:hair-dryer", icon="mdi:hair-dryer",
translation_key="tumbledryerdrylevel" translation_key="tumbledryerdrylevel",
), ),
NumberEntityDescription( NumberEntityDescription(
key="startProgram.tempLevel", key="startProgram.tempLevel",
name="Temperature level", name="Temperature level",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
icon="mdi:thermometer", icon="mdi:thermometer",
translation_key="tumbledryertemplevel" translation_key="tumbledryertemplevel",
), ),
NumberEntityDescription( NumberEntityDescription(
key="startProgram.antiCreaseTime", key="startProgram.antiCreaseTime",
name="Anti-Crease time", name="Anti-Crease time",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
icon="mdi:timer", icon="mdi:timer",
native_unit_of_measurement=UnitOfTime.MINUTES native_unit_of_measurement=UnitOfTime.MINUTES,
), ),
NumberEntityDescription( NumberEntityDescription(
key="startProgram.sterilizationStatus", key="startProgram.sterilizationStatus",
name="Sterilization status", name="Sterilization status",
icon="mdi:clock-start", icon="mdi:clock-start",
entity_category=EntityCategory.CONFIG entity_category=EntityCategory.CONFIG,
),
),
"WD": (
NumberEntityDescription(
key="startProgram.delayTime",
name="Delay Time",
icon="mdi:timer-plus",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTime.MINUTES,
),
),
"OV": (
NumberEntityDescription(
key="startProgram.delayTime",
name="Delay time",
icon="mdi:timer-plus",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTime.MINUTES,
),
NumberEntityDescription(
key="startProgram.tempSel",
name="Target Temperature",
entity_category=EntityCategory.CONFIG,
icon="mdi:thermometer",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
NumberEntityDescription(
key="startProgram.prTime",
name="Program Duration",
entity_category=EntityCategory.CONFIG,
icon="mdi:timelapse",
native_unit_of_measurement=UnitOfTime.MINUTES,
),
),
"IV": (
NumberEntityDescription(
key="startProgram.temp",
name="Temperature",
entity_category=EntityCategory.CONFIG,
icon="mdi:thermometer",
),
NumberEntityDescription(
key="startProgram.powerManagement",
name="Power Management",
entity_category=EntityCategory.CONFIG,
icon="mdi:timelapse",
), ),
), ),
} }
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None: async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id] hon: Hon = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"] coordinators = hass.data[DOMAIN]["coordinators"]
appliances = [] appliances = []
for device in hon.devices: for device in hon.appliances:
if device.mac_address in coordinators: if device.unique_id in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address] coordinator = hass.data[DOMAIN]["coordinators"][device.unique_id]
else: else:
coordinator = HonCoordinator(hass, device) coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator hass.data[DOMAIN]["coordinators"][device.unique_id] = coordinator
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
if descriptions := NUMBERS.get(device.appliance_type): if descriptions := NUMBERS.get(device.appliance_type):
for description in descriptions: for description in descriptions:
appliances.extend([ if not device.settings.get(description.key):
HonNumberEntity(hass, coordinator, entry, device, description)] continue
appliances.extend(
[HonNumberEntity(hass, coordinator, entry, device, description)]
) )
async_add_entities(appliances) async_add_entities(appliances)

View File

@ -1,18 +1,22 @@
from __future__ import annotations from __future__ import annotations
from pyhon import HonConnection import logging
from pyhon.device import HonDevice
from pyhon import Hon
from pyhon.appliance import HonAppliance
from pyhon.parameter import HonParameterFixed from pyhon.parameter import HonParameterFixed
from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTemperature, REVOLUTIONS_PER_MINUTE from homeassistant.const import UnitOfTemperature, UnitOfTime, REVOLUTIONS_PER_MINUTE
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from .const import DOMAIN from .const import DOMAIN
from .hon import HonEntity, HonCoordinator from .hon import HonEntity, HonCoordinator
_LOGGER = logging.getLogger(__name__)
SELECTS = { SELECTS = {
"WM": ( "WM": (
SelectEntityDescription( SelectEntityDescription(
@ -20,20 +24,20 @@ SELECTS = {
name="Spin speed", name="Spin speed",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
icon="mdi:numeric", icon="mdi:numeric",
unit_of_measurement=REVOLUTIONS_PER_MINUTE unit_of_measurement=REVOLUTIONS_PER_MINUTE,
), ),
SelectEntityDescription( SelectEntityDescription(
key="startProgram.temp", key="startProgram.temp",
name="Temperature", name="Temperature",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
icon="mdi:thermometer", icon="mdi:thermometer",
unit_of_measurement=UnitOfTemperature.CELSIUS unit_of_measurement=UnitOfTemperature.CELSIUS,
), ),
SelectEntityDescription( SelectEntityDescription(
key="startProgram.program", key="startProgram.program",
name="Program", name="Program",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
translation_key="programs" translation_key="programs",
), ),
), ),
"TD": ( "TD": (
@ -41,36 +45,74 @@ SELECTS = {
key="startProgram.program", key="startProgram.program",
name="Program", name="Program",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
translation_key="programs" translation_key="programs",
), ),
) SelectEntityDescription(
key="startProgram.dryTimeMM",
name="Time",
entity_category=EntityCategory.CONFIG,
icon="mdi:timer",
unit_of_measurement=UnitOfTime.MINUTES,
),
),
"WD": (
SelectEntityDescription(
key="startProgram.program",
name="Program",
entity_category=EntityCategory.CONFIG,
translation_key="programs",
),
),
"OV": (
SelectEntityDescription(
key="startProgram.program",
name="Program",
entity_category=EntityCategory.CONFIG,
translation_key="programs",
),
SelectEntityDescription(
key="startProgram.preheatStatus",
name="Preheat",
entity_category=EntityCategory.CONFIG,
),
),
"IV": (
SelectEntityDescription(
key="startProgram.program",
name="Program",
entity_category=EntityCategory.CONFIG,
translation_key="programs",
),
),
} }
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None: async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id] hon: Hon = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"] coordinators = hass.data[DOMAIN]["coordinators"]
appliances = [] appliances = []
for device in hon.devices: for device in hon.appliances:
if device.mac_address in coordinators: if device.unique_id in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address] coordinator = hass.data[DOMAIN]["coordinators"][device.unique_id]
else: else:
coordinator = HonCoordinator(hass, device) coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator hass.data[DOMAIN]["coordinators"][device.unique_id] = coordinator
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
if descriptions := SELECTS.get(device.appliance_type): if descriptions := SELECTS.get(device.appliance_type):
for description in descriptions: for description in descriptions:
if not device.get(description.key): if not device.settings.get(description.key):
continue continue
appliances.extend([ appliances.extend(
HonSelectEntity(hass, coordinator, entry, device, description)] [HonSelectEntity(hass, coordinator, entry, device, description)]
) )
async_add_entities(appliances) async_add_entities(appliances)
class HonSelectEntity(HonEntity, SelectEntity): class HonSelectEntity(HonEntity, SelectEntity):
def __init__(self, hass, coordinator, entry, device: HonDevice, description) -> None: def __init__(
self, hass, coordinator, entry, device: HonAppliance, description
) -> None:
super().__init__(hass, entry, coordinator, device) super().__init__(hass, entry, coordinator, device)
self._coordinator = coordinator self._coordinator = coordinator
@ -97,7 +139,9 @@ class HonSelectEntity(HonEntity, SelectEntity):
@callback @callback
def _handle_coordinator_update(self): def _handle_coordinator_update(self):
setting = self._device.settings[self.entity_description.key] setting = self._device.settings[self.entity_description.key]
if not isinstance(self._device.settings[self.entity_description.key], HonParameterFixed): if not isinstance(
self._device.settings[self.entity_description.key], HonParameterFixed
):
self._attr_options: list[str] = setting.values self._attr_options: list[str] = setting.values
else: else:
self._attr_options = [setting.value] self._attr_options = [setting.value]

View File

@ -1,6 +1,6 @@
import logging import logging
from pyhon import HonConnection from pyhon import Hon
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorEntity, SensorEntity,
@ -9,7 +9,15 @@ from homeassistant.components.sensor import (
SensorEntityDescription, SensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfEnergy, UnitOfVolume, UnitOfMass, UnitOfPower, UnitOfTime from homeassistant.const import (
REVOLUTIONS_PER_MINUTE,
UnitOfEnergy,
UnitOfVolume,
UnitOfMass,
UnitOfPower,
UnitOfTime,
UnitOfTemperature,
)
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
@ -26,20 +34,20 @@ SENSORS: dict[str, tuple[SensorEntityDescription, ...]] = {
name="Total Power", name="Total Power",
device_class=SensorDeviceClass.ENERGY, device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
), ),
SensorEntityDescription( SensorEntityDescription(
key="totalWaterUsed", key="totalWaterUsed",
name="Total Water", name="Total Water",
device_class=SensorDeviceClass.WATER, device_class=SensorDeviceClass.WATER,
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfVolume.LITERS native_unit_of_measurement=UnitOfVolume.LITERS,
), ),
SensorEntityDescription( SensorEntityDescription(
key="totalWashCycle", key="totalWashCycle",
name="Total Wash Cycle", name="Total Wash Cycle",
state_class=SensorStateClass.TOTAL_INCREASING, state_class=SensorStateClass.TOTAL_INCREASING,
icon="mdi:counter" icon="mdi:counter",
), ),
SensorEntityDescription( SensorEntityDescription(
key="currentElectricityUsed", key="currentElectricityUsed",
@ -47,13 +55,13 @@ SENSORS: dict[str, tuple[SensorEntityDescription, ...]] = {
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.KILO_WATT, native_unit_of_measurement=UnitOfPower.KILO_WATT,
icon="mdi:lightning-bolt" icon="mdi:lightning-bolt",
), ),
SensorEntityDescription( SensorEntityDescription(
key="currentWaterUsed", key="currentWaterUsed",
name="Current Water Used", name="Current Water Used",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
icon="mdi:water" icon="mdi:water",
), ),
SensorEntityDescription( SensorEntityDescription(
key="startProgram.weight", key="startProgram.weight",
@ -61,19 +69,16 @@ SENSORS: dict[str, tuple[SensorEntityDescription, ...]] = {
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfMass.KILOGRAMS, native_unit_of_measurement=UnitOfMass.KILOGRAMS,
icon="mdi:weight-kilogram" icon="mdi:weight-kilogram",
), ),
SensorEntityDescription( SensorEntityDescription(
key="machMode", key="machMode",
name="Machine Status", name="Machine Status",
icon="mdi:information", icon="mdi:information",
translation_key="mode" translation_key="mode",
), ),
SensorEntityDescription( SensorEntityDescription(
key="errors", key="errors", name="Error", icon="mdi:math-log", translation_key="errors"
name="Error",
icon="mdi:math-log",
translation_key="errors"
), ),
SensorEntityDescription( SensorEntityDescription(
key="remainingTimeMM", key="remainingTimeMM",
@ -85,7 +90,7 @@ SENSORS: dict[str, tuple[SensorEntityDescription, ...]] = {
SensorEntityDescription( SensorEntityDescription(
key="spinSpeed", key="spinSpeed",
name="Spin Speed", name="Spin Speed",
icon="mdi:timer", icon="mdi:speedometer",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
), ),
@ -95,13 +100,10 @@ SENSORS: dict[str, tuple[SensorEntityDescription, ...]] = {
key="machMode", key="machMode",
name="Machine Status", name="Machine Status",
icon="mdi:information", icon="mdi:information",
translation_key="mode" translation_key="mode",
), ),
SensorEntityDescription( SensorEntityDescription(
key="errors", key="errors", name="Error", icon="mdi:math-log", translation_key="errors"
name="Error",
icon="mdi:math-log",
translation_key="errors"
), ),
SensorEntityDescription( SensorEntityDescription(
key="remainingTimeMM", key="remainingTimeMM",
@ -121,48 +123,144 @@ SENSORS: dict[str, tuple[SensorEntityDescription, ...]] = {
key="prCode", key="prCode",
name="Program", name="Program",
icon="mdi:tumble-dryer", icon="mdi:tumble-dryer",
translation_key="tumbledryerprogram" translation_key="tumbledryerprogram",
), ),
SensorEntityDescription( SensorEntityDescription(
key="prPhase", key="prPhase",
name="Program Phase", name="Program Phase",
icon="mdi:tumble-dryer", icon="mdi:tumble-dryer",
translation_key="tumbledryerprogramphase" translation_key="tumbledryerprogramphase",
), ),
SensorEntityDescription( SensorEntityDescription(
key="dryLevel", key="dryLevel",
name="Dry level", name="Dry level",
icon="mdi:hair-dryer", icon="mdi:hair-dryer",
translation_key="tumbledryerdrylevel" translation_key="tumbledryerdrylevel",
), ),
SensorEntityDescription( SensorEntityDescription(
key="tempLevel", key="tempLevel",
name="Temperature level", name="Temperature level",
icon="mdi:thermometer", icon="mdi:thermometer",
translation_key="tumbledryertemplevel" translation_key="tumbledryertemplevel",
), ),
) ),
"WD": (
SensorEntityDescription(
key="machMode",
name="Machine Status",
icon="mdi:information",
translation_key="mode",
),
SensorEntityDescription(
key="spinSpeed",
name="Spin Speed",
icon="mdi:fast-forward-outline",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
),
SensorEntityDescription(
key="remainingTimeMM",
name="Remaining Time",
icon="mdi:timer",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTime.MINUTES,
),
SensorEntityDescription(
key="prCode",
name="Current Program",
icon="mdi:tumble-dryer",
),
SensorEntityDescription(
key="prPhase",
name="Program Phase",
icon="mdi:tumble-dryer",
),
SensorEntityDescription(
key="dryLevel",
name="Dry level",
icon="mdi:hair-dryer",
),
SensorEntityDescription(
key="dirtyLevel",
name="Dirt level",
icon="mdi:liquid-spot",
),
SensorEntityDescription(
key="steamLevel",
name="Steam level",
icon="mdi:smoke",
),
SensorEntityDescription(
key="temp",
name="Current Temperature",
icon="mdi:thermometer",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
),
"OV": (
SensorEntityDescription(
key="remainingTimeMM",
name="Remaining Time",
icon="mdi:timer",
native_unit_of_measurement=UnitOfTime.MINUTES,
),
SensorEntityDescription(
key="delayTime",
name="Start Time",
icon="mdi:clock-start",
),
SensorEntityDescription(
key="temp",
name="Temperature",
icon="mdi:thermometer",
),
SensorEntityDescription(
key="tempSel",
name="Temperature Selected",
icon="mdi:thermometer",
),
),
"IV": (
SensorEntityDescription(
key="remainingTimeMM",
name="Remaining Time",
icon="mdi:timer",
native_unit_of_measurement=UnitOfTime.MINUTES,
),
SensorEntityDescription(
key="temp",
name="Temperature",
icon="mdi:thermometer",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
SensorEntityDescription(key="errors", name="Error", icon="mdi:math-log"),
),
} }
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None: async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id] hon: Hon = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"] coordinators = hass.data[DOMAIN]["coordinators"]
appliances = [] appliances = []
for device in hon.devices: for device in hon.appliances:
if device.mac_address in coordinators: if device.unique_id in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address] coordinator = hass.data[DOMAIN]["coordinators"][device.unique_id]
else: else:
coordinator = HonCoordinator(hass, device) coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator hass.data[DOMAIN]["coordinators"][device.unique_id] = coordinator
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
if descriptions := SENSORS.get(device.appliance_type): if descriptions := SENSORS.get(device.appliance_type):
for description in descriptions: for description in descriptions:
if not device.get(description.key): if not device.get(description.key):
_LOGGER.warning(
"[%s] Can't setup %s", device.appliance_type, description.key
)
continue continue
appliances.extend([ appliances.extend(
HonSensorEntity(hass, coordinator, entry, device, description)] [HonSensorEntity(hass, coordinator, entry, device, description)]
) )
async_add_entities(appliances) async_add_entities(appliances)

View File

@ -1,15 +1,19 @@
import logging
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from homeassistant.components.switch import SwitchEntityDescription, SwitchEntity from homeassistant.components.switch import SwitchEntityDescription, SwitchEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory from homeassistant.const import EntityCategory
from pyhon import HonConnection from pyhon import Hon
from pyhon.device import HonDevice from pyhon.appliance import HonAppliance
from .const import DOMAIN from .const import DOMAIN
from .hon import HonCoordinator, HonEntity from .hon import HonCoordinator, HonEntity
_LOGGER = logging.getLogger(__name__)
@dataclass @dataclass
class HonSwitchEntityDescriptionMixin: class HonSwitchEntityDescriptionMixin:
@ -18,8 +22,8 @@ class HonSwitchEntityDescriptionMixin:
@dataclass @dataclass
class HonSwitchEntityDescription(HonSwitchEntityDescriptionMixin, class HonSwitchEntityDescription(
SwitchEntityDescription HonSwitchEntityDescriptionMixin, SwitchEntityDescription
): ):
pass pass
@ -44,13 +48,13 @@ SWITCHES: dict[str, tuple[HonSwitchEntityDescription, ...]] = {
key="startProgram.delayStatus", key="startProgram.delayStatus",
name="Delay Status", name="Delay Status",
icon="mdi:timer-check", icon="mdi:timer-check",
entity_category=EntityCategory.CONFIG entity_category=EntityCategory.CONFIG,
), ),
HonSwitchEntityDescription( HonSwitchEntityDescription(
key="startProgram.haier_SoakPrewashSelection", key="startProgram.haier_SoakPrewashSelection",
name="Soak Prewash Selection", name="Soak Prewash Selection",
icon="mdi:tshirt-crew", icon="mdi:tshirt-crew",
entity_category=EntityCategory.CONFIG entity_category=EntityCategory.CONFIG,
), ),
), ),
"TD": ( "TD": (
@ -69,26 +73,49 @@ SWITCHES: dict[str, tuple[HonSwitchEntityDescription, ...]] = {
turn_off_key="resumeProgram", turn_off_key="resumeProgram",
), ),
), ),
"WD": (
HonSwitchEntityDescription(
key="active",
name="Washing Machine",
icon="mdi:washing-machine",
turn_on_key="startProgram",
turn_off_key="stopProgram",
),
HonSwitchEntityDescription(
key="pause",
name="Pause Washing Machine",
icon="mdi:pause",
turn_on_key="pauseProgram",
turn_off_key="resumeProgram",
),
),
} }
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None: async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id] hon: Hon = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"] coordinators = hass.data[DOMAIN]["coordinators"]
appliances = [] appliances = []
for device in hon.devices: for device in hon.appliances:
if device.mac_address in coordinators: if device.unique_id in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address] coordinator = hass.data[DOMAIN]["coordinators"][device.unique_id]
else: else:
coordinator = HonCoordinator(hass, device) coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator hass.data[DOMAIN]["coordinators"][device.unique_id] = coordinator
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
if descriptions := SWITCHES.get(device.appliance_type): if descriptions := SWITCHES.get(device.appliance_type):
for description in descriptions: for description in descriptions:
if device.get(description.key) is not None or device.commands.get(description.key) is not None: if (
appliances.extend([ device.get(description.key) is not None
HonSwitchEntity(hass, coordinator, entry, device, description)] or device.commands.get(description.key) is not None
):
appliances.extend(
[HonSwitchEntity(hass, coordinator, entry, device, description)]
)
else:
_LOGGER.warning(
"[%s] Can't setup %s", device.appliance_type, description.key
) )
async_add_entities(appliances) async_add_entities(appliances)
@ -97,7 +124,14 @@ async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> Non
class HonSwitchEntity(HonEntity, SwitchEntity): class HonSwitchEntity(HonEntity, SwitchEntity):
entity_description: HonSwitchEntityDescription entity_description: HonSwitchEntityDescription
def __init__(self, hass, coordinator, entry, device: HonDevice, description: HonSwitchEntityDescription) -> None: def __init__(
self,
hass,
coordinator,
entry,
device: HonAppliance,
description: HonSwitchEntityDescription,
) -> None:
super().__init__(hass, entry, coordinator, device) super().__init__(hass, entry, coordinator, device)
self._coordinator = coordinator self._coordinator = coordinator
self._device = device self._device = device
@ -106,7 +140,9 @@ class HonSwitchEntity(HonEntity, SwitchEntity):
def available(self) -> bool: def available(self) -> bool:
if self.entity_category == EntityCategory.CONFIG: if self.entity_category == EntityCategory.CONFIG:
return self._device.settings[self.entity_description.key].typology != "fixed" return (
self._device.settings[self.entity_description.key].typology != "fixed"
)
return True return True
@property @property
@ -114,7 +150,11 @@ class HonSwitchEntity(HonEntity, SwitchEntity):
"""Return True if entity is on.""" """Return True if entity is on."""
if self.entity_category == EntityCategory.CONFIG: if self.entity_category == EntityCategory.CONFIG:
setting = self._device.settings[self.entity_description.key] setting = self._device.settings[self.entity_description.key]
return setting.value == "1" or hasattr(setting, "min") and setting.value != setting.min return (
setting.value == "1"
or hasattr(setting, "min")
and setting.value != setting.min
)
return self._device.get(self.entity_description.key, False) return self._device.get(self.entity_description.key, False)
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
@ -132,5 +172,3 @@ class HonSwitchEntity(HonEntity, SwitchEntity):
self.async_write_ha_state() self.async_write_ha_state()
else: else:
await self._device.commands[self.entity_description.turn_off_key].send() await self._device.commands[self.entity_description.turn_off_key].send()

View File

@ -377,6 +377,7 @@
"single_item_steam": "Single Item + Steam", "single_item_steam": "Single Item + Steam",
"smart_wash": "Smart Wash", "smart_wash": "Smart Wash",
"soft_care": "Soft Care", "soft_care": "Soft Care",
"soft_care_steam": "Soft Care + Steam",
"soft_care_steam_title": "Soft Care + Steam", "soft_care_steam_title": "Soft Care + Steam",
"special_39": "Special 39'", "special_39": "Special 39'",
"special_39_full_load": "Special 39'", "special_39_full_load": "Special 39'",

View File

@ -377,6 +377,7 @@
"single_item_steam": "Single Item + Steam", "single_item_steam": "Single Item + Steam",
"smart_wash": "Smart Wash", "smart_wash": "Smart Wash",
"soft_care": "Soft Care", "soft_care": "Soft Care",
"soft_care_steam": "Soft Care + Steam",
"soft_care_steam_title": "Soft Care + Steam", "soft_care_steam_title": "Soft Care + Steam",
"special_39": "Special 39'", "special_39": "Special 39'",
"special_39_full_load": "Special 39'", "special_39_full_load": "Special 39'",
@ -401,7 +402,64 @@
"wool": "Wool", "wool": "Wool",
"wool_and_delicates_49": "Wool and Delicates 49'", "wool_and_delicates_49": "Wool and Delicates 49'",
"wool_dry": "Wool Dry", "wool_dry": "Wool Dry",
"wool_soft_care": "Wool and Soft Car" "wool_soft_care": "Wool and Soft Car",
"bakery": "Pasta and Pastries",
"bakery_steam": "Steam-baked bread",
"bottom_heating": "Bottom Heating",
"bottom_heating_fan": "Bottom Heating + Fan",
"bread": "Bread",
"bread_steam": "Steam-baked pastries",
"combi": "Combi",
"convection_fan": "Convection + Fan",
"convection_fan_turnspit": "Convection + Fan + Turnspit",
"conventional": "Conventional",
"conventional_turnspit": "Convection + Turnspit",
"defrost": "Defrost",
"descaling": "Descaling",
"fish": "Fish",
"fish_steam": "Steam-cooked fish",
"grill_cata": "Grill",
"grill_fan_cata": "Grill fan",
"grill_fan_pyro": "Grill + Fan",
"grill_pyro": "Grill",
"h20_clean": "H2O-Clean",
"iot_bread": "Bread",
"iot_h20_clean": "h2O clean",
"leavening": "Leavening",
"light_fan": "Light Fan",
"low_temp_cooking": "Low Temperature Cooking",
"low_temp_cooking_fish": "Low Temperature Cooking - Fish",
"low_temp_cooking_fish_steam": "Low Temperature Steam Cooking - Fish",
"low_temp_cooking_meat": "Low Temperature Cooking - Meat",
"low_temp_cooking_meat_steam": "Low Temperature Steam Cooking - Meat",
"low_temp_cooking_steam": "Low Temperature Steam Cooking",
"meat": "Meat",
"meat_steam": "Steam-cooked meat",
"multi_level": "Multi-Level",
"paella": "Paella",
"pasta_and_bakery": "Pasta and Bakery",
"pizza": "Pizza",
"pyrolysis": "Pyrolysis",
"pyrolysis_plus": "Pyrolysis +",
"red_meat": "Red Meat",
"red_meat_steam": "Steam-cooked red meat",
"regenerate": "Regeneration",
"soft_plus": "Soft+",
"super_grill": "Super Grill",
"tailor_bake": "Tailor bake",
"tailor_bake_cata": "Tailor Bake",
"tailor_bake_pyro": "Tailor Bake",
"vegetables": "Vegetables",
"vegetables_cata": "Vegetables",
"vegetables_pyro": "Vegetables",
"water_discharge": "Water Drain",
"white_meat": "White Meat",
"white_meat_steam": "Steam-cooked white meat",
"iot_standard_boiling": "Boiling",
"iot_standard_frying": "Frying",
"iot_standard_keep_warm": "Keep Warm",
"iot_standard_melting": "Melting",
"iot_standard_simmering": "Simmering"
} }
} }
} }

View File

@ -1,5 +1,6 @@
{ {
"name": "Haier hOn", "name": "Haier hOn",
"render_readme": true, "homeassistant": "2023.2.0",
"homeassistant": "2023.2.0" "zip_release": true,
"filename": "haier_hon.zip"
} }

40
info.md Normal file
View File

@ -0,0 +1,40 @@
# Haier hOn
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/Andre0512/hon?color=green)](https://github.com/Andre0512/hon/releases/latest)
![GitHub](https://img.shields.io/github/license/Andre0512/hon?color=red)
[![Home Assistant installs](https://img.shields.io/badge/dynamic/json?color=blue&label=usage&suffix=%20installs&cacheSeconds=15600&url=https://analytics.home-assistant.io/custom_integrations.json&query=$.hon.total)](https://analytics.home-assistant.io/)
Support for home appliances of Haier's mobile app hOn.
## Supported Appliances
- Tumble Dryer
- Washer Dryer
- Washing Machine
- Oven
- Hob
## Tested Appliances
- Haier WD90-B14TEAM5
- Haier HD80-A3959
- Haier HWO60SM2F3XH
- Hoover H-WASH 500
## Configuration
**Method 1**: [![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=hon)
**Method 2**: Settings > Devices & Services > Add Integration > **Haier hOn**
_If the integration is not in the list, you need to clear the browser cache._
## Contribute
Want to help us to support more appliances? Or add more sensors? Or help with translating? Or beautify some icons or captions?
Check out the [project on GitHub](https://github.com/Andre0512/hon), every contribution is welcome!
## Useful Links
* [GitHub repository](https://github.com/Andre0512/hon) (please add a star if you like this integration!)
* [pyhOn library](https://github.com/Andre0512/pyhOn)
* [Release notes](https://github.com/Andre0512/hon/releases)
* [Discussion and help](https://github.com/Andre0512/hon/discussions)
* [Issues](https://github.com/Andre0512/hon/issues)