Rename repo

This commit is contained in:
Andre Basche 2023-03-06 13:41:58 +01:00
parent 9d8b17b2cf
commit 03a1e40b6e
14 changed files with 7 additions and 6 deletions

View file

@ -0,0 +1,49 @@
import logging
import voluptuous as vol
from pyhon import HonConnection
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.helpers import config_validation as cv, aiohttp_client
from homeassistant.helpers.typing import HomeAssistantType
from .const import DOMAIN, PLATFORMS
_LOGGER = logging.getLogger(__name__)
HON_SCHEMA = vol.Schema(
{
vol.Required(CONF_EMAIL): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
}
)
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema(vol.All(cv.ensure_list, [HON_SCHEMA]))},
extra=vol.ALLOW_EXTRA,
)
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
session = aiohttp_client.async_get_clientsession(hass)
hon = HonConnection(entry.data["email"], entry.data["password"], session)
await hon.setup()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.unique_id] = hon
hass.data[DOMAIN]["coordinators"] = {}
for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform)
)
return True
async def async_unload_entry(hass, entry: ConfigEntry) -> bool:
unload = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload:
if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN, None)
return unload

View file

@ -0,0 +1,86 @@
import logging
from dataclasses import dataclass
from pyhon import HonConnection
from homeassistant.components.binary_sensor import BinarySensorEntityDescription, BinarySensorDeviceClass, \
BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from .const import DOMAIN
from .hon import HonCoordinator, HonEntity
_LOGGER = logging.getLogger(__name__)
@dataclass
class HonBinarySensorEntityDescriptionMixin:
on_value: str = ""
@dataclass
class HonBinarySensorEntityDescription(HonBinarySensorEntityDescriptionMixin, BinarySensorEntityDescription):
pass
BINARY_SENSORS: dict[str, tuple[HonBinarySensorEntityDescription, ...]] = {
"WM": (
HonBinarySensorEntityDescription(
key="lastConnEvent.category",
name="Connection",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
on_value="CONNECTED",
),
HonBinarySensorEntityDescription(
key="doorLockStatus",
name="Door Locked",
device_class=BinarySensorDeviceClass.DOOR,
on_value="0",
),
)
}
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"]
appliances = []
for device in hon.devices:
if device.mac_address in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address]
else:
coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator
await coordinator.async_config_entry_first_refresh()
if descriptions := BINARY_SENSORS.get(device.appliance_type_name):
for description in descriptions:
if not device.data.get(description.key):
_LOGGER.info("Can't setup %s", description.key)
continue
appliances.extend([
HonBinarySensorEntity(hass, coordinator, entry, device, description)]
)
async_add_entities(appliances)
class HonBinarySensorEntity(HonEntity, BinarySensorEntity):
entity_description: HonBinarySensorEntityDescription
def __init__(self, hass, coordinator, entry, device, description) -> None:
super().__init__(hass, entry, coordinator, device)
self._coordinator = coordinator
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}{description.key}"
@property
def is_on(self) -> bool:
return self._device.data.get(self.entity_description.key, "") == self.entity_description.on_value
@callback
def _handle_coordinator_update(self):
self._attr_native_value = self._device.data.get(self.entity_description.key, "")
self.async_write_ha_state()

View file

@ -0,0 +1,59 @@
from pyhon import HonConnection
from pyhon.device import HonDevice
from homeassistant.components.button import ButtonEntityDescription, ButtonEntity
from homeassistant.config_entries import ConfigEntry
from .const import DOMAIN
from .hon import HonCoordinator, HonEntity
BUTTONS: dict[str, tuple[ButtonEntityDescription, ...]] = {
"WM": (
ButtonEntityDescription(
key="pauseProgram",
name="Pause Program",
icon="mdi:pause",
),
ButtonEntityDescription(
key="resumeProgram",
name="Resume Program",
icon="mdi:play-pause",
),
),
}
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"]
appliances = []
for device in hon.devices:
if device.mac_address in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address]
else:
coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator
await coordinator.async_config_entry_first_refresh()
if descriptions := BUTTONS.get(device.appliance_type_name):
for description in descriptions:
if not device.commands.get(description.key):
continue
appliances.extend([
HonButtonEntity(hass, coordinator, entry, device, description)]
)
async_add_entities(appliances)
class HonButtonEntity(HonEntity, ButtonEntity):
def __init__(self, hass, coordinator, entry, device: HonDevice, description) -> None:
super().__init__(hass, entry, coordinator, device)
self._coordinator = coordinator
self._device = device
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}{description.key}"
async def async_press(self) -> None:
await self._device.commands[self.entity_description.key].send()

View file

@ -0,0 +1,42 @@
import logging
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
class HonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
def __init__(self):
self._email = None
self._password = None
async def async_step_user(self, user_input=None):
if user_input is None:
return self.async_show_form(step_id="user", data_schema=vol.Schema(
{vol.Required(CONF_EMAIL): str, vol.Required(CONF_PASSWORD): str}))
self._email = user_input[CONF_EMAIL]
self._password = user_input[CONF_PASSWORD]
# Check if already configured
await self.async_set_unique_id(self._email)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=self._email,
data={
CONF_EMAIL: self._email,
CONF_PASSWORD: self._password,
},
)
async def async_step_import(self, user_input=None):
return await self.async_step_user(user_input)

View file

@ -0,0 +1,10 @@
DOMAIN = "hon"
PLATFORMS = [
"sensor",
"select",
"number",
"switch",
"button",
"binary_sensor",
]

View file

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

View file

@ -0,0 +1,12 @@
{
"domain": "hon",
"name": "Haier hOn",
"codeowners": ["@Andre0512"],
"config_flow": true,
"documentation": "https://github.com/Andre0512/hon/",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/Andre0512/hon/issues",
"requirements": ["pyhOn==0.2.4"],
"version": "0.1.1"
}

View file

@ -0,0 +1,94 @@
from __future__ import annotations
from pyhon import HonConnection
from pyhon.parameter import HonParameterRange
from homeassistant.components.number import (
NumberEntity,
NumberEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTime
from homeassistant.core import callback
from homeassistant.helpers.entity import EntityCategory
from .const import DOMAIN
from .hon import HonEntity, HonCoordinator
NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = {
"WM": (
NumberEntityDescription(
key="startProgram.delayTime",
name="Delay Time",
icon="mdi:timer",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTime.MINUTES
),
NumberEntityDescription(
key="startProgram.rinseIterations",
name="Rinse Iterations",
entity_category=EntityCategory.CONFIG
),
NumberEntityDescription(
key="startProgram.mainWashTime",
name="Main Wash Time",
icon="mdi:timer",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTime.MINUTES
),
),
}
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"]
appliances = []
for device in hon.devices:
if device.mac_address in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address]
else:
coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator
await coordinator.async_config_entry_first_refresh()
if descriptions := NUMBERS.get(device.appliance_type_name):
for description in descriptions:
appliances.extend([
HonNumberEntity(hass, coordinator, entry, device, description)]
)
async_add_entities(appliances)
class HonNumberEntity(HonEntity, NumberEntity):
def __init__(self, hass, coordinator, entry, device, description) -> None:
super().__init__(hass, entry, coordinator, device)
self._coordinator = coordinator
self._data = device.settings[description.key]
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}{description.key}"
if isinstance(self._data, HonParameterRange):
self._attr_native_max_value = self._data.max
self._attr_native_min_value = self._data.min
self._attr_native_step = self._data.step
@property
def native_value(self) -> float | None:
return self._data.value
async def async_set_native_value(self, value: float) -> None:
self._data.value = value
await self.coordinator.async_request_refresh()
@callback
def _handle_coordinator_update(self):
self._data = self._device.settings[self.entity_description.key]
if isinstance(self._data, HonParameterRange):
self._attr_native_max_value = self._data.max
self._attr_native_min_value = self._data.min
self._attr_native_step = self._data.step
self._attr_native_value = self._data.value
self.async_write_ha_state()

View file

@ -0,0 +1,97 @@
from __future__ import annotations
from pyhon import HonConnection
from pyhon.device import HonDevice
from pyhon.parameter import HonParameterFixed
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTemperature, REVOLUTIONS_PER_MINUTE
from homeassistant.core import callback
from homeassistant.helpers.entity import EntityCategory
from .const import DOMAIN
from .hon import HonEntity, HonCoordinator
SELECTS = {
"WM": (
SelectEntityDescription(
key="startProgram.spinSpeed",
name="Spin speed",
entity_category=EntityCategory.CONFIG,
icon="mdi:numeric",
unit_of_measurement=REVOLUTIONS_PER_MINUTE
),
SelectEntityDescription(
key="startProgram.temp",
name="Temperature",
entity_category=EntityCategory.CONFIG,
icon="mdi:thermometer",
unit_of_measurement=UnitOfTemperature.CELSIUS
),
SelectEntityDescription(
key="startProgram.program",
name="Programme",
entity_category=EntityCategory.CONFIG
),
)
}
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"]
appliances = []
for device in hon.devices:
if device.mac_address in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address]
else:
coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator
await coordinator.async_config_entry_first_refresh()
if descriptions := SELECTS.get(device.appliance_type_name):
for description in descriptions:
if not device.data.get(description.key):
continue
appliances.extend([
HonSelectEntity(hass, coordinator, entry, device, description)]
)
async_add_entities(appliances)
class HonSelectEntity(HonEntity, SelectEntity):
def __init__(self, hass, coordinator, entry, device: HonDevice, description) -> None:
super().__init__(hass, entry, coordinator, device)
self._coordinator = coordinator
self._device = device
self._data = device.settings[description.key]
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}{description.key}"
if not isinstance(self._data, HonParameterFixed):
self._attr_options: list[str] = self._data.values
else:
self._attr_options = [self._data.value]
@property
def current_option(self) -> str | None:
value = self._data.value
if value is None or value not in self._attr_options:
return None
return value
async def async_select_option(self, option: str) -> None:
self._data.value = option
await self.coordinator.async_request_refresh()
@callback
def _handle_coordinator_update(self):
self._data = self._device.settings[self.entity_description.key]
if not isinstance(self._data, HonParameterFixed):
self._attr_options: list[str] = self._data.values
else:
self._attr_options = [self._data.value]
self._attr_native_value = self._data.value
self.async_write_ha_state()

View file

@ -0,0 +1,135 @@
import logging
from pyhon import HonConnection
from homeassistant.components.sensor import (
SensorEntity,
SensorDeviceClass,
SensorStateClass,
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfEnergy, UnitOfVolume, UnitOfMass, UnitOfPower, UnitOfTime
from homeassistant.core import callback
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from .hon import HonCoordinator, HonEntity
_LOGGER = logging.getLogger(__name__)
SENSORS: dict[str, tuple[SensorEntityDescription, ...]] = {
"WM": (
SensorEntityDescription(
key="totalElectricityUsed",
name="Total Power",
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR
),
SensorEntityDescription(
key="totalWaterUsed",
name="Total Water",
device_class=SensorDeviceClass.WATER,
state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=UnitOfVolume.LITERS
),
SensorEntityDescription(
key="totalWashCycle",
name="Total Wash Cycle",
state_class=SensorStateClass.TOTAL_INCREASING,
icon="mdi:counter"
),
SensorEntityDescription(
key="currentElectricityUsed",
name="Current Electricity Used",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.KILO_WATT,
icon="mdi:lightning-bolt"
),
SensorEntityDescription(
key="currentWaterUsed",
name="Current Water Used",
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:water"
),
SensorEntityDescription(
key="startProgram.weight",
name="Suggested weight",
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfMass.KILOGRAMS,
icon="mdi:weight-kilogram"
),
SensorEntityDescription(
key="machMode",
name="Machine Last Status",
icon="mdi:information",
translation_key="mode"
),
SensorEntityDescription(
key="errors",
name="Last Error",
icon="mdi:math-log",
translation_key="errors"
),
SensorEntityDescription(
key="remainingTimeMM",
name="Remaining Time",
icon="mdi:timer",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTime.MINUTES,
),
SensorEntityDescription(
key="spinSpeed",
name="Spin Speed",
icon="mdi:timer",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTime.MINUTES,
),
)
}
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"]
appliances = []
for device in hon.devices:
if device.mac_address in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address]
else:
coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator
await coordinator.async_config_entry_first_refresh()
if descriptions := SENSORS.get(device.appliance_type_name):
for description in descriptions:
if not device.data.get(description.key):
continue
appliances.extend([
HonSensorEntity(hass, coordinator, entry, device, description)]
)
async_add_entities(appliances)
class HonSensorEntity(HonEntity, SensorEntity):
def __init__(self, hass, coordinator, entry, device, description) -> None:
super().__init__(hass, entry, coordinator, device)
self._coordinator = coordinator
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}{description.key}"
@property
def native_value(self) -> StateType:
return self._device.data.get(self.entity_description.key, "")
@callback
def _handle_coordinator_update(self):
self._attr_native_value = self._device.data.get(self.entity_description.key, "")
self.async_write_ha_state()

View file

@ -0,0 +1,111 @@
from dataclasses import dataclass
from typing import Any
from homeassistant.components.switch import SwitchEntityDescription, SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from pyhon import HonConnection
from pyhon.device import HonDevice
from .const import DOMAIN
from .hon import HonCoordinator, HonEntity
@dataclass
class HonSwitchEntityDescriptionMixin:
turn_on_key: str = ""
turn_off_key: str = ""
@dataclass
class HonSwitchEntityDescription(HonSwitchEntityDescriptionMixin,
SwitchEntityDescription
):
pass
SWITCHES: dict[str, tuple[HonSwitchEntityDescription, ...]] = {
"WM": (
HonSwitchEntityDescription(
key="startProgram",
name="Start Program",
icon="mdi:play",
turn_on_key="startProgram",
turn_off_key="stopProgram",
),
HonSwitchEntityDescription(
key="startProgram.delayStatus",
name="Delay Status",
entity_category=EntityCategory.CONFIG
),
HonSwitchEntityDescription(
key="startProgram.haier_SoakPrewashSelection",
name="Soak Prewash Selection",
entity_category=EntityCategory.CONFIG
),
),
}
async def async_setup_entry(hass, entry: ConfigEntry, async_add_entities) -> None:
hon: HonConnection = hass.data[DOMAIN][entry.unique_id]
coordinators = hass.data[DOMAIN]["coordinators"]
appliances = []
for device in hon.devices:
if device.mac_address in coordinators:
coordinator = hass.data[DOMAIN]["coordinators"][device.mac_address]
else:
coordinator = HonCoordinator(hass, device)
hass.data[DOMAIN]["coordinators"][device.mac_address] = coordinator
await coordinator.async_config_entry_first_refresh()
if descriptions := SWITCHES.get(device.appliance_type_name):
for description in descriptions:
if device.data.get(description.key) is not None or device.commands.get(description.key) is not None:
appliances.extend([
HonSwitchEntity(hass, coordinator, entry, device, description)]
)
async_add_entities(appliances)
class HonSwitchEntity(HonEntity, SwitchEntity):
entity_description: HonSwitchEntityDescription
def __init__(self, hass, coordinator, entry, device: HonDevice, description: HonSwitchEntityDescription) -> None:
super().__init__(hass, entry, coordinator, device)
self._coordinator = coordinator
self._device = device
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}{description.key}"
def available(self) -> bool:
if self.entity_category == EntityCategory.CONFIG:
return self._device.settings[self.entity_description.key].typology == "fixed"
return True
@property
def is_on(self) -> bool | None:
"""Return True if entity is on."""
if self.entity_category == EntityCategory.CONFIG:
setting = self._device.settings[self.entity_description.key]
return setting.value == "1" or hasattr(setting, "min") and setting.value != setting.min
return self._device.data.get(self.entity_description.key, "")
async def async_turn_on(self, **kwargs: Any) -> None:
if self.entity_category == EntityCategory.CONFIG:
setting = self._device.settings[self.entity_description.key]
setting.value = setting.max
self.async_write_ha_state()
else:
await self._device.commands[self.entity_description.turn_on_key].send()
async def async_turn_off(self, **kwargs: Any) -> None:
if self.entity_category == EntityCategory.CONFIG:
setting = self._device.settings[self.entity_description.key]
setting.value = setting.min
self.async_write_ha_state()
else:
await self._device.commands[self.entity_description.turn_off_key].send()

View file

@ -0,0 +1,35 @@
{
"config": {
"step": {
"user": {
"description": "Please enters your hOn credentials",
"data": {
"email": "Email Address",
"password": "Password"
}
}
}
},
"entity": {
"sensor": {
"mode": {
"state": {
"0": "Disconnected",
"1": "Ready",
"2": "Running",
"3": "Paused",
"5": "Scheduled",
"6": "Error",
"7": "Finished"
}
},
"errors": {
"state": {
"00": "No error",
"100000000000": "E2: Check if the door is closed",
"8000000000000": "E4: Check the water supply"
}
}
}
}
}