first commit
This commit is contained in:
		
							
								
								
									
										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" | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user