Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Andre0512 committed Feb 19, 2023
0 parents commit 31c30b1
Show file tree
Hide file tree
Showing 12 changed files with 450 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
test.py
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# hOn
Home Assistant component supporting hOn cloud.
52 changes: 52 additions & 0 deletions custom_components/hon/__init__.py
Original file line number Diff line number Diff line change
@@ -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 changes: 42 additions & 0 deletions custom_components/hon/config_flow.py
Original file line number Diff line number Diff line change
@@ -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 changes: 7 additions & 0 deletions custom_components/hon/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DOMAIN = "hon"

PLATFORMS = [
"sensor",
"select",
"number"
]
29 changes: 29 additions & 0 deletions custom_components/hon/hon.py
Original file line number Diff line number Diff line change
@@ -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 changes: 8 additions & 0 deletions custom_components/hon/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"domain": "hon",
"name": "hOn",
"config_flow": true,
"version": "0.0.1",
"codeowners": ["@Andre0512"],
"iot_class": "cloud_polling"
}
100 changes: 100 additions & 0 deletions custom_components/hon/number.py
Original file line number Diff line number Diff line change
@@ -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 changes: 95 additions & 0 deletions custom_components/hon/select.py
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit 31c30b1

Please sign in to comment.