From d2de7dae9049e950479ab9d5f3007d70d31a7166 Mon Sep 17 00:00:00 2001 From: sususweet Date: Fri, 13 Dec 2024 10:57:27 +0800 Subject: [PATCH 1/8] feat: add control support for new protocol like DYD-T22A3, DYD-D50A3 --- .../deye_dehumidifier/__init__.py | 63 +++++++++++-------- .../deye_dehumidifier/binary_sensor.py | 15 ++--- custom_components/deye_dehumidifier/fan.py | 17 ++--- .../deye_dehumidifier/humidifier.py | 17 ++--- .../deye_dehumidifier/manifest.json | 4 +- custom_components/deye_dehumidifier/sensor.py | 15 ++--- custom_components/deye_dehumidifier/switch.py | 33 +++++----- 7 files changed, 90 insertions(+), 74 deletions(-) diff --git a/custom_components/deye_dehumidifier/__init__.py b/custom_components/deye_dehumidifier/__init__.py index f9b4590..c54eea9 100644 --- a/custom_components/deye_dehumidifier/__init__.py +++ b/custom_components/deye_dehumidifier/__init__.py @@ -57,7 +57,7 @@ def on_auth_token_refreshed(auth_token: str) -> None: entry.data[CONF_AUTH_TOKEN], ) cloud_api.on_auth_token_refreshed = on_auth_token_refreshed - mqtt_info = await cloud_api.get_mqtt_info() + mqtt_info = await cloud_api.get_deye_platform_mqtt_info() mqtt_client = DeyeMqttClient( mqtt_info["mqtthost"], mqtt_info["sslport"], @@ -103,11 +103,12 @@ class DeyeEntity(Entity): """Initiate Deye Base Class.""" def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient + self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: """Initialize the instance.""" self._device = device self._mqtt_client = mqtt_client + self._cloud_api = cloud_api self._attr_has_entity_name = True self._attr_available = self._device["online"] self._attr_unique_id = self._device["mac"] @@ -144,34 +145,40 @@ def update_device_state(self, state: DeyeDeviceState) -> None: async def async_added_to_hass(self) -> None: """When entity is added to Home Assistant.""" - self.async_on_remove( - self._mqtt_client.subscribe_availability_change( - self._device["product_id"], - self._device["device_id"], - self.update_device_availability, + if self._device["platform"] == 1: + self.async_on_remove( + self._mqtt_client.subscribe_availability_change( + self._device["product_id"], + self._device["device_id"], + self.update_device_availability, + ) ) - ) - self.async_on_remove( - self._mqtt_client.subscribe_state_change( - self._device["product_id"], - self._device["device_id"], - self.update_device_state, + self.async_on_remove( + self._mqtt_client.subscribe_state_change( + self._device["product_id"], + self._device["device_id"], + self.update_device_state, + ) ) - ) - self.poll_device_state() + + await self.poll_device_state() self.async_on_remove(self.cancel_polling) @callback - def poll_device_state(self, now: datetime | None = None) -> None: + async def poll_device_state(self, now: datetime | None = None) -> None: """ Some Deye devices have a very long heartbeat period. So polling is still necessary to get the latest state as quickly as possible. """ - self._mqtt_client.publish_command( - self._device["product_id"], - self._device["device_id"], - QUERY_DEVICE_STATE_COMMAND, - ) + if self._device["platform"] == 1: + self._mqtt_client.publish_command( + self._device["product_id"], + self._device["device_id"], + QUERY_DEVICE_STATE_COMMAND, + ) + elif self._device["platform"] == 2: + state = DeyeDeviceState(await self._cloud_api.get_fog_platform_device_properties(self._device["device_id"])) + self.update_device_state(state) self.cancel_polling = async_call_later(self.hass, 10, self.poll_device_state) def mute_subscription_for_a_while(self) -> None: @@ -185,10 +192,14 @@ def unmute(now: datetime) -> None: self.subscription_muted = async_call_later(self.hass, 10, unmute) - def publish_command(self, command: DeyeDeviceCommand) -> None: - """Publish a MQTT command to this device.""" - self._mqtt_client.publish_command( - self._device["product_id"], self._device["device_id"], command.bytes() - ) + async def publish_command(self, command: DeyeDeviceCommand) -> None: + if self._device["platform"] == 1: + """Publish a MQTT command to this device.""" + self._mqtt_client.publish_command( + self._device["product_id"], self._device["device_id"], command.bytes() + ) + elif self._device["platform"] == 2: + """Publish a MQTT command to this device.""" + await self._cloud_api.set_fog_platform_device_properties(self._device["device_id"], command.json()) self.async_write_ha_state() self.mute_subscription_for_a_while() diff --git a/custom_components/deye_dehumidifier/binary_sensor.py b/custom_components/deye_dehumidifier/binary_sensor.py index 7c5e572..0330005 100644 --- a/custom_components/deye_dehumidifier/binary_sensor.py +++ b/custom_components/deye_dehumidifier/binary_sensor.py @@ -13,8 +13,9 @@ from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo +from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN async def async_setup_entry( @@ -28,8 +29,8 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: async_add_entities( [ - DeyeWaterTankBinarySensor(device, data[DATA_MQTT_CLIENT]), - DeyeDefrostingBinarySensor(device, data[DATA_MQTT_CLIENT]), + DeyeWaterTankBinarySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), + DeyeDefrostingBinarySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), ] ) @@ -42,10 +43,10 @@ class DeyeWaterTankBinarySensor(DeyeEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient + self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: """Initialize the sensor.""" - super().__init__(device, mqtt_client) + super().__init__(device, mqtt_client, cloud_api) assert self._attr_unique_id is not None self._attr_unique_id += "-water-tank" self.entity_id = f"binary_sensor.{self.entity_id_base}_water_tank" @@ -64,10 +65,10 @@ class DeyeDefrostingBinarySensor(DeyeEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient + self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: """Initialize the sensor.""" - super().__init__(device, mqtt_client) + super().__init__(device, mqtt_client, cloud_api) assert self._attr_unique_id is not None self._attr_unique_id += "-defrosting" self.entity_id = f"binary_sensor.{self.entity_id_base}_defrosting" diff --git a/custom_components/deye_dehumidifier/fan.py b/custom_components/deye_dehumidifier/fan.py index 3008eee..b80f00e 100644 --- a/custom_components/deye_dehumidifier/fan.py +++ b/custom_components/deye_dehumidifier/fan.py @@ -16,8 +16,9 @@ from libdeye.types import DeyeApiResponseDeviceInfo, DeyeFanSpeed from libdeye.utils import get_product_feature_config +from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN async def async_setup_entry( @@ -31,7 +32,7 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: feature_config = get_product_feature_config(device["product_id"]) if len(feature_config["fan_speed"]) > 0: - async_add_entities([DeyeFan(device, data[DATA_MQTT_CLIENT])]) + async_add_entities([DeyeFan(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) class DeyeFan(DeyeEntity, FanEntity): @@ -40,10 +41,10 @@ class DeyeFan(DeyeEntity, FanEntity): _attr_translation_key = "fan" def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient + self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: """Initialize the fan entity.""" - super().__init__(device, mqtt_client) + super().__init__(device, mqtt_client, cloud_api) assert self._attr_unique_id is not None self._attr_unique_id += "-fan" self.entity_id = f"fan.{self.entity_id_base}_fan" @@ -76,7 +77,7 @@ def percentage(self) -> int: async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" self.device_state.oscillating_switch = oscillating - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" @@ -85,7 +86,7 @@ async def async_set_percentage(self, percentage: int) -> None: self.device_state.fan_speed = percentage_to_ordered_list_item( self._named_fan_speeds, percentage ) - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) async def async_turn_on( self, @@ -99,9 +100,9 @@ async def async_turn_on( self.device_state.fan_speed = percentage_to_ordered_list_item( self._named_fan_speeds, percentage ) - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" self.device_state.power_switch = False - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) diff --git a/custom_components/deye_dehumidifier/humidifier.py b/custom_components/deye_dehumidifier/humidifier.py index d690533..3b56575 100644 --- a/custom_components/deye_dehumidifier/humidifier.py +++ b/custom_components/deye_dehumidifier/humidifier.py @@ -17,8 +17,9 @@ from libdeye.types import DeyeApiResponseDeviceInfo, DeyeDeviceMode from libdeye.utils import get_product_feature_config +from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN MODE_MANUAL = "manual" MODE_AIR_PURIFIER = "air_purifier" @@ -34,7 +35,7 @@ async def async_setup_entry( data = hass.data[DOMAIN][config_entry.entry_id] for device in data[DATA_DEVICE_LIST]: - async_add_entities([DeyeDehumidifier(device, data[DATA_MQTT_CLIENT])]) + async_add_entities([DeyeDehumidifier(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) class DeyeDehumidifier(DeyeEntity, HumidifierEntity): @@ -45,10 +46,10 @@ class DeyeDehumidifier(DeyeEntity, HumidifierEntity): _attr_name = None # Inherits from device name def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient + self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: """Initialize the humidifier entity.""" - super().__init__(device, mqtt_client) + super().__init__(device, mqtt_client, cloud_api) assert self._attr_unique_id is not None self._attr_unique_id += "-dehumidifier" self.entity_id = f"humidifier.{self.entity_id_base}_dehumidifier" @@ -102,22 +103,22 @@ def action(self) -> str: async def async_set_mode(self, mode: str) -> None: """Set new working mode.""" self.device_state.mode = hass_mode_to_deye_mode(mode) - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) async def async_set_humidity(self, humidity: int) -> None: """Set new target humidity.""" self.device_state.target_humidity = humidity - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" self.device_state.power_switch = True - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" self.device_state.power_switch = False - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) def deye_mode_to_hass_mode(mode: DeyeDeviceMode) -> str: diff --git a/custom_components/deye_dehumidifier/manifest.json b/custom_components/deye_dehumidifier/manifest.json index 48de9db..e7a0987 100644 --- a/custom_components/deye_dehumidifier/manifest.json +++ b/custom_components/deye_dehumidifier/manifest.json @@ -6,6 +6,6 @@ "documentation": "https://github.com/stackia/ha-deye-dehumidifier", "iot_class": "cloud_push", "issue_tracker": "https://github.com/stackia/ha-deye-dehumidifier/issues", - "requirements": ["libdeye==1.2.0"], - "version": "1.5.0" + "requirements": ["libdeye==1.3.0"], + "version": "1.6.0" } diff --git a/custom_components/deye_dehumidifier/sensor.py b/custom_components/deye_dehumidifier/sensor.py index b49c98c..684d7f6 100644 --- a/custom_components/deye_dehumidifier/sensor.py +++ b/custom_components/deye_dehumidifier/sensor.py @@ -14,8 +14,9 @@ from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo +from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN async def async_setup_entry( @@ -29,8 +30,8 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: async_add_entities( [ - DeyeHumiditySensor(device, data[DATA_MQTT_CLIENT]), - DeyeTemperatureSensor(device, data[DATA_MQTT_CLIENT]), + DeyeHumiditySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), + DeyeTemperatureSensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), ] ) @@ -44,10 +45,10 @@ class DeyeHumiditySensor(DeyeEntity, SensorEntity): _attr_native_unit_of_measurement = PERCENTAGE def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient + self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: """Initialize the sensor.""" - super().__init__(device, mqtt_client) + super().__init__(device, mqtt_client, cloud_api) assert self._attr_unique_id is not None self._attr_unique_id += "-humidity" self.entity_id = f"sensor.{self.entity_id_base}_humidity" @@ -67,10 +68,10 @@ class DeyeTemperatureSensor(DeyeEntity, SensorEntity): _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient + self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: """Initialize the sensor.""" - super().__init__(device, mqtt_client) + super().__init__(device, mqtt_client, cloud_api) assert self._attr_unique_id is not None self._attr_unique_id += "-temperature" self.entity_id = f"sensor.{self.entity_id_base}_temperature" diff --git a/custom_components/deye_dehumidifier/switch.py b/custom_components/deye_dehumidifier/switch.py index 17da427..10f72c8 100644 --- a/custom_components/deye_dehumidifier/switch.py +++ b/custom_components/deye_dehumidifier/switch.py @@ -13,8 +13,9 @@ from libdeye.types import DeyeApiResponseDeviceInfo from libdeye.utils import get_product_feature_config +from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN async def async_setup_entry( @@ -26,12 +27,12 @@ async def async_setup_entry( data = hass.data[DOMAIN][config_entry.entry_id] for device in data[DATA_DEVICE_LIST]: - async_add_entities([DeyeChildLockSwitch(device, data[DATA_MQTT_CLIENT])]) + async_add_entities([DeyeChildLockSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) feature_config = get_product_feature_config(device["product_id"]) if feature_config["anion"]: - async_add_entities([DeyeAnionSwitch(device, data[DATA_MQTT_CLIENT])]) + async_add_entities([DeyeAnionSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) if feature_config["water_pump"]: - async_add_entities([DeyeWaterPumpSwitch(device, data[DATA_MQTT_CLIENT])]) + async_add_entities([DeyeWaterPumpSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) class DeyeChildLockSwitch(DeyeEntity, SwitchEntity): @@ -42,10 +43,10 @@ class DeyeChildLockSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient + self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: """Initialize the switch.""" - super().__init__(device, mqtt_client) + super().__init__(device, mqtt_client, cloud_api) assert self._attr_unique_id is not None self._attr_unique_id += "-child-lock" self.entity_id = f"switch.{self.entity_id_base}_child_lock" @@ -58,12 +59,12 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the child lock on.""" self.device_state.child_lock_switch = True - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the child lock off.""" self.device_state.child_lock_switch = False - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) class DeyeAnionSwitch(DeyeEntity, SwitchEntity): @@ -74,10 +75,10 @@ class DeyeAnionSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient + self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: """Initialize the switch.""" - super().__init__(device, mqtt_client) + super().__init__(device, mqtt_client, cloud_api) assert self._attr_unique_id is not None self._attr_unique_id += "-anion" self.entity_id = f"switch.{self.entity_id_base}_anion" @@ -90,12 +91,12 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the anion switch on.""" self.device_state.anion_switch = True - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the anion switch off.""" self.device_state.anion_switch = False - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) class DeyeWaterPumpSwitch(DeyeEntity, SwitchEntity): @@ -106,10 +107,10 @@ class DeyeWaterPumpSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient + self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: """Initialize the switch.""" - super().__init__(device, mqtt_client) + super().__init__(device, mqtt_client, cloud_api) assert self._attr_unique_id is not None self._attr_unique_id += "-water-pump" self.entity_id = f"switch.{self.entity_id_base}_water_pump" @@ -122,9 +123,9 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the water pump on.""" self.device_state.water_pump_switch = True - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the water pump off.""" self.device_state.water_pump_switch = False - self.publish_command(self.device_state.to_command()) + await self.publish_command(self.device_state.to_command()) From 33a5f0c84805e0bc072c24531424abfd288912c0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 02:58:08 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../deye_dehumidifier/__init__.py | 15 ++++++-- .../deye_dehumidifier/binary_sensor.py | 22 ++++++++---- custom_components/deye_dehumidifier/fan.py | 13 ++++--- .../deye_dehumidifier/humidifier.py | 13 ++++--- custom_components/deye_dehumidifier/sensor.py | 22 ++++++++---- custom_components/deye_dehumidifier/switch.py | 35 ++++++++++++++----- 6 files changed, 89 insertions(+), 31 deletions(-) diff --git a/custom_components/deye_dehumidifier/__init__.py b/custom_components/deye_dehumidifier/__init__.py index c54eea9..a05e146 100644 --- a/custom_components/deye_dehumidifier/__init__.py +++ b/custom_components/deye_dehumidifier/__init__.py @@ -103,7 +103,10 @@ class DeyeEntity(Entity): """Initiate Deye Base Class.""" def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the instance.""" self._device = device @@ -177,7 +180,11 @@ async def poll_device_state(self, now: datetime | None = None) -> None: QUERY_DEVICE_STATE_COMMAND, ) elif self._device["platform"] == 2: - state = DeyeDeviceState(await self._cloud_api.get_fog_platform_device_properties(self._device["device_id"])) + state = DeyeDeviceState( + await self._cloud_api.get_fog_platform_device_properties( + self._device["device_id"] + ) + ) self.update_device_state(state) self.cancel_polling = async_call_later(self.hass, 10, self.poll_device_state) @@ -200,6 +207,8 @@ async def publish_command(self, command: DeyeDeviceCommand) -> None: ) elif self._device["platform"] == 2: """Publish a MQTT command to this device.""" - await self._cloud_api.set_fog_platform_device_properties(self._device["device_id"], command.json()) + await self._cloud_api.set_fog_platform_device_properties( + self._device["device_id"], command.json() + ) self.async_write_ha_state() self.mute_subscription_for_a_while() diff --git a/custom_components/deye_dehumidifier/binary_sensor.py b/custom_components/deye_dehumidifier/binary_sensor.py index 0330005..254758a 100644 --- a/custom_components/deye_dehumidifier/binary_sensor.py +++ b/custom_components/deye_dehumidifier/binary_sensor.py @@ -10,12 +10,12 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN async def async_setup_entry( @@ -29,8 +29,12 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: async_add_entities( [ - DeyeWaterTankBinarySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), - DeyeDefrostingBinarySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), + DeyeWaterTankBinarySensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), + DeyeDefrostingBinarySensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), ] ) @@ -43,7 +47,10 @@ class DeyeWaterTankBinarySensor(DeyeEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) @@ -65,7 +72,10 @@ class DeyeDefrostingBinarySensor(DeyeEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) diff --git a/custom_components/deye_dehumidifier/fan.py b/custom_components/deye_dehumidifier/fan.py index b80f00e..5b17c6b 100644 --- a/custom_components/deye_dehumidifier/fan.py +++ b/custom_components/deye_dehumidifier/fan.py @@ -12,13 +12,13 @@ ordered_list_item_to_percentage, percentage_to_ordered_list_item, ) +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo, DeyeFanSpeed from libdeye.utils import get_product_feature_config -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN async def async_setup_entry( @@ -32,7 +32,9 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: feature_config = get_product_feature_config(device["product_id"]) if len(feature_config["fan_speed"]) > 0: - async_add_entities([DeyeFan(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [DeyeFan(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])] + ) class DeyeFan(DeyeEntity, FanEntity): @@ -41,7 +43,10 @@ class DeyeFan(DeyeEntity, FanEntity): _attr_translation_key = "fan" def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the fan entity.""" super().__init__(device, mqtt_client, cloud_api) diff --git a/custom_components/deye_dehumidifier/humidifier.py b/custom_components/deye_dehumidifier/humidifier.py index 3b56575..3f25321 100644 --- a/custom_components/deye_dehumidifier/humidifier.py +++ b/custom_components/deye_dehumidifier/humidifier.py @@ -13,13 +13,13 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo, DeyeDeviceMode from libdeye.utils import get_product_feature_config -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN MODE_MANUAL = "manual" MODE_AIR_PURIFIER = "air_purifier" @@ -35,7 +35,9 @@ async def async_setup_entry( data = hass.data[DOMAIN][config_entry.entry_id] for device in data[DATA_DEVICE_LIST]: - async_add_entities([DeyeDehumidifier(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [DeyeDehumidifier(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])] + ) class DeyeDehumidifier(DeyeEntity, HumidifierEntity): @@ -46,7 +48,10 @@ class DeyeDehumidifier(DeyeEntity, HumidifierEntity): _attr_name = None # Inherits from device name def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the humidifier entity.""" super().__init__(device, mqtt_client, cloud_api) diff --git a/custom_components/deye_dehumidifier/sensor.py b/custom_components/deye_dehumidifier/sensor.py index 684d7f6..14ca940 100644 --- a/custom_components/deye_dehumidifier/sensor.py +++ b/custom_components/deye_dehumidifier/sensor.py @@ -11,12 +11,12 @@ from homeassistant.const import PERCENTAGE, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN async def async_setup_entry( @@ -30,8 +30,12 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: async_add_entities( [ - DeyeHumiditySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), - DeyeTemperatureSensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), + DeyeHumiditySensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), + DeyeTemperatureSensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), ] ) @@ -45,7 +49,10 @@ class DeyeHumiditySensor(DeyeEntity, SensorEntity): _attr_native_unit_of_measurement = PERCENTAGE def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) @@ -68,7 +75,10 @@ class DeyeTemperatureSensor(DeyeEntity, SensorEntity): _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) diff --git a/custom_components/deye_dehumidifier/switch.py b/custom_components/deye_dehumidifier/switch.py index 10f72c8..20d5090 100644 --- a/custom_components/deye_dehumidifier/switch.py +++ b/custom_components/deye_dehumidifier/switch.py @@ -9,13 +9,13 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo from libdeye.utils import get_product_feature_config -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN async def async_setup_entry( @@ -27,12 +27,22 @@ async def async_setup_entry( data = hass.data[DOMAIN][config_entry.entry_id] for device in data[DATA_DEVICE_LIST]: - async_add_entities([DeyeChildLockSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [DeyeChildLockSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])] + ) feature_config = get_product_feature_config(device["product_id"]) if feature_config["anion"]: - async_add_entities([DeyeAnionSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [DeyeAnionSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])] + ) if feature_config["water_pump"]: - async_add_entities([DeyeWaterPumpSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [ + DeyeWaterPumpSwitch( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ) + ] + ) class DeyeChildLockSwitch(DeyeEntity, SwitchEntity): @@ -43,7 +53,10 @@ class DeyeChildLockSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the switch.""" super().__init__(device, mqtt_client, cloud_api) @@ -75,7 +88,10 @@ class DeyeAnionSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the switch.""" super().__init__(device, mqtt_client, cloud_api) @@ -107,7 +123,10 @@ class DeyeWaterPumpSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the switch.""" super().__init__(device, mqtt_client, cloud_api) From 6efae3018ef22ebd45d056bb797063c6365ee475 Mon Sep 17 00:00:00 2001 From: sususweet Date: Tue, 17 Dec 2024 00:29:22 +0800 Subject: [PATCH 3/8] refactor: use `DataUpdateCoordinator` for device state polling to reduce parallel requests to Deye official cloud server --- .../deye_dehumidifier/__init__.py | 268 +++++++++++++----- .../deye_dehumidifier/binary_sensor.py | 2 +- custom_components/deye_dehumidifier/const.py | 1 + .../deye_dehumidifier/data_coordinator.py | 97 +++++++ custom_components/deye_dehumidifier/fan.py | 23 +- .../deye_dehumidifier/humidifier.py | 75 ++++- custom_components/deye_dehumidifier/sensor.py | 3 +- custom_components/deye_dehumidifier/switch.py | 21 +- 8 files changed, 385 insertions(+), 105 deletions(-) create mode 100644 custom_components/deye_dehumidifier/data_coordinator.py diff --git a/custom_components/deye_dehumidifier/__init__.py b/custom_components/deye_dehumidifier/__init__.py index c54eea9..bbc2e96 100644 --- a/custom_components/deye_dehumidifier/__init__.py +++ b/custom_components/deye_dehumidifier/__init__.py @@ -2,25 +2,25 @@ from __future__ import annotations -from datetime import datetime +import logging from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform +from homeassistant.const import Platform, STATE_ON from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import DeviceInfo, Entity -from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.update_coordinator import CoordinatorEntity from libdeye.cloud_api import ( DeyeCloudApi, DeyeCloudApiCannotConnectError, DeyeCloudApiInvalidAuthError, ) -from libdeye.const import QUERY_DEVICE_STATE_COMMAND + from libdeye.device_state_command import DeyeDeviceCommand, DeyeDeviceState from libdeye.mqtt_client import DeyeMqttClient -from libdeye.types import DeyeApiResponseDeviceInfo - +from libdeye.types import DeyeApiResponseDeviceInfo, DeyeFanSpeed, DeyeDeviceMode +from .data_coordinator import DeyeDataUpdateCoordinator from .const import ( CONF_AUTH_TOKEN, CONF_PASSWORD, @@ -28,6 +28,7 @@ DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, + DATA_COORDINATOR, DOMAIN, MANUFACTURER, ) @@ -40,6 +41,7 @@ Platform.FAN, ] +_LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Deye Dehumidifier from a config entry.""" @@ -72,6 +74,11 @@ def on_auth_token_refreshed(auth_token: str) -> None: await cloud_api.get_device_list(), ) ) + for device in device_list: + coordinator = DeyeDataUpdateCoordinator(hass, device, mqtt_client, cloud_api) + device[DATA_COORDINATOR] = coordinator + await device[DATA_COORDINATOR].async_config_entry_first_refresh() + except DeyeCloudApiInvalidAuthError as err: raise ConfigEntryAuthFailed from err except DeyeCloudApiCannotConnectError as err: @@ -99,13 +106,36 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -class DeyeEntity(Entity): +class DeyeEntity(CoordinatorEntity, Entity): """Initiate Deye Base Class.""" def __init__( self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: + # async def state_changed_listener(entity_id, old_state, new_state): + # _LOGGER.error(entity_id) + # _LOGGER.error(old_state) + # _LOGGER.error(new_state) + # if entity_id.endswith("_child_lock"): + # self.device_state.child_lock_switch = new_state.state is STATE_ON + # elif entity_id.endswith("_anion"): + # self.device_state.anion_switch = new_state.state is STATE_ON + # command = self.device_state.to_command() + # _LOGGER.error("old2" + json.dumps(command.json())) + # if self._device["platform"] == 1: + # """Publish a MQTT command to this device.""" + # self._mqtt_client.publish_command( + # self._device["product_id"], self._device["device_id"], command.bytes() + # ) + # elif self._device["platform"] == 2: + # """Post a Remote command to this device.""" + # await self._cloud_api.set_fog_platform_device_properties(self._device["device_id"], command.json()) + # # 更新设备实体的状态 + # # self.async_schedule_update_ha_state(True) + """Initialize the instance.""" + self.coordinator = device[DATA_COORDINATOR] + super().__init__(self.coordinator) self._device = device self._mqtt_client = mqtt_client self._cloud_api = cloud_api @@ -128,78 +158,168 @@ def __init__( self.device_state = DeyeDeviceState( "1411000000370000000000000000003C3C0000000000" # 20°C/60%RH as the default state ) + remove_handle = self.coordinator.async_add_listener(self._handle_coordinator_update) + self.async_on_remove(remove_handle) - def update_device_availability(self, available: bool) -> None: - """Will be called when received new availability status.""" - if self.subscription_muted: - return - self._attr_available = available + async def publish_command_async(self, attribute, value): + """获取所有实体的状态。""" self.async_write_ha_state() + self.hass.bus.fire('call_humidifier_method', {'prop': attribute, 'value': value}) + await self.coordinator.async_request_refresh() - def update_device_state(self, state: DeyeDeviceState) -> None: - """Will be called when received new DeyeDeviceState.""" - if self.subscription_muted: - return - self.device_state = state - self.async_write_ha_state() + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self.device_state = self.coordinator.data + super()._handle_coordinator_update() async def async_added_to_hass(self) -> None: """When entity is added to Home Assistant.""" - if self._device["platform"] == 1: - self.async_on_remove( - self._mqtt_client.subscribe_availability_change( - self._device["product_id"], - self._device["device_id"], - self.update_device_availability, - ) - ) - self.async_on_remove( - self._mqtt_client.subscribe_state_change( - self._device["product_id"], - self._device["device_id"], - self.update_device_state, - ) - ) + pass + # if self._device["platform"] == 1: + # self.async_on_remove( + # self._mqtt_client.subscribe_availability_change( + # self._device["product_id"], + # self._device["device_id"], + # self.update_device_availability, + # ) + # ) + # self.async_on_remove( + # self._mqtt_client.subscribe_state_change( + # self._device["product_id"], + # self._device["device_id"], + # self.update_device_state, + # ) + # ) + # + # await self.poll_device_state() + # self.hass.bus.async_fire('humidifier_state_changed', {'state': json.dumps(self.device_state.__dict__)}) + # self.hass.helpers.event.async_track_time_interval( + # self.put_device_state, timedelta(seconds=5) + # ) + #self.async_on_remove(self.cancel_polling) - await self.poll_device_state() - self.async_on_remove(self.cancel_polling) + # def update_device_availability(self, available: bool) -> None: + # """Will be called when received new availability status.""" + # if self.subscription_muted: + # return + # self._attr_available = available + # self.async_write_ha_state() + # + # def update_device_state(self, state: DeyeDeviceState) -> None: + # """Will be called when received new DeyeDeviceState.""" + # # if self.entity_id_base == 'deye_849dc2621ea5': + # # _LOGGER.error("local:" + json.dumps(self.device_state.to_command().json())) + # # _LOGGER.error("cloud:" + json.dumps(state.to_command().json())) + # if self.subscription_muted: + # return + # self.device_state = state + # self.hass.bus.async_fire('humidifier_state_changed', {'state': json.dumps(self.device_state.__dict__)}) + # self.async_write_ha_state() - @callback - async def poll_device_state(self, now: datetime | None = None) -> None: - """ - Some Deye devices have a very long heartbeat period. So polling is still necessary to get the latest state as - quickly as possible. - """ - if self._device["platform"] == 1: - self._mqtt_client.publish_command( - self._device["product_id"], - self._device["device_id"], - QUERY_DEVICE_STATE_COMMAND, - ) - elif self._device["platform"] == 2: - state = DeyeDeviceState(await self._cloud_api.get_fog_platform_device_properties(self._device["device_id"])) - self.update_device_state(state) - self.cancel_polling = async_call_later(self.hass, 10, self.poll_device_state) - - def mute_subscription_for_a_while(self) -> None: - """Mute subscription for a while to avoid state bouncing.""" - if self.subscription_muted: - self.subscription_muted() - - @callback - def unmute(now: datetime) -> None: - self.subscription_muted = None - - self.subscription_muted = async_call_later(self.hass, 10, unmute) - - async def publish_command(self, command: DeyeDeviceCommand) -> None: - if self._device["platform"] == 1: - """Publish a MQTT command to this device.""" - self._mqtt_client.publish_command( - self._device["product_id"], self._device["device_id"], command.bytes() - ) - elif self._device["platform"] == 2: - """Publish a MQTT command to this device.""" - await self._cloud_api.set_fog_platform_device_properties(self._device["device_id"], command.json()) - self.async_write_ha_state() - self.mute_subscription_for_a_while() + # @callback + # async def poll_device_state(self, now: datetime | None = None) -> None: + # + # self.cancel_polling = async_call_later(self.hass, 60, self.poll_device_state) + + # def mute_subscription_for_a_while(self) -> None: + # """Mute subscription for a while to avoid state bouncing.""" + # if self.hass.states.get(f"refresh.{self.entity_id_base}_dehumidifier") is False: + # self.subscription_muted() + # + # @callback + # def unmute(now: datetime) -> None: + # self.hass.states.async_set(f"refresh.{self.entity_id_base}_dehumidifier", True) + # + # self.subscription_muted = async_call_later(self.hass, 20, unmute) + + # def set_dev_state(self, state): + # _LOGGER.error(state) + # self._dev_state = state + + # def mute_command_for_a_while(self) -> None: + # """Mute subscription for a while to avoid state bouncing.""" + # if self.command_muted: + # self.command_muted() + # + # @callback + # def unmute(now: datetime) -> None: + # self.command_muted = None + # + # self.command_muted = async_call_later(self.hass, 10, unmute) + # async def publish_command(self, prop, value) -> None: + # self.device_state_change_list[prop] = value + # _LOGGER.error("old" + json.dumps(self.device_state.to_command().json())) + # set_class_variable(self.device_state, prop, value) + # command = self.device_state.to_command() + # _LOGGER.error("old2" + json.dumps(command.json())) + # statatatt = self.hass.states.get(f"device.{self.entity_id_base}_state").attributes.get("state") + # _LOGGER.error( + # statatatt + # ) + # _LOGGER.error( + # statatatt.to_command().json() + # ) + # _LOGGER.error( + # self.hass.states.get(f"fan.{self.entity_id_base}_fan").attributes + # ) + + + + # feature_config = get_product_feature_config(self._device["product_id"]) + # fan = self.hass.states.get(f"fan.{self.entity_id_base}_fan") + # fan_speed = DeyeFanSpeed.STOPPED + # oscillating_switch = False + # if fan is not None: + # if len(feature_config["fan_speed"]) > 0: + # fan_speed = percentage_to_ordered_list_item( + # feature_config["fan_speed"], fan.attributes.get("percentage") + # ) + # if feature_config["oscillating"]: + # oscillating_switch = fan.attributes.get("oscillating") + # + # dehumidifier = self.hass.states.get(f"humidifier.{self.entity_id_base}_dehumidifier") + # target_humidity = 60 + # if dehumidifier is not None: + # if dehumidifier.attributes.get("humidity") is not None: + # target_humidity = dehumidifier.attributes.get("humidity") + # + # command = DeyeDeviceCommand( + # self.hass.states.is_state(f"switch.{self.entity_id_base}_anion", STATE_ON), + # self.hass.states.is_state(f"switch.{self.entity_id_base}_water_pump", STATE_ON), + # self.hass.states.is_state(f"fan.{self.entity_id_base}_fan", STATE_ON), + # oscillating_switch, + # self.hass.states.is_state(f"switch.{self.entity_id_base}_child_lock", STATE_ON), + # fan_speed, + # self.device_state.mode, + # target_humidity, + # ) + # set_class_variable(command, prop, value) + + + + # _LOGGER.error("anion" + str()) + # _LOGGER.error("child_lock" + str(self.hass.states.get(f"{SwitchDeviceClass.SWITCH}.{self.entity_id_base}_child_lock"))) + # _LOGGER.error("muted" + str(command_muted)) + # # if command_muted: + # # async_call_later(self.hass, 5, self.publish_command) + # # else: + # _LOGGER.error("muted" + str(command_muted)) + # # command_muted = True + # _LOGGER.error("muted" + str(command_muted)) + # _LOGGER.error(json.dumps(self.device_state.to_command().json())) + # set_class_variable(self.device_state, prop, value) + # command = self.device_state.to_command() + # _LOGGER.error("new" + json.dumps(command.json())) + # if self._device["platform"] == 1: + # """Publish a MQTT command to this device.""" + # self._mqtt_client.publish_command( + # self._device["product_id"], self._device["device_id"], command.bytes() + # ) + # elif self._device["platform"] == 2: + # """Post a Remote command to this device.""" + # await self._cloud_api.set_fog_platform_device_properties(self._device["device_id"], command.json()) + # self.async_write_ha_state() + + # command_muted = False + # _LOGGER.error("muted" + str(command_muted)) diff --git a/custom_components/deye_dehumidifier/binary_sensor.py b/custom_components/deye_dehumidifier/binary_sensor.py index 0330005..3b45211 100644 --- a/custom_components/deye_dehumidifier/binary_sensor.py +++ b/custom_components/deye_dehumidifier/binary_sensor.py @@ -15,7 +15,7 @@ from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR async def async_setup_entry( diff --git a/custom_components/deye_dehumidifier/const.py b/custom_components/deye_dehumidifier/const.py index 1914dd0..bdccf93 100644 --- a/custom_components/deye_dehumidifier/const.py +++ b/custom_components/deye_dehumidifier/const.py @@ -7,4 +7,5 @@ DATA_CLOUD_API = "cloud_api" DATA_MQTT_CLIENT = "mqtt_client" DATA_DEVICE_LIST = "device_list" +DATA_COORDINATOR = "coordinator" MANUFACTURER = "Ningbo Deye Technology Co., Ltd" diff --git a/custom_components/deye_dehumidifier/data_coordinator.py b/custom_components/deye_dehumidifier/data_coordinator.py new file mode 100644 index 0000000..e7db7c3 --- /dev/null +++ b/custom_components/deye_dehumidifier/data_coordinator.py @@ -0,0 +1,97 @@ +import asyncio +import logging +from datetime import timedelta, datetime + +from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from libdeye.device_state_command import DeyeDeviceState +from libdeye.const import QUERY_DEVICE_STATE_COMMAND + +_LOGGER = logging.getLogger(__name__) + + +class DeyeDataUpdateCoordinator(DataUpdateCoordinator): + def __init__(self, hass, device, mqtt_client, cloud_api): + super().__init__( + hass, + _LOGGER, + name="deye_data_update_coordinator", + update_method=self.poll_device_state, + update_interval=timedelta(seconds=10), + ) + self._mqtt_client = mqtt_client + self._cloud_api = cloud_api + self.subscription_muted: CALLBACK_TYPE | None = None + + self.data = DeyeDeviceState( + "1411000000370000000000000000003C3C0000000000" # 20°C/60%RH as the default state + ) + self._device = device + """When entity is added to Home Assistant.""" + if self._device["platform"] == 1: + + self._mqtt_client.subscribe_availability_change( + self._device["product_id"], + self._device["device_id"], + self.update_device_availability, + ) + self._mqtt_client.subscribe_state_change( + self._device["product_id"], + self._device["device_id"], + self.update_device_state, + ) + + self.receive_queue = asyncio.Queue() + + def mute_subscription_for_a_while(self) -> None: + """Mute subscription for a while to avoid state bouncing.""" + if self.subscription_muted: + self.subscription_muted() + + @callback + def unmute(now: datetime) -> None: + self.subscription_muted = None + + self.subscription_muted = async_call_later(self.hass, 20, unmute) + + def update_device_availability(self, available: bool) -> None: + """Will be called when received new availability status.""" + # if self.hass.states.get(f"refresh.{self.entity_id_base}_dehumidifier") is False: + # return + _LOGGER.error(available) + # self.async_set_updated_data(available) + + def update_device_state(self, state: DeyeDeviceState) -> None: + """Will be called when received new DeyeDeviceState.""" + # if self.hass.states.get(f"refresh.{self.entity_id_base}_dehumidifier") is False: + # return + self.receive_queue.put_nowait(state) + # self.async_set_updated_data(state) + + async def async_request_refresh(self) -> None: + self.mute_subscription_for_a_while() + await super().async_request_refresh() + + async def poll_device_state(self) -> DeyeDeviceState: + """ + Some Deye devices have a very long heartbeat period. So polling is still necessary to get the latest state as + quickly as possible. + """ + # _LOGGER.error("poll_device_state called: " + str(self._device["product_id"])) + if self.subscription_muted: + return self.data + + if self._device["platform"] == 1: + self._mqtt_client.publish_command( + self._device["product_id"], + self._device["device_id"], + QUERY_DEVICE_STATE_COMMAND, + ) + response = await asyncio.wait_for(self.receive_queue.get(), timeout=10) # 设置超时时间 + #_LOGGER.error(response.to_command().json()) + return response + elif self._device["platform"] == 2: + response = DeyeDeviceState(await self._cloud_api.get_fog_platform_device_properties(self._device["device_id"])) + # _LOGGER.error(response.to_command().json()) + return response diff --git a/custom_components/deye_dehumidifier/fan.py b/custom_components/deye_dehumidifier/fan.py index b80f00e..13ef1a8 100644 --- a/custom_components/deye_dehumidifier/fan.py +++ b/custom_components/deye_dehumidifier/fan.py @@ -17,8 +17,8 @@ from libdeye.utils import get_product_feature_config from libdeye.cloud_api import DeyeCloudApi -from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from . import DeyeEntity, DeyeDataUpdateCoordinator +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR async def async_setup_entry( @@ -77,16 +77,17 @@ def percentage(self) -> int: async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" self.device_state.oscillating_switch = oscillating - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('oscillating_switch', oscillating) async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" if percentage == 0: await self.async_turn_off() - self.device_state.fan_speed = percentage_to_ordered_list_item( + fan_speed = int(percentage_to_ordered_list_item( self._named_fan_speeds, percentage - ) - await self.publish_command(self.device_state.to_command()) + )) + self.device_state.fan_speed = fan_speed + await self.publish_command_async('fan_speed', fan_speed) async def async_turn_on( self, @@ -96,13 +97,13 @@ async def async_turn_on( ) -> None: """Turn on the fan.""" self.device_state.power_switch = True + await self.publish_command_async('power_switch', True) if percentage is not None: - self.device_state.fan_speed = percentage_to_ordered_list_item( - self._named_fan_speeds, percentage - ) - await self.publish_command(self.device_state.to_command()) + fan_speed = int(percentage_to_ordered_list_item(self._named_fan_speeds, percentage)) + self.device_state.fan_speed = fan_speed + await self.publish_command_async('fan_speed', fan_speed) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" self.device_state.power_switch = False - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('power_switch', False) diff --git a/custom_components/deye_dehumidifier/humidifier.py b/custom_components/deye_dehumidifier/humidifier.py index 3b56575..355cdef 100644 --- a/custom_components/deye_dehumidifier/humidifier.py +++ b/custom_components/deye_dehumidifier/humidifier.py @@ -2,6 +2,9 @@ from __future__ import annotations +import json +import logging +from datetime import datetime, timedelta from typing import Any from homeassistant.components.humidifier import ( @@ -11,15 +14,17 @@ ) from homeassistant.components.humidifier.const import MODE_AUTO, MODE_SLEEP from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback, CALLBACK_TYPE from homeassistant.helpers.entity_platform import AddEntitiesCallback from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo, DeyeDeviceMode from libdeye.utils import get_product_feature_config from libdeye.cloud_api import DeyeCloudApi -from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN + +from libdeye.device_state_command import DeyeDeviceState +from . import DeyeEntity, DeyeDataUpdateCoordinator +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR MODE_MANUAL = "manual" MODE_AIR_PURIFIER = "air_purifier" @@ -35,7 +40,17 @@ async def async_setup_entry( data = hass.data[DOMAIN][config_entry.entry_id] for device in data[DATA_DEVICE_LIST]: - async_add_entities([DeyeDehumidifier(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + deye_dehumidifier = DeyeDehumidifier(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]) + async_add_entities([deye_dehumidifier]) + + async def call_method(event): + prop = event.data.get('prop') + value = event.data.get('value') + await deye_dehumidifier.publish_command(prop, value) + + hass.bus.async_listen('call_humidifier_method', call_method) + +_LOGGER = logging.getLogger(__name__) class DeyeDehumidifier(DeyeEntity, HumidifierEntity): @@ -51,6 +66,7 @@ def __init__( """Initialize the humidifier entity.""" super().__init__(device, mqtt_client, cloud_api) assert self._attr_unique_id is not None + self.subscription_muted: CALLBACK_TYPE | None = None self._attr_unique_id += "-dehumidifier" self.entity_id = f"humidifier.{self.entity_id_base}_dehumidifier" feature_config = get_product_feature_config(device["product_id"]) @@ -62,6 +78,43 @@ def __init__( self._attr_min_humidity = feature_config["min_target_humidity"] self._attr_max_humidity = feature_config["max_target_humidity"] self._attr_entity_picture = device["product_icon"] + self.data_change_list : dict = dict() + + async def async_added_to_hass(self) -> None: + await super().async_added_to_hass() + self.hass.states.async_set(f"refresh.{self.entity_id_base}_dehumidifier", True) + self.hass.helpers.event.async_track_time_interval( + self.put_device_state, timedelta(seconds=5) + ) + + @callback + async def put_device_state(self, now: datetime | None = None) -> None: + # _LOGGER.error(self.data_change_list) + if len(self.data_change_list.items()) > 0: + command = self.device_state.to_command() + for prop, value in self.data_change_list.items(): + set_class_variable(command, prop, value) + self.data_change_list.clear() + _LOGGER.error("new" + json.dumps(command.json())) + if self._device["platform"] == 1: + """Publish a MQTT command to this device.""" + self._mqtt_client.publish_command( + self._device["product_id"], self._device["device_id"], command.bytes() + ) + elif self._device["platform"] == 2: + """Post a Remote command to this device.""" + await self._cloud_api.set_fog_platform_device_properties(self._device["device_id"], command.json()) + + self.async_write_ha_state() + + async def publish_command(self, prop, value) -> None: + self.hass.states.async_set(f"refresh.{self.entity_id_base}_dehumidifier", False) + self.data_change_list[prop] = value + # self.mute_subscription_for_a_while() + + @property + def get_device_state(self) -> DeyeDeviceState: + return self.device_state @property def target_humidity(self) -> int: @@ -103,22 +156,28 @@ def action(self) -> str: async def async_set_mode(self, mode: str) -> None: """Set new working mode.""" self.device_state.mode = hass_mode_to_deye_mode(mode) - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('mode', hass_mode_to_deye_mode(mode)) async def async_set_humidity(self, humidity: int) -> None: """Set new target humidity.""" self.device_state.target_humidity = humidity - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('target_humidity', humidity) async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" self.device_state.power_switch = True - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('power_switch', True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" self.device_state.power_switch = False - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('power_switch', False) + +def set_class_variable(obj, var_name, new_value): + if hasattr(obj, var_name): + setattr(obj, var_name, new_value) + else: + raise AttributeError(f"'{obj.__class__.__name__}' object has no attribute '{var_name}'") def deye_mode_to_hass_mode(mode: DeyeDeviceMode) -> str: diff --git a/custom_components/deye_dehumidifier/sensor.py b/custom_components/deye_dehumidifier/sensor.py index 684d7f6..9bc16fa 100644 --- a/custom_components/deye_dehumidifier/sensor.py +++ b/custom_components/deye_dehumidifier/sensor.py @@ -16,8 +16,7 @@ from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN - +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR async def async_setup_entry( hass: HomeAssistant, diff --git a/custom_components/deye_dehumidifier/switch.py b/custom_components/deye_dehumidifier/switch.py index 10f72c8..ff24089 100644 --- a/custom_components/deye_dehumidifier/switch.py +++ b/custom_components/deye_dehumidifier/switch.py @@ -2,21 +2,24 @@ from __future__ import annotations +import logging from typing import Any from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo from libdeye.utils import get_product_feature_config from libdeye.cloud_api import DeyeCloudApi -from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from . import DeyeEntity, DeyeDataUpdateCoordinator +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR + +_LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, @@ -59,12 +62,12 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the child lock on.""" self.device_state.child_lock_switch = True - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('child_lock_switch', True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the child lock off.""" self.device_state.child_lock_switch = False - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('child_lock_switch', False) class DeyeAnionSwitch(DeyeEntity, SwitchEntity): @@ -91,12 +94,12 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the anion switch on.""" self.device_state.anion_switch = True - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('anion_switch', True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the anion switch off.""" self.device_state.anion_switch = False - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('anion_switch', False) class DeyeWaterPumpSwitch(DeyeEntity, SwitchEntity): @@ -123,9 +126,9 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the water pump on.""" self.device_state.water_pump_switch = True - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('water_pump_switch', True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the water pump off.""" self.device_state.water_pump_switch = False - await self.publish_command(self.device_state.to_command()) + await self.publish_command_async('water_pump_switch', False) From 54a892e5a054baf7e77de5978d4b03525561f45a Mon Sep 17 00:00:00 2001 From: sususweet Date: Wed, 18 Dec 2024 16:36:36 +0800 Subject: [PATCH 4/8] fix: improve for fan entity to avoid FanEntityFeature warnings. --- custom_components/deye_dehumidifier/fan.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/custom_components/deye_dehumidifier/fan.py b/custom_components/deye_dehumidifier/fan.py index 13ef1a8..a66dceb 100644 --- a/custom_components/deye_dehumidifier/fan.py +++ b/custom_components/deye_dehumidifier/fan.py @@ -17,7 +17,7 @@ from libdeye.utils import get_product_feature_config from libdeye.cloud_api import DeyeCloudApi -from . import DeyeEntity, DeyeDataUpdateCoordinator +from . import DeyeEntity from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR @@ -50,6 +50,10 @@ def __init__( self.entity_id = f"fan.{self.entity_id_base}_fan" feature_config = get_product_feature_config(device["product_id"]) self._attr_supported_features = FanEntityFeature.SET_SPEED + if hasattr(FanEntityFeature, 'TURN_ON'): # v2024.8 + self._attr_supported_features |= FanEntityFeature.TURN_ON + if hasattr(FanEntityFeature, 'TURN_OFF'): + self._attr_supported_features |= FanEntityFeature.TURN_OFF if feature_config["oscillating"]: self._attr_supported_features |= FanEntityFeature.OSCILLATE self._named_fan_speeds = feature_config["fan_speed"] From de23e921e17249354622f62a53599bcd3dfc73e7 Mon Sep 17 00:00:00 2001 From: sususweet Date: Wed, 18 Dec 2024 16:37:07 +0800 Subject: [PATCH 5/8] feat: display device online status --- .../deye_dehumidifier/__init__.py | 188 +----------------- .../deye_dehumidifier/data_coordinator.py | 36 ++-- .../deye_dehumidifier/humidifier.py | 12 +- custom_components/deye_dehumidifier/switch.py | 3 - 4 files changed, 29 insertions(+), 210 deletions(-) diff --git a/custom_components/deye_dehumidifier/__init__.py b/custom_components/deye_dehumidifier/__init__.py index bbc2e96..7f0da52 100644 --- a/custom_components/deye_dehumidifier/__init__.py +++ b/custom_components/deye_dehumidifier/__init__.py @@ -2,11 +2,9 @@ from __future__ import annotations -import logging - from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform, STATE_ON -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import DeviceInfo, Entity @@ -41,7 +39,6 @@ Platform.FAN, ] -_LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Deye Dehumidifier from a config entry.""" @@ -112,27 +109,6 @@ class DeyeEntity(CoordinatorEntity, Entity): def __init__( self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi ) -> None: - # async def state_changed_listener(entity_id, old_state, new_state): - # _LOGGER.error(entity_id) - # _LOGGER.error(old_state) - # _LOGGER.error(new_state) - # if entity_id.endswith("_child_lock"): - # self.device_state.child_lock_switch = new_state.state is STATE_ON - # elif entity_id.endswith("_anion"): - # self.device_state.anion_switch = new_state.state is STATE_ON - # command = self.device_state.to_command() - # _LOGGER.error("old2" + json.dumps(command.json())) - # if self._device["platform"] == 1: - # """Publish a MQTT command to this device.""" - # self._mqtt_client.publish_command( - # self._device["product_id"], self._device["device_id"], command.bytes() - # ) - # elif self._device["platform"] == 2: - # """Post a Remote command to this device.""" - # await self._cloud_api.set_fog_platform_device_properties(self._device["device_id"], command.json()) - # # 更新设备实体的状态 - # # self.async_schedule_update_ha_state(True) - """Initialize the instance.""" self.coordinator = device[DATA_COORDINATOR] super().__init__(self.coordinator) @@ -140,7 +116,7 @@ def __init__( self._mqtt_client = mqtt_client self._cloud_api = cloud_api self._attr_has_entity_name = True - self._attr_available = self._device["online"] + self._device_available = self._device["online"] self._attr_unique_id = self._device["mac"] self.entity_id_base = f'deye_{self._device["mac"].lower()}' # We will override HA generated entity ID self._attr_device_info = DeviceInfo( @@ -150,7 +126,6 @@ def __init__( name=self._device["device_name"], ) self._attr_should_poll = False - self.subscription_muted: CALLBACK_TYPE | None = None # payload from the server sometimes are not a valid string if isinstance(self._device["payload"], str): self.device_state = DeyeDeviceState(self._device["payload"]) @@ -162,164 +137,19 @@ def __init__( self.async_on_remove(remove_handle) async def publish_command_async(self, attribute, value): - """获取所有实体的状态。""" + """Push command to a queue and deal with them together.""" self.async_write_ha_state() self.hass.bus.fire('call_humidifier_method', {'prop': attribute, 'value': value}) await self.coordinator.async_request_refresh() + @property + def available(self): + return self._device_available + @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" self.device_state = self.coordinator.data + self._device_available = self.coordinator.device_available super()._handle_coordinator_update() - async def async_added_to_hass(self) -> None: - """When entity is added to Home Assistant.""" - pass - # if self._device["platform"] == 1: - # self.async_on_remove( - # self._mqtt_client.subscribe_availability_change( - # self._device["product_id"], - # self._device["device_id"], - # self.update_device_availability, - # ) - # ) - # self.async_on_remove( - # self._mqtt_client.subscribe_state_change( - # self._device["product_id"], - # self._device["device_id"], - # self.update_device_state, - # ) - # ) - # - # await self.poll_device_state() - # self.hass.bus.async_fire('humidifier_state_changed', {'state': json.dumps(self.device_state.__dict__)}) - # self.hass.helpers.event.async_track_time_interval( - # self.put_device_state, timedelta(seconds=5) - # ) - #self.async_on_remove(self.cancel_polling) - - # def update_device_availability(self, available: bool) -> None: - # """Will be called when received new availability status.""" - # if self.subscription_muted: - # return - # self._attr_available = available - # self.async_write_ha_state() - # - # def update_device_state(self, state: DeyeDeviceState) -> None: - # """Will be called when received new DeyeDeviceState.""" - # # if self.entity_id_base == 'deye_849dc2621ea5': - # # _LOGGER.error("local:" + json.dumps(self.device_state.to_command().json())) - # # _LOGGER.error("cloud:" + json.dumps(state.to_command().json())) - # if self.subscription_muted: - # return - # self.device_state = state - # self.hass.bus.async_fire('humidifier_state_changed', {'state': json.dumps(self.device_state.__dict__)}) - # self.async_write_ha_state() - - # @callback - # async def poll_device_state(self, now: datetime | None = None) -> None: - # - # self.cancel_polling = async_call_later(self.hass, 60, self.poll_device_state) - - # def mute_subscription_for_a_while(self) -> None: - # """Mute subscription for a while to avoid state bouncing.""" - # if self.hass.states.get(f"refresh.{self.entity_id_base}_dehumidifier") is False: - # self.subscription_muted() - # - # @callback - # def unmute(now: datetime) -> None: - # self.hass.states.async_set(f"refresh.{self.entity_id_base}_dehumidifier", True) - # - # self.subscription_muted = async_call_later(self.hass, 20, unmute) - - # def set_dev_state(self, state): - # _LOGGER.error(state) - # self._dev_state = state - - # def mute_command_for_a_while(self) -> None: - # """Mute subscription for a while to avoid state bouncing.""" - # if self.command_muted: - # self.command_muted() - # - # @callback - # def unmute(now: datetime) -> None: - # self.command_muted = None - # - # self.command_muted = async_call_later(self.hass, 10, unmute) - # async def publish_command(self, prop, value) -> None: - # self.device_state_change_list[prop] = value - # _LOGGER.error("old" + json.dumps(self.device_state.to_command().json())) - # set_class_variable(self.device_state, prop, value) - # command = self.device_state.to_command() - # _LOGGER.error("old2" + json.dumps(command.json())) - # statatatt = self.hass.states.get(f"device.{self.entity_id_base}_state").attributes.get("state") - # _LOGGER.error( - # statatatt - # ) - # _LOGGER.error( - # statatatt.to_command().json() - # ) - # _LOGGER.error( - # self.hass.states.get(f"fan.{self.entity_id_base}_fan").attributes - # ) - - - - # feature_config = get_product_feature_config(self._device["product_id"]) - # fan = self.hass.states.get(f"fan.{self.entity_id_base}_fan") - # fan_speed = DeyeFanSpeed.STOPPED - # oscillating_switch = False - # if fan is not None: - # if len(feature_config["fan_speed"]) > 0: - # fan_speed = percentage_to_ordered_list_item( - # feature_config["fan_speed"], fan.attributes.get("percentage") - # ) - # if feature_config["oscillating"]: - # oscillating_switch = fan.attributes.get("oscillating") - # - # dehumidifier = self.hass.states.get(f"humidifier.{self.entity_id_base}_dehumidifier") - # target_humidity = 60 - # if dehumidifier is not None: - # if dehumidifier.attributes.get("humidity") is not None: - # target_humidity = dehumidifier.attributes.get("humidity") - # - # command = DeyeDeviceCommand( - # self.hass.states.is_state(f"switch.{self.entity_id_base}_anion", STATE_ON), - # self.hass.states.is_state(f"switch.{self.entity_id_base}_water_pump", STATE_ON), - # self.hass.states.is_state(f"fan.{self.entity_id_base}_fan", STATE_ON), - # oscillating_switch, - # self.hass.states.is_state(f"switch.{self.entity_id_base}_child_lock", STATE_ON), - # fan_speed, - # self.device_state.mode, - # target_humidity, - # ) - # set_class_variable(command, prop, value) - - - - # _LOGGER.error("anion" + str()) - # _LOGGER.error("child_lock" + str(self.hass.states.get(f"{SwitchDeviceClass.SWITCH}.{self.entity_id_base}_child_lock"))) - # _LOGGER.error("muted" + str(command_muted)) - # # if command_muted: - # # async_call_later(self.hass, 5, self.publish_command) - # # else: - # _LOGGER.error("muted" + str(command_muted)) - # # command_muted = True - # _LOGGER.error("muted" + str(command_muted)) - # _LOGGER.error(json.dumps(self.device_state.to_command().json())) - # set_class_variable(self.device_state, prop, value) - # command = self.device_state.to_command() - # _LOGGER.error("new" + json.dumps(command.json())) - # if self._device["platform"] == 1: - # """Publish a MQTT command to this device.""" - # self._mqtt_client.publish_command( - # self._device["product_id"], self._device["device_id"], command.bytes() - # ) - # elif self._device["platform"] == 2: - # """Post a Remote command to this device.""" - # await self._cloud_api.set_fog_platform_device_properties(self._device["device_id"], command.json()) - # self.async_write_ha_state() - - # command_muted = False - # _LOGGER.error("muted" + str(command_muted)) diff --git a/custom_components/deye_dehumidifier/data_coordinator.py b/custom_components/deye_dehumidifier/data_coordinator.py index e7db7c3..88479e1 100644 --- a/custom_components/deye_dehumidifier/data_coordinator.py +++ b/custom_components/deye_dehumidifier/data_coordinator.py @@ -4,12 +4,10 @@ from homeassistant.helpers.event import async_call_later from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, callback from libdeye.device_state_command import DeyeDeviceState from libdeye.const import QUERY_DEVICE_STATE_COMMAND -_LOGGER = logging.getLogger(__name__) - class DeyeDataUpdateCoordinator(DataUpdateCoordinator): def __init__(self, hass, device, mqtt_client, cloud_api): @@ -28,14 +26,14 @@ def __init__(self, hass, device, mqtt_client, cloud_api): "1411000000370000000000000000003C3C0000000000" # 20°C/60%RH as the default state ) self._device = device + self.device_available = self._device["online"] """When entity is added to Home Assistant.""" if self._device["platform"] == 1: - - self._mqtt_client.subscribe_availability_change( - self._device["product_id"], - self._device["device_id"], - self.update_device_availability, - ) + # self._mqtt_client.subscribe_availability_change( + # self._device["product_id"], + # self._device["device_id"], + # self.update_device_availability, + # ) self._mqtt_client.subscribe_state_change( self._device["product_id"], self._device["device_id"], @@ -43,6 +41,7 @@ def __init__(self, hass, device, mqtt_client, cloud_api): ) self.receive_queue = asyncio.Queue() + self.device_available_queue = asyncio.Queue() def mute_subscription_for_a_while(self) -> None: """Mute subscription for a while to avoid state bouncing.""" @@ -55,17 +54,8 @@ def unmute(now: datetime) -> None: self.subscription_muted = async_call_later(self.hass, 20, unmute) - def update_device_availability(self, available: bool) -> None: - """Will be called when received new availability status.""" - # if self.hass.states.get(f"refresh.{self.entity_id_base}_dehumidifier") is False: - # return - _LOGGER.error(available) - # self.async_set_updated_data(available) - def update_device_state(self, state: DeyeDeviceState) -> None: """Will be called when received new DeyeDeviceState.""" - # if self.hass.states.get(f"refresh.{self.entity_id_base}_dehumidifier") is False: - # return self.receive_queue.put_nowait(state) # self.async_set_updated_data(state) @@ -82,6 +72,16 @@ async def poll_device_state(self) -> DeyeDeviceState: if self.subscription_muted: return self.data + device_list = list( + filter( + lambda d: d["product_type"] == "dehumidifier" and d["device_id"] == self._device["device_id"], + await self._cloud_api.get_device_list(), + ) + ) + if len(device_list) > 0: + device = device_list[0] + self.device_available = device["online"] + if self._device["platform"] == 1: self._mqtt_client.publish_command( self._device["product_id"], diff --git a/custom_components/deye_dehumidifier/humidifier.py b/custom_components/deye_dehumidifier/humidifier.py index 355cdef..2d52b47 100644 --- a/custom_components/deye_dehumidifier/humidifier.py +++ b/custom_components/deye_dehumidifier/humidifier.py @@ -2,8 +2,6 @@ from __future__ import annotations -import json -import logging from datetime import datetime, timedelta from typing import Any @@ -23,8 +21,8 @@ from libdeye.cloud_api import DeyeCloudApi from libdeye.device_state_command import DeyeDeviceState -from . import DeyeEntity, DeyeDataUpdateCoordinator -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR +from . import DeyeEntity +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN MODE_MANUAL = "manual" MODE_AIR_PURIFIER = "air_purifier" @@ -50,8 +48,6 @@ async def call_method(event): hass.bus.async_listen('call_humidifier_method', call_method) -_LOGGER = logging.getLogger(__name__) - class DeyeDehumidifier(DeyeEntity, HumidifierEntity): """Dehumidifier entity.""" @@ -82,7 +78,6 @@ def __init__( async def async_added_to_hass(self) -> None: await super().async_added_to_hass() - self.hass.states.async_set(f"refresh.{self.entity_id_base}_dehumidifier", True) self.hass.helpers.event.async_track_time_interval( self.put_device_state, timedelta(seconds=5) ) @@ -95,7 +90,6 @@ async def put_device_state(self, now: datetime | None = None) -> None: for prop, value in self.data_change_list.items(): set_class_variable(command, prop, value) self.data_change_list.clear() - _LOGGER.error("new" + json.dumps(command.json())) if self._device["platform"] == 1: """Publish a MQTT command to this device.""" self._mqtt_client.publish_command( @@ -108,9 +102,7 @@ async def put_device_state(self, now: datetime | None = None) -> None: self.async_write_ha_state() async def publish_command(self, prop, value) -> None: - self.hass.states.async_set(f"refresh.{self.entity_id_base}_dehumidifier", False) self.data_change_list[prop] = value - # self.mute_subscription_for_a_while() @property def get_device_state(self) -> DeyeDeviceState: diff --git a/custom_components/deye_dehumidifier/switch.py b/custom_components/deye_dehumidifier/switch.py index ff24089..42f988c 100644 --- a/custom_components/deye_dehumidifier/switch.py +++ b/custom_components/deye_dehumidifier/switch.py @@ -2,7 +2,6 @@ from __future__ import annotations -import logging from typing import Any from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity @@ -19,8 +18,6 @@ from . import DeyeEntity, DeyeDataUpdateCoordinator from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, From 2af370f7e6d85c3231a33ec43513da85c735c230 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 08:51:49 +0000 Subject: [PATCH 6/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../deye_dehumidifier/__init__.py | 26 ++++++--- .../deye_dehumidifier/binary_sensor.py | 28 ++++++++-- .../deye_dehumidifier/data_coordinator.py | 21 ++++--- custom_components/deye_dehumidifier/fan.py | 43 ++++++++++----- .../deye_dehumidifier/humidifier.py | 49 ++++++++++------- custom_components/deye_dehumidifier/sensor.py | 29 ++++++++-- custom_components/deye_dehumidifier/switch.py | 55 ++++++++++++++----- 7 files changed, 174 insertions(+), 77 deletions(-) diff --git a/custom_components/deye_dehumidifier/__init__.py b/custom_components/deye_dehumidifier/__init__.py index 7f0da52..a189330 100644 --- a/custom_components/deye_dehumidifier/__init__.py +++ b/custom_components/deye_dehumidifier/__init__.py @@ -14,22 +14,22 @@ DeyeCloudApiCannotConnectError, DeyeCloudApiInvalidAuthError, ) - from libdeye.device_state_command import DeyeDeviceCommand, DeyeDeviceState from libdeye.mqtt_client import DeyeMqttClient -from libdeye.types import DeyeApiResponseDeviceInfo, DeyeFanSpeed, DeyeDeviceMode -from .data_coordinator import DeyeDataUpdateCoordinator +from libdeye.types import DeyeApiResponseDeviceInfo, DeyeDeviceMode, DeyeFanSpeed + from .const import ( CONF_AUTH_TOKEN, CONF_PASSWORD, CONF_USERNAME, DATA_CLOUD_API, + DATA_COORDINATOR, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, - DATA_COORDINATOR, DOMAIN, MANUFACTURER, ) +from .data_coordinator import DeyeDataUpdateCoordinator PLATFORMS: list[Platform] = [ Platform.HUMIDIFIER, @@ -72,7 +72,9 @@ def on_auth_token_refreshed(auth_token: str) -> None: ) ) for device in device_list: - coordinator = DeyeDataUpdateCoordinator(hass, device, mqtt_client, cloud_api) + coordinator = DeyeDataUpdateCoordinator( + hass, device, mqtt_client, cloud_api + ) device[DATA_COORDINATOR] = coordinator await device[DATA_COORDINATOR].async_config_entry_first_refresh() @@ -107,7 +109,10 @@ class DeyeEntity(CoordinatorEntity, Entity): """Initiate Deye Base Class.""" def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the instance.""" self.coordinator = device[DATA_COORDINATOR] @@ -133,13 +138,17 @@ def __init__( self.device_state = DeyeDeviceState( "1411000000370000000000000000003C3C0000000000" # 20°C/60%RH as the default state ) - remove_handle = self.coordinator.async_add_listener(self._handle_coordinator_update) + remove_handle = self.coordinator.async_add_listener( + self._handle_coordinator_update + ) self.async_on_remove(remove_handle) async def publish_command_async(self, attribute, value): """Push command to a queue and deal with them together.""" self.async_write_ha_state() - self.hass.bus.fire('call_humidifier_method', {'prop': attribute, 'value': value}) + self.hass.bus.fire( + "call_humidifier_method", {"prop": attribute, "value": value} + ) await self.coordinator.async_request_refresh() @property @@ -152,4 +161,3 @@ def _handle_coordinator_update(self) -> None: self.device_state = self.coordinator.data self._device_available = self.coordinator.device_available super()._handle_coordinator_update() - diff --git a/custom_components/deye_dehumidifier/binary_sensor.py b/custom_components/deye_dehumidifier/binary_sensor.py index 3b45211..660e9c0 100644 --- a/custom_components/deye_dehumidifier/binary_sensor.py +++ b/custom_components/deye_dehumidifier/binary_sensor.py @@ -10,12 +10,18 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR +from .const import ( + DATA_CLOUD_API, + DATA_COORDINATOR, + DATA_DEVICE_LIST, + DATA_MQTT_CLIENT, + DOMAIN, +) async def async_setup_entry( @@ -29,8 +35,12 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: async_add_entities( [ - DeyeWaterTankBinarySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), - DeyeDefrostingBinarySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), + DeyeWaterTankBinarySensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), + DeyeDefrostingBinarySensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), ] ) @@ -43,7 +53,10 @@ class DeyeWaterTankBinarySensor(DeyeEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) @@ -65,7 +78,10 @@ class DeyeDefrostingBinarySensor(DeyeEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) diff --git a/custom_components/deye_dehumidifier/data_coordinator.py b/custom_components/deye_dehumidifier/data_coordinator.py index 88479e1..9b95cd0 100644 --- a/custom_components/deye_dehumidifier/data_coordinator.py +++ b/custom_components/deye_dehumidifier/data_coordinator.py @@ -1,12 +1,12 @@ import asyncio import logging -from datetime import timedelta, datetime +from datetime import datetime, timedelta +from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers.event import async_call_later from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from homeassistant.core import CALLBACK_TYPE, callback -from libdeye.device_state_command import DeyeDeviceState from libdeye.const import QUERY_DEVICE_STATE_COMMAND +from libdeye.device_state_command import DeyeDeviceState class DeyeDataUpdateCoordinator(DataUpdateCoordinator): @@ -74,7 +74,8 @@ async def poll_device_state(self) -> DeyeDeviceState: device_list = list( filter( - lambda d: d["product_type"] == "dehumidifier" and d["device_id"] == self._device["device_id"], + lambda d: d["product_type"] == "dehumidifier" + and d["device_id"] == self._device["device_id"], await self._cloud_api.get_device_list(), ) ) @@ -88,10 +89,16 @@ async def poll_device_state(self) -> DeyeDeviceState: self._device["device_id"], QUERY_DEVICE_STATE_COMMAND, ) - response = await asyncio.wait_for(self.receive_queue.get(), timeout=10) # 设置超时时间 - #_LOGGER.error(response.to_command().json()) + response = await asyncio.wait_for( + self.receive_queue.get(), timeout=10 + ) # 设置超时时间 + # _LOGGER.error(response.to_command().json()) return response elif self._device["platform"] == 2: - response = DeyeDeviceState(await self._cloud_api.get_fog_platform_device_properties(self._device["device_id"])) + response = DeyeDeviceState( + await self._cloud_api.get_fog_platform_device_properties( + self._device["device_id"] + ) + ) # _LOGGER.error(response.to_command().json()) return response diff --git a/custom_components/deye_dehumidifier/fan.py b/custom_components/deye_dehumidifier/fan.py index a66dceb..1bafd76 100644 --- a/custom_components/deye_dehumidifier/fan.py +++ b/custom_components/deye_dehumidifier/fan.py @@ -12,13 +12,19 @@ ordered_list_item_to_percentage, percentage_to_ordered_list_item, ) +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo, DeyeFanSpeed from libdeye.utils import get_product_feature_config -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR +from .const import ( + DATA_CLOUD_API, + DATA_COORDINATOR, + DATA_DEVICE_LIST, + DATA_MQTT_CLIENT, + DOMAIN, +) async def async_setup_entry( @@ -32,7 +38,9 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: feature_config = get_product_feature_config(device["product_id"]) if len(feature_config["fan_speed"]) > 0: - async_add_entities([DeyeFan(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [DeyeFan(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])] + ) class DeyeFan(DeyeEntity, FanEntity): @@ -41,7 +49,10 @@ class DeyeFan(DeyeEntity, FanEntity): _attr_translation_key = "fan" def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the fan entity.""" super().__init__(device, mqtt_client, cloud_api) @@ -50,9 +61,9 @@ def __init__( self.entity_id = f"fan.{self.entity_id_base}_fan" feature_config = get_product_feature_config(device["product_id"]) self._attr_supported_features = FanEntityFeature.SET_SPEED - if hasattr(FanEntityFeature, 'TURN_ON'): # v2024.8 + if hasattr(FanEntityFeature, "TURN_ON"): # v2024.8 self._attr_supported_features |= FanEntityFeature.TURN_ON - if hasattr(FanEntityFeature, 'TURN_OFF'): + if hasattr(FanEntityFeature, "TURN_OFF"): self._attr_supported_features |= FanEntityFeature.TURN_OFF if feature_config["oscillating"]: self._attr_supported_features |= FanEntityFeature.OSCILLATE @@ -81,17 +92,17 @@ def percentage(self) -> int: async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" self.device_state.oscillating_switch = oscillating - await self.publish_command_async('oscillating_switch', oscillating) + await self.publish_command_async("oscillating_switch", oscillating) async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" if percentage == 0: await self.async_turn_off() - fan_speed = int(percentage_to_ordered_list_item( - self._named_fan_speeds, percentage - )) + fan_speed = int( + percentage_to_ordered_list_item(self._named_fan_speeds, percentage) + ) self.device_state.fan_speed = fan_speed - await self.publish_command_async('fan_speed', fan_speed) + await self.publish_command_async("fan_speed", fan_speed) async def async_turn_on( self, @@ -101,13 +112,15 @@ async def async_turn_on( ) -> None: """Turn on the fan.""" self.device_state.power_switch = True - await self.publish_command_async('power_switch', True) + await self.publish_command_async("power_switch", True) if percentage is not None: - fan_speed = int(percentage_to_ordered_list_item(self._named_fan_speeds, percentage)) + fan_speed = int( + percentage_to_ordered_list_item(self._named_fan_speeds, percentage) + ) self.device_state.fan_speed = fan_speed - await self.publish_command_async('fan_speed', fan_speed) + await self.publish_command_async("fan_speed", fan_speed) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" self.device_state.power_switch = False - await self.publish_command_async('power_switch', False) + await self.publish_command_async("power_switch", False) diff --git a/custom_components/deye_dehumidifier/humidifier.py b/custom_components/deye_dehumidifier/humidifier.py index 2d52b47..7dcea50 100644 --- a/custom_components/deye_dehumidifier/humidifier.py +++ b/custom_components/deye_dehumidifier/humidifier.py @@ -12,17 +12,16 @@ ) from homeassistant.components.humidifier.const import MODE_AUTO, MODE_SLEEP from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, callback, CALLBACK_TYPE +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi +from libdeye.device_state_command import DeyeDeviceState from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo, DeyeDeviceMode from libdeye.utils import get_product_feature_config -from libdeye.cloud_api import DeyeCloudApi - -from libdeye.device_state_command import DeyeDeviceState from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN MODE_MANUAL = "manual" MODE_AIR_PURIFIER = "air_purifier" @@ -38,15 +37,17 @@ async def async_setup_entry( data = hass.data[DOMAIN][config_entry.entry_id] for device in data[DATA_DEVICE_LIST]: - deye_dehumidifier = DeyeDehumidifier(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]) + deye_dehumidifier = DeyeDehumidifier( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ) async_add_entities([deye_dehumidifier]) async def call_method(event): - prop = event.data.get('prop') - value = event.data.get('value') + prop = event.data.get("prop") + value = event.data.get("value") await deye_dehumidifier.publish_command(prop, value) - hass.bus.async_listen('call_humidifier_method', call_method) + hass.bus.async_listen("call_humidifier_method", call_method) class DeyeDehumidifier(DeyeEntity, HumidifierEntity): @@ -57,7 +58,10 @@ class DeyeDehumidifier(DeyeEntity, HumidifierEntity): _attr_name = None # Inherits from device name def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the humidifier entity.""" super().__init__(device, mqtt_client, cloud_api) @@ -74,14 +78,14 @@ def __init__( self._attr_min_humidity = feature_config["min_target_humidity"] self._attr_max_humidity = feature_config["max_target_humidity"] self._attr_entity_picture = device["product_icon"] - self.data_change_list : dict = dict() + self.data_change_list: dict = dict() async def async_added_to_hass(self) -> None: await super().async_added_to_hass() self.hass.helpers.event.async_track_time_interval( self.put_device_state, timedelta(seconds=5) ) - + @callback async def put_device_state(self, now: datetime | None = None) -> None: # _LOGGER.error(self.data_change_list) @@ -93,11 +97,15 @@ async def put_device_state(self, now: datetime | None = None) -> None: if self._device["platform"] == 1: """Publish a MQTT command to this device.""" self._mqtt_client.publish_command( - self._device["product_id"], self._device["device_id"], command.bytes() + self._device["product_id"], + self._device["device_id"], + command.bytes(), ) elif self._device["platform"] == 2: """Post a Remote command to this device.""" - await self._cloud_api.set_fog_platform_device_properties(self._device["device_id"], command.json()) + await self._cloud_api.set_fog_platform_device_properties( + self._device["device_id"], command.json() + ) self.async_write_ha_state() @@ -148,28 +156,31 @@ def action(self) -> str: async def async_set_mode(self, mode: str) -> None: """Set new working mode.""" self.device_state.mode = hass_mode_to_deye_mode(mode) - await self.publish_command_async('mode', hass_mode_to_deye_mode(mode)) + await self.publish_command_async("mode", hass_mode_to_deye_mode(mode)) async def async_set_humidity(self, humidity: int) -> None: """Set new target humidity.""" self.device_state.target_humidity = humidity - await self.publish_command_async('target_humidity', humidity) + await self.publish_command_async("target_humidity", humidity) async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" self.device_state.power_switch = True - await self.publish_command_async('power_switch', True) + await self.publish_command_async("power_switch", True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" self.device_state.power_switch = False - await self.publish_command_async('power_switch', False) + await self.publish_command_async("power_switch", False) + def set_class_variable(obj, var_name, new_value): if hasattr(obj, var_name): setattr(obj, var_name, new_value) else: - raise AttributeError(f"'{obj.__class__.__name__}' object has no attribute '{var_name}'") + raise AttributeError( + f"'{obj.__class__.__name__}' object has no attribute '{var_name}'" + ) def deye_mode_to_hass_mode(mode: DeyeDeviceMode) -> str: diff --git a/custom_components/deye_dehumidifier/sensor.py b/custom_components/deye_dehumidifier/sensor.py index 9bc16fa..9cea490 100644 --- a/custom_components/deye_dehumidifier/sensor.py +++ b/custom_components/deye_dehumidifier/sensor.py @@ -11,12 +11,19 @@ from homeassistant.const import PERCENTAGE, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR +from .const import ( + DATA_CLOUD_API, + DATA_COORDINATOR, + DATA_DEVICE_LIST, + DATA_MQTT_CLIENT, + DOMAIN, +) + async def async_setup_entry( hass: HomeAssistant, @@ -29,8 +36,12 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: async_add_entities( [ - DeyeHumiditySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), - DeyeTemperatureSensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), + DeyeHumiditySensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), + DeyeTemperatureSensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), ] ) @@ -44,7 +55,10 @@ class DeyeHumiditySensor(DeyeEntity, SensorEntity): _attr_native_unit_of_measurement = PERCENTAGE def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) @@ -67,7 +81,10 @@ class DeyeTemperatureSensor(DeyeEntity, SensorEntity): _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) diff --git a/custom_components/deye_dehumidifier/switch.py b/custom_components/deye_dehumidifier/switch.py index 42f988c..c4fb6ef 100644 --- a/custom_components/deye_dehumidifier/switch.py +++ b/custom_components/deye_dehumidifier/switch.py @@ -9,14 +9,20 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo from libdeye.utils import get_product_feature_config -from libdeye.cloud_api import DeyeCloudApi +from . import DeyeDataUpdateCoordinator, DeyeEntity +from .const import ( + DATA_CLOUD_API, + DATA_COORDINATOR, + DATA_DEVICE_LIST, + DATA_MQTT_CLIENT, + DOMAIN, +) -from . import DeyeEntity, DeyeDataUpdateCoordinator -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR async def async_setup_entry( hass: HomeAssistant, @@ -27,12 +33,22 @@ async def async_setup_entry( data = hass.data[DOMAIN][config_entry.entry_id] for device in data[DATA_DEVICE_LIST]: - async_add_entities([DeyeChildLockSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [DeyeChildLockSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])] + ) feature_config = get_product_feature_config(device["product_id"]) if feature_config["anion"]: - async_add_entities([DeyeAnionSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [DeyeAnionSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])] + ) if feature_config["water_pump"]: - async_add_entities([DeyeWaterPumpSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [ + DeyeWaterPumpSwitch( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ) + ] + ) class DeyeChildLockSwitch(DeyeEntity, SwitchEntity): @@ -43,7 +59,10 @@ class DeyeChildLockSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the switch.""" super().__init__(device, mqtt_client, cloud_api) @@ -59,12 +78,12 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the child lock on.""" self.device_state.child_lock_switch = True - await self.publish_command_async('child_lock_switch', True) + await self.publish_command_async("child_lock_switch", True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the child lock off.""" self.device_state.child_lock_switch = False - await self.publish_command_async('child_lock_switch', False) + await self.publish_command_async("child_lock_switch", False) class DeyeAnionSwitch(DeyeEntity, SwitchEntity): @@ -75,7 +94,10 @@ class DeyeAnionSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the switch.""" super().__init__(device, mqtt_client, cloud_api) @@ -91,12 +113,12 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the anion switch on.""" self.device_state.anion_switch = True - await self.publish_command_async('anion_switch', True) + await self.publish_command_async("anion_switch", True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the anion switch off.""" self.device_state.anion_switch = False - await self.publish_command_async('anion_switch', False) + await self.publish_command_async("anion_switch", False) class DeyeWaterPumpSwitch(DeyeEntity, SwitchEntity): @@ -107,7 +129,10 @@ class DeyeWaterPumpSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the switch.""" super().__init__(device, mqtt_client, cloud_api) @@ -123,9 +148,9 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the water pump on.""" self.device_state.water_pump_switch = True - await self.publish_command_async('water_pump_switch', True) + await self.publish_command_async("water_pump_switch", True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the water pump off.""" self.device_state.water_pump_switch = False - await self.publish_command_async('water_pump_switch', False) + await self.publish_command_async("water_pump_switch", False) From 0f9929ff0a8ec27232ee61b8117030a62ed669fb Mon Sep 17 00:00:00 2001 From: sususweet Date: Wed, 18 Dec 2024 16:55:14 +0800 Subject: [PATCH 7/8] refactor: fix imports --- custom_components/deye_dehumidifier/__init__.py | 4 ++-- custom_components/deye_dehumidifier/binary_sensor.py | 2 +- custom_components/deye_dehumidifier/data_coordinator.py | 1 + custom_components/deye_dehumidifier/fan.py | 2 +- custom_components/deye_dehumidifier/sensor.py | 2 +- custom_components/deye_dehumidifier/switch.py | 6 +++--- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/custom_components/deye_dehumidifier/__init__.py b/custom_components/deye_dehumidifier/__init__.py index 7f0da52..d758ee2 100644 --- a/custom_components/deye_dehumidifier/__init__.py +++ b/custom_components/deye_dehumidifier/__init__.py @@ -15,9 +15,9 @@ DeyeCloudApiInvalidAuthError, ) -from libdeye.device_state_command import DeyeDeviceCommand, DeyeDeviceState +from libdeye.device_state_command import DeyeDeviceState from libdeye.mqtt_client import DeyeMqttClient -from libdeye.types import DeyeApiResponseDeviceInfo, DeyeFanSpeed, DeyeDeviceMode +from libdeye.types import DeyeApiResponseDeviceInfo from .data_coordinator import DeyeDataUpdateCoordinator from .const import ( CONF_AUTH_TOKEN, diff --git a/custom_components/deye_dehumidifier/binary_sensor.py b/custom_components/deye_dehumidifier/binary_sensor.py index 3b45211..0330005 100644 --- a/custom_components/deye_dehumidifier/binary_sensor.py +++ b/custom_components/deye_dehumidifier/binary_sensor.py @@ -15,7 +15,7 @@ from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN async def async_setup_entry( diff --git a/custom_components/deye_dehumidifier/data_coordinator.py b/custom_components/deye_dehumidifier/data_coordinator.py index 88479e1..cc5212f 100644 --- a/custom_components/deye_dehumidifier/data_coordinator.py +++ b/custom_components/deye_dehumidifier/data_coordinator.py @@ -8,6 +8,7 @@ from libdeye.device_state_command import DeyeDeviceState from libdeye.const import QUERY_DEVICE_STATE_COMMAND +_LOGGER = logging.getLogger(__name__) class DeyeDataUpdateCoordinator(DataUpdateCoordinator): def __init__(self, hass, device, mqtt_client, cloud_api): diff --git a/custom_components/deye_dehumidifier/fan.py b/custom_components/deye_dehumidifier/fan.py index a66dceb..4284187 100644 --- a/custom_components/deye_dehumidifier/fan.py +++ b/custom_components/deye_dehumidifier/fan.py @@ -18,7 +18,7 @@ from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN async def async_setup_entry( diff --git a/custom_components/deye_dehumidifier/sensor.py b/custom_components/deye_dehumidifier/sensor.py index 9bc16fa..ed59b12 100644 --- a/custom_components/deye_dehumidifier/sensor.py +++ b/custom_components/deye_dehumidifier/sensor.py @@ -16,7 +16,7 @@ from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN async def async_setup_entry( hass: HomeAssistant, diff --git a/custom_components/deye_dehumidifier/switch.py b/custom_components/deye_dehumidifier/switch.py index 42f988c..ef37233 100644 --- a/custom_components/deye_dehumidifier/switch.py +++ b/custom_components/deye_dehumidifier/switch.py @@ -7,7 +7,7 @@ from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo @@ -15,8 +15,8 @@ from libdeye.cloud_api import DeyeCloudApi -from . import DeyeEntity, DeyeDataUpdateCoordinator -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN, DATA_COORDINATOR +from . import DeyeEntity +from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN async def async_setup_entry( hass: HomeAssistant, From 5714674c2daad8ad5f4ebf5bed288f3b60da0796 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 08:58:12 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../deye_dehumidifier/__init__.py | 24 ++++++--- .../deye_dehumidifier/binary_sensor.py | 22 ++++++--- .../deye_dehumidifier/data_coordinator.py | 1 + custom_components/deye_dehumidifier/fan.py | 37 ++++++++------ custom_components/deye_dehumidifier/sensor.py | 23 ++++++--- custom_components/deye_dehumidifier/switch.py | 49 +++++++++++++------ 6 files changed, 106 insertions(+), 50 deletions(-) diff --git a/custom_components/deye_dehumidifier/__init__.py b/custom_components/deye_dehumidifier/__init__.py index d758ee2..301d098 100644 --- a/custom_components/deye_dehumidifier/__init__.py +++ b/custom_components/deye_dehumidifier/__init__.py @@ -14,22 +14,22 @@ DeyeCloudApiCannotConnectError, DeyeCloudApiInvalidAuthError, ) - from libdeye.device_state_command import DeyeDeviceState from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo -from .data_coordinator import DeyeDataUpdateCoordinator + from .const import ( CONF_AUTH_TOKEN, CONF_PASSWORD, CONF_USERNAME, DATA_CLOUD_API, + DATA_COORDINATOR, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, - DATA_COORDINATOR, DOMAIN, MANUFACTURER, ) +from .data_coordinator import DeyeDataUpdateCoordinator PLATFORMS: list[Platform] = [ Platform.HUMIDIFIER, @@ -72,7 +72,9 @@ def on_auth_token_refreshed(auth_token: str) -> None: ) ) for device in device_list: - coordinator = DeyeDataUpdateCoordinator(hass, device, mqtt_client, cloud_api) + coordinator = DeyeDataUpdateCoordinator( + hass, device, mqtt_client, cloud_api + ) device[DATA_COORDINATOR] = coordinator await device[DATA_COORDINATOR].async_config_entry_first_refresh() @@ -107,7 +109,10 @@ class DeyeEntity(CoordinatorEntity, Entity): """Initiate Deye Base Class.""" def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the instance.""" self.coordinator = device[DATA_COORDINATOR] @@ -133,13 +138,17 @@ def __init__( self.device_state = DeyeDeviceState( "1411000000370000000000000000003C3C0000000000" # 20°C/60%RH as the default state ) - remove_handle = self.coordinator.async_add_listener(self._handle_coordinator_update) + remove_handle = self.coordinator.async_add_listener( + self._handle_coordinator_update + ) self.async_on_remove(remove_handle) async def publish_command_async(self, attribute, value): """Push command to a queue and deal with them together.""" self.async_write_ha_state() - self.hass.bus.fire('call_humidifier_method', {'prop': attribute, 'value': value}) + self.hass.bus.fire( + "call_humidifier_method", {"prop": attribute, "value": value} + ) await self.coordinator.async_request_refresh() @property @@ -152,4 +161,3 @@ def _handle_coordinator_update(self) -> None: self.device_state = self.coordinator.data self._device_available = self.coordinator.device_available super()._handle_coordinator_update() - diff --git a/custom_components/deye_dehumidifier/binary_sensor.py b/custom_components/deye_dehumidifier/binary_sensor.py index 0330005..254758a 100644 --- a/custom_components/deye_dehumidifier/binary_sensor.py +++ b/custom_components/deye_dehumidifier/binary_sensor.py @@ -10,12 +10,12 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN async def async_setup_entry( @@ -29,8 +29,12 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: async_add_entities( [ - DeyeWaterTankBinarySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), - DeyeDefrostingBinarySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), + DeyeWaterTankBinarySensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), + DeyeDefrostingBinarySensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), ] ) @@ -43,7 +47,10 @@ class DeyeWaterTankBinarySensor(DeyeEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) @@ -65,7 +72,10 @@ class DeyeDefrostingBinarySensor(DeyeEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) diff --git a/custom_components/deye_dehumidifier/data_coordinator.py b/custom_components/deye_dehumidifier/data_coordinator.py index e2622ce..c592252 100644 --- a/custom_components/deye_dehumidifier/data_coordinator.py +++ b/custom_components/deye_dehumidifier/data_coordinator.py @@ -10,6 +10,7 @@ _LOGGER = logging.getLogger(__name__) + class DeyeDataUpdateCoordinator(DataUpdateCoordinator): def __init__(self, hass, device, mqtt_client, cloud_api): super().__init__( diff --git a/custom_components/deye_dehumidifier/fan.py b/custom_components/deye_dehumidifier/fan.py index 4284187..d8cc579 100644 --- a/custom_components/deye_dehumidifier/fan.py +++ b/custom_components/deye_dehumidifier/fan.py @@ -12,13 +12,13 @@ ordered_list_item_to_percentage, percentage_to_ordered_list_item, ) +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo, DeyeFanSpeed from libdeye.utils import get_product_feature_config -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN async def async_setup_entry( @@ -32,7 +32,9 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: feature_config = get_product_feature_config(device["product_id"]) if len(feature_config["fan_speed"]) > 0: - async_add_entities([DeyeFan(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [DeyeFan(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])] + ) class DeyeFan(DeyeEntity, FanEntity): @@ -41,7 +43,10 @@ class DeyeFan(DeyeEntity, FanEntity): _attr_translation_key = "fan" def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the fan entity.""" super().__init__(device, mqtt_client, cloud_api) @@ -50,9 +55,9 @@ def __init__( self.entity_id = f"fan.{self.entity_id_base}_fan" feature_config = get_product_feature_config(device["product_id"]) self._attr_supported_features = FanEntityFeature.SET_SPEED - if hasattr(FanEntityFeature, 'TURN_ON'): # v2024.8 + if hasattr(FanEntityFeature, "TURN_ON"): # v2024.8 self._attr_supported_features |= FanEntityFeature.TURN_ON - if hasattr(FanEntityFeature, 'TURN_OFF'): + if hasattr(FanEntityFeature, "TURN_OFF"): self._attr_supported_features |= FanEntityFeature.TURN_OFF if feature_config["oscillating"]: self._attr_supported_features |= FanEntityFeature.OSCILLATE @@ -81,17 +86,17 @@ def percentage(self) -> int: async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" self.device_state.oscillating_switch = oscillating - await self.publish_command_async('oscillating_switch', oscillating) + await self.publish_command_async("oscillating_switch", oscillating) async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" if percentage == 0: await self.async_turn_off() - fan_speed = int(percentage_to_ordered_list_item( - self._named_fan_speeds, percentage - )) + fan_speed = int( + percentage_to_ordered_list_item(self._named_fan_speeds, percentage) + ) self.device_state.fan_speed = fan_speed - await self.publish_command_async('fan_speed', fan_speed) + await self.publish_command_async("fan_speed", fan_speed) async def async_turn_on( self, @@ -101,13 +106,15 @@ async def async_turn_on( ) -> None: """Turn on the fan.""" self.device_state.power_switch = True - await self.publish_command_async('power_switch', True) + await self.publish_command_async("power_switch", True) if percentage is not None: - fan_speed = int(percentage_to_ordered_list_item(self._named_fan_speeds, percentage)) + fan_speed = int( + percentage_to_ordered_list_item(self._named_fan_speeds, percentage) + ) self.device_state.fan_speed = fan_speed - await self.publish_command_async('fan_speed', fan_speed) + await self.publish_command_async("fan_speed", fan_speed) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" self.device_state.power_switch = False - await self.publish_command_async('power_switch', False) + await self.publish_command_async("power_switch", False) diff --git a/custom_components/deye_dehumidifier/sensor.py b/custom_components/deye_dehumidifier/sensor.py index ed59b12..14ca940 100644 --- a/custom_components/deye_dehumidifier/sensor.py +++ b/custom_components/deye_dehumidifier/sensor.py @@ -11,12 +11,13 @@ from homeassistant.const import PERCENTAGE, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo -from libdeye.cloud_api import DeyeCloudApi from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN + async def async_setup_entry( hass: HomeAssistant, @@ -29,8 +30,12 @@ async def async_setup_entry( for device in data[DATA_DEVICE_LIST]: async_add_entities( [ - DeyeHumiditySensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), - DeyeTemperatureSensor(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API]), + DeyeHumiditySensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), + DeyeTemperatureSensor( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ), ] ) @@ -44,7 +49,10 @@ class DeyeHumiditySensor(DeyeEntity, SensorEntity): _attr_native_unit_of_measurement = PERCENTAGE def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) @@ -67,7 +75,10 @@ class DeyeTemperatureSensor(DeyeEntity, SensorEntity): _attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the sensor.""" super().__init__(device, mqtt_client, cloud_api) diff --git a/custom_components/deye_dehumidifier/switch.py b/custom_components/deye_dehumidifier/switch.py index ef37233..4501ca7 100644 --- a/custom_components/deye_dehumidifier/switch.py +++ b/custom_components/deye_dehumidifier/switch.py @@ -9,14 +9,14 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from libdeye.cloud_api import DeyeCloudApi from libdeye.mqtt_client import DeyeMqttClient from libdeye.types import DeyeApiResponseDeviceInfo from libdeye.utils import get_product_feature_config -from libdeye.cloud_api import DeyeCloudApi - from . import DeyeEntity -from .const import DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DATA_CLOUD_API, DOMAIN +from .const import DATA_CLOUD_API, DATA_DEVICE_LIST, DATA_MQTT_CLIENT, DOMAIN + async def async_setup_entry( hass: HomeAssistant, @@ -27,12 +27,22 @@ async def async_setup_entry( data = hass.data[DOMAIN][config_entry.entry_id] for device in data[DATA_DEVICE_LIST]: - async_add_entities([DeyeChildLockSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [DeyeChildLockSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])] + ) feature_config = get_product_feature_config(device["product_id"]) if feature_config["anion"]: - async_add_entities([DeyeAnionSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [DeyeAnionSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])] + ) if feature_config["water_pump"]: - async_add_entities([DeyeWaterPumpSwitch(device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API])]) + async_add_entities( + [ + DeyeWaterPumpSwitch( + device, data[DATA_MQTT_CLIENT], data[DATA_CLOUD_API] + ) + ] + ) class DeyeChildLockSwitch(DeyeEntity, SwitchEntity): @@ -43,7 +53,10 @@ class DeyeChildLockSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the switch.""" super().__init__(device, mqtt_client, cloud_api) @@ -59,12 +72,12 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the child lock on.""" self.device_state.child_lock_switch = True - await self.publish_command_async('child_lock_switch', True) + await self.publish_command_async("child_lock_switch", True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the child lock off.""" self.device_state.child_lock_switch = False - await self.publish_command_async('child_lock_switch', False) + await self.publish_command_async("child_lock_switch", False) class DeyeAnionSwitch(DeyeEntity, SwitchEntity): @@ -75,7 +88,10 @@ class DeyeAnionSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the switch.""" super().__init__(device, mqtt_client, cloud_api) @@ -91,12 +107,12 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the anion switch on.""" self.device_state.anion_switch = True - await self.publish_command_async('anion_switch', True) + await self.publish_command_async("anion_switch", True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the anion switch off.""" self.device_state.anion_switch = False - await self.publish_command_async('anion_switch', False) + await self.publish_command_async("anion_switch", False) class DeyeWaterPumpSwitch(DeyeEntity, SwitchEntity): @@ -107,7 +123,10 @@ class DeyeWaterPumpSwitch(DeyeEntity, SwitchEntity): _attr_entity_category = EntityCategory.CONFIG def __init__( - self, device: DeyeApiResponseDeviceInfo, mqtt_client: DeyeMqttClient, cloud_api: DeyeCloudApi + self, + device: DeyeApiResponseDeviceInfo, + mqtt_client: DeyeMqttClient, + cloud_api: DeyeCloudApi, ) -> None: """Initialize the switch.""" super().__init__(device, mqtt_client, cloud_api) @@ -123,9 +142,9 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the water pump on.""" self.device_state.water_pump_switch = True - await self.publish_command_async('water_pump_switch', True) + await self.publish_command_async("water_pump_switch", True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the water pump off.""" self.device_state.water_pump_switch = False - await self.publish_command_async('water_pump_switch', False) + await self.publish_command_async("water_pump_switch", False)