Compare commits

...

12 Commits

Author SHA1 Message Date
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
10 changed files with 292 additions and 47 deletions

View File

@ -1,24 +1,27 @@
# 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://github.com/hacs/integration)
[![GitHub manifest version (path)](https://img.shields.io/github/manifest-json/v/andre0512/hon?color=g&filename=custom_components%2Fhon%2Fmanifest.json)](https://github.com/Andre0512/hon/releases/latest)
Home Assistant component supporting devices of Haier's mobile app **hOn**. [![Home Assistant installs](https://img.shields.io/badge/dynamic/json?color=41BDF5&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
## 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 ## Configuration
7. Shift reload your browser
8. Click Add Integration **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)
9. Search for Haier hOn
10. Type your username used in the hOn App and hit submit **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!
@ -68,6 +71,8 @@ Any kind of contribution is welcome!
## 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

@ -27,15 +27,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,7 +58,67 @@ 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="Online",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED",
icon="mdi:wifi"
),
HonBinarySensorEntityDescription(
key="attributes.parameters.remoteCtrValid",
name="On",
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"
),
),
} }
@ -70,7 +137,7 @@ async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> Non
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)]

View File

@ -1,25 +1,24 @@
from pyhon import HonConnection
from pyhon.device import HonDevice
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 HonConnection
from pyhon.device import HonDevice
from .const import DOMAIN from .const import DOMAIN
from .hon import HonCoordinator, HonEntity from .hon import HonCoordinator, HonEntity
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(
# key="resumeProgram",
# name="Resume Program",
# icon="mdi:play-pause",
# ),
), ),
ButtonEntityDescription(
key="stopProgram",
name="Stop Program",
icon="mdi:power-off",
),
)
} }

View File

@ -7,6 +7,6 @@
"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.4.1"],
"version": "0.3.0" "version": "0.4.0"
} }

View File

@ -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
@ -74,6 +74,39 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = {
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
),
),
} }
@ -91,6 +124,8 @@ async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> Non
if descriptions := NUMBERS.get(device.appliance_type): if descriptions := NUMBERS.get(device.appliance_type):
for description in descriptions: for description in descriptions:
if not device.settings.get(description.key):
continue
appliances.extend([ appliances.extend([
HonNumberEntity(hass, coordinator, entry, device, description)] HonNumberEntity(hass, coordinator, entry, device, description)]
) )

View File

@ -1,18 +1,22 @@
from __future__ import annotations from __future__ import annotations
import logging
from pyhon import HonConnection from pyhon import HonConnection
from pyhon.device import HonDevice from pyhon.device import HonDevice
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(
@ -43,7 +47,34 @@ SELECTS = {
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,
),
SelectEntityDescription(
key="startProgram.preheatStatus",
name="Preheat",
entity_category=EntityCategory.CONFIG
),
),
} }
@ -61,7 +92,7 @@ async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> Non
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)]

View File

@ -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
@ -85,7 +93,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,
), ),
@ -141,7 +149,84 @@ SENSORS: dict[str, tuple[SensorEntityDescription, ...]] = {
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",
),
),
} }
@ -160,6 +245,7 @@ async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> Non
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)]

View File

@ -1,3 +1,5 @@
import logging
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
@ -10,6 +12,8 @@ from pyhon.device import HonDevice
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:
@ -20,7 +24,7 @@ class HonSwitchEntityDescriptionMixin:
@dataclass @dataclass
class HonSwitchEntityDescription(HonSwitchEntityDescriptionMixin, class HonSwitchEntityDescription(HonSwitchEntityDescriptionMixin,
SwitchEntityDescription SwitchEntityDescription
): ):
pass pass
@ -69,6 +73,22 @@ 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",
),
),
} }
@ -90,6 +110,8 @@ async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> Non
appliances.extend([ appliances.extend([
HonSwitchEntity(hass, coordinator, entry, device, description)] 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)
@ -132,5 +154,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'",