first commit
This commit is contained in:
commit
31c30b1670
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
__pycache__/
|
||||
test.py
|
2
README.md
Normal file
2
README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# hOn
|
||||
Home Assistant component supporting hOn cloud.
|
52
custom_components/hon/__init__.py
Normal file
52
custom_components/hon/__init__.py
Normal file
@ -0,0 +1,52 @@
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
from pyhon import HonConnection
|
||||
from pyhon.device import HonDevice
|
||||
|
||||
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 homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from custom_components.hon.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)
|
||||
)
|
||||
|
||||
|
||||
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.load_attributes()
|
42
custom_components/hon/config_flow.py
Executable file
42
custom_components/hon/config_flow.py
Executable 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)
|
7
custom_components/hon/const.py
Executable file
7
custom_components/hon/const.py
Executable file
@ -0,0 +1,7 @@
|
||||
DOMAIN = "hon"
|
||||
|
||||
PLATFORMS = [
|
||||
"sensor",
|
||||
"select",
|
||||
"number"
|
||||
]
|
29
custom_components/hon/hon.py
Executable file
29
custom_components/hon/hon.py
Executable file
@ -0,0 +1,29 @@
|
||||
from pyhon.device import HonDevice
|
||||
|
||||
from .const import DOMAIN
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
|
||||
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 a device description for device registry."""
|
||||
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,
|
||||
)
|
8
custom_components/hon/manifest.json
Executable file
8
custom_components/hon/manifest.json
Executable file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"domain": "hon",
|
||||
"name": "hOn",
|
||||
"config_flow": true,
|
||||
"version": "0.0.1",
|
||||
"codeowners": ["@Andre0512"],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
100
custom_components/hon/number.py
Normal file
100
custom_components/hon/number.py
Normal file
@ -0,0 +1,100 @@
|
||||
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.core import callback
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from custom_components import DOMAIN, HonCoordinator
|
||||
from custom_components.hon import HonEntity
|
||||
|
||||
NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = {
|
||||
"WM": (
|
||||
NumberEntityDescription(
|
||||
key="delayStatus",
|
||||
name="delayStatus",
|
||||
entity_category=EntityCategory.CONFIG
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="delayTime",
|
||||
name="delayTime",
|
||||
icon="mdi:timer",
|
||||
entity_category=EntityCategory.CONFIG
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="haier_SoakPrewashSelection",
|
||||
name="haier_SoakPrewashSelection",
|
||||
entity_category=EntityCategory.CONFIG
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="rinseIterations",
|
||||
name="rinseIterations",
|
||||
entity_category=EntityCategory.CONFIG
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="mainWashTime",
|
||||
name="mainWashTime",
|
||||
icon="mdi:timer",
|
||||
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 := 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()
|
95
custom_components/hon/select.py
Normal file
95
custom_components/hon/select.py
Normal file
@ -0,0 +1,95 @@
|
||||
"""Support for Tuya select."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pyhon import HonConnection
|
||||
from pyhon.device import HonDevice
|
||||
from pyhon.parameter import HonParameterFixed
|
||||
|
||||
from config.custom_components.hon import HonCoordinator
|
||||
from config.custom_components.hon.hon import HonEntity
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
|
||||
DOMAIN = "hon"
|
||||
|
||||
SELECTS = {
|
||||
"WM": (
|
||||
SelectEntityDescription(
|
||||
key="spinSpeed",
|
||||
name="Spin speed",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
icon="mdi:numeric"
|
||||
),
|
||||
SelectEntityDescription(
|
||||
key="temp",
|
||||
name="Temperature",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
icon="mdi:thermometer"
|
||||
),
|
||||
SelectEntityDescription(
|
||||
key="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:
|
||||
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()
|
93
custom_components/hon/sensor.py
Normal file
93
custom_components/hon/sensor.py
Normal file
@ -0,0 +1,93 @@
|
||||
import logging
|
||||
|
||||
from pyhon import HonConnection
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorDeviceClass,
|
||||
SensorStateClass,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from custom_components import HonCoordinator
|
||||
from .const import DOMAIN
|
||||
from custom_components.hon import 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,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="totalWaterUsed",
|
||||
name="Total Water",
|
||||
device_class=SensorDeviceClass.WATER,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
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,
|
||||
icon="mdi:lightning-bolt"
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="currentWaterUsed",
|
||||
name="Current Water Used",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:water"
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
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:
|
||||
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.attributes.get(self.entity_description.key, "")
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self):
|
||||
self._attr_native_value = self._device.attributes.get(self.entity_description.key, "")
|
||||
self.async_write_ha_state()
|
14
custom_components/hon/translations/en.json
Normal file
14
custom_components/hon/translations/en.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "hOn",
|
||||
"description": "Please enters your hOn credentials",
|
||||
"data": {
|
||||
"email": "Email Address",
|
||||
"password": "Password"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user