Skip to content
This repository was archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
Fix/fix fan discovery (#21)
Browse files Browse the repository at this point in the history
* added LAP-C601S-WUS (Core 600s Air Purifier)

* fix style

* better fix to diagnostic.

* fix preset mode

* add a log line.

* add another debug log

* add some missing fan devices.

* auto discover preset_modes

* auto discover fan speed range

* fix fan speed and preset modes

* fix fan speed number

* add some debug log.

* merge fix from main

* rename a key in diagnostics.
  • Loading branch information
vlebourl authored Apr 27, 2022
1 parent a87ff4c commit 0afbef7
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 29 deletions.
6 changes: 4 additions & 2 deletions custom_components/vesync/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ def _setup_entities(devices, async_add_entities):
entities = []
for dev in devices:
if is_humidifier(dev.device_type):
entities.append(VeSyncOutOfWaterSensor(dev))
entities.append(VeSyncWaterTankLiftedSensor(dev))
entities.extend(
(VeSyncOutOfWaterSensor(dev), VeSyncWaterTankLiftedSensor(dev))
)

else:
_LOGGER.warning(
"%s - Unknown device type - %s", dev.device_name, dev.device_type
Expand Down
8 changes: 8 additions & 0 deletions custom_components/vesync/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ def is_humidifier(device_type: str) -> bool:
return model_features(device_type)["module"].find("VeSyncHumid") > -1


def is_air_purifier(device_type: str) -> bool:
"""Return true if the device type is a an air purifier."""
return model_features(device_type)["module"].find("VeSyncAirBypass") > -1


async def async_process_devices(hass, manager):
"""Assign devices to proper component."""
devices = {
Expand All @@ -40,6 +45,7 @@ async def async_process_devices(hass, manager):
if manager.fans:
for fan in manager.fans:
# VeSync classifies humidifiers as fans
_LOGGER.debug("Found a fan: %s", fan.__dict__)
if is_humidifier(fan.device_type):
devices[VS_HUMIDIFIERS].append(fan)
devices[VS_NUMBERS].append(fan) # for night light and mist level
Expand All @@ -51,6 +57,8 @@ async def async_process_devices(hass, manager):
if fan.night_light:
devices[VS_LIGHTS].append(fan) # for night light
else:
if hasattr(fan, "config_dict"):
devices[VS_NUMBERS].append(fan)
devices[VS_FANS].append(fan)
_LOGGER.info("%d VeSync fans found", len(manager.fans))

Expand Down
6 changes: 5 additions & 1 deletion custom_components/vesync/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
VS_MODE_MANUAL = "manual"

DEV_TYPE_TO_HA = {
"LV-PUR131S": "fan",
"Core200S": "fan",
"Core300S": "fan",
"Core400S": "fan",
"LAP-C201S-AUSR": "fan",
"LAP-C202S-WUSR": "fan",
"LAP-C401S-WUSR": "fan",
"LAP-C601S-WUS": "fan",
"LV-PUR131S": "fan",
"Classic300S": "humidifier",
"ESD16": "walldimmer",
"ESWD16": "walldimmer",
Expand Down
10 changes: 7 additions & 3 deletions custom_components/vesync/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
from .const import DOMAIN


def _if_has_attr_else_none(obj, attr):
return getattr(obj, attr) if hasattr(obj, attr) else None


async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
Expand All @@ -19,9 +23,9 @@ async def async_get_config_entry_diagnostics(
for d in data["manager"]._dev_list[type]:
devices[type].append(
{
"device": d.config_dict or {},
"config": d.config or {},
"details": d.details or {},
"config_dict": _if_has_attr_else_none(d, "config_dict") or {},
"config": _if_has_attr_else_none(d, "config") or {},
"details": _if_has_attr_else_none(d, "details") or {},
}
)
return devices
40 changes: 19 additions & 21 deletions custom_components/vesync/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@

FAN_MODE_AUTO = "auto"
FAN_MODE_SLEEP = "sleep"
# Fixme add other models
PRESET_MODES = {
"Core200S": [FAN_MODE_SLEEP],
"Core300S": [FAN_MODE_AUTO, FAN_MODE_SLEEP],
"Core400S": [FAN_MODE_AUTO, FAN_MODE_SLEEP],
"LV-PUR131S": [FAN_MODE_AUTO, FAN_MODE_SLEEP],
}
SPEED_RANGE = (1, 3) # off is not included


async def async_setup_entry(
Expand Down Expand Up @@ -56,11 +48,12 @@ def _setup_entities(devices, async_add_entities):
"""Check if device is online and add entity."""
entities = []
for dev in devices:
_LOGGER.debug("Adding device %s %s", dev.device_name, dev.device_type)
if DEV_TYPE_TO_HA.get(dev.device_type) == "fan":
entities.append(VeSyncFanHA(dev))
else:
_LOGGER.warning(
"%s - Unknown device type - %s", dev.device_name, dev.device_type
"Unknown device type %s %s", dev.device_name, dev.device_type
)
continue

Expand All @@ -72,13 +65,25 @@ class VeSyncFanHA(VeSyncDevice, FanEntity):

def __init__(self, fan):
"""Initialize the VeSync fan device."""
_LOGGER.debug("Initializing fan")
super().__init__(fan)
self.smartfan = fan
if hasattr(self.smartfan, "config_dict"):
self._speed_range = (1, max(self.smartfan.config_dict["levels"]))
self._attr_preset_modes = [
mode
for mode in ["auto", "sleep"]
if mode in self.smartfan.config_dict["modes"]
]
else:
self._speed_range = (1, 1)
self._attr_preset_modes = []
self._attr_preset_modes = [FAN_MODE_AUTO, FAN_MODE_SLEEP]

@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_SET_SPEED
return SUPPORT_SET_SPEED if self.speed_count > 1 else 0

@property
def percentage(self):
Expand All @@ -87,25 +92,18 @@ def percentage(self):
self.smartfan.mode == "manual"
and (current_level := self.smartfan.fan_level) is not None
):
return ranged_value_to_percentage(SPEED_RANGE, current_level)
return ranged_value_to_percentage(self._speed_range, current_level)
return None

@property
def speed_count(self) -> int:
"""Return the number of speeds the fan supports."""
return int_states_in_range(SPEED_RANGE)

@property
def preset_modes(self):
"""Get the list of available preset modes."""
return PRESET_MODES[self.device.device_type]
return int_states_in_range(self._speed_range)

@property
def preset_mode(self):
"""Get the current preset mode."""
if self.smartfan.mode in (FAN_MODE_AUTO, FAN_MODE_SLEEP):
return self.smartfan.mode
return None
return self.smartfan.mode

@property
def unique_info(self):
Expand Down Expand Up @@ -151,7 +149,7 @@ def set_percentage(self, percentage):

self.smartfan.manual_mode()
self.smartfan.change_fan_speed(
math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage))
math.ceil(percentage_to_ranged_value(self._speed_range, percentage))
)
self.schedule_update_ha_state()

Expand Down
62 changes: 60 additions & 2 deletions custom_components/vesync/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .common import VeSyncBaseEntity, is_humidifier
from .common import VeSyncBaseEntity, is_air_purifier, is_humidifier
from .const import DOMAIN, VS_DISCOVERY, VS_NUMBERS

MAX_HUMIDITY = 80
Expand Down Expand Up @@ -50,7 +50,8 @@ def _setup_entities(devices, async_add_entities):
VeSyncHumidifierTargetLevelHA(dev),
)
)

elif is_air_purifier(dev.device_type):
entities.extend((VeSyncFanSpeedLevelHA(dev),))
else:
_LOGGER.debug(
"%s - Unknown device type - %s", dev.device_name, dev.device_type
Expand All @@ -60,6 +61,63 @@ def _setup_entities(devices, async_add_entities):
async_add_entities(entities, update_before_add=True)


class VeSyncFanNumberEntity(VeSyncBaseEntity, NumberEntity):
"""Representation of a number for configuring a VeSync fan."""

def __init__(self, fan):
"""Initialize the VeSync fan device."""
super().__init__(fan)
self.smartfan = fan

@property
def entity_category(self):
"""Return the diagnostic entity category."""
return EntityCategory.CONFIG


class VeSyncFanSpeedLevelHA(VeSyncFanNumberEntity):
"""Representation of the fan speed level of a VeSync fan."""

@property
def unique_id(self):
"""Return the ID of this device."""
return f"{super().unique_id}-fan-speed-level"

@property
def name(self):
"""Return the name of the device."""
return f"{super().name} fan speed level"

@property
def value(self):
"""Return the fan speed level."""
return self.device.speed

@property
def min_value(self) -> float:
"""Return the minimum fan speed level."""
return self.device.config_dict["levels"][0]

@property
def max_value(self) -> float:
"""Return the maximum fan speed level."""
return self.device.config_dict["levels"][-1]

@property
def step(self) -> float:
"""Return the steps for the fan speed level."""
return 1.0

@property
def extra_state_attributes(self):
"""Return the state attributes of the humidifier."""
return {"fan speed levels": self.device.config_dict["levels"]}

def set_value(self, value):
"""Set the fan speed level."""
self.device.change_fan_speed(int(value))


class VeSyncHumidifierNumberEntity(VeSyncBaseEntity, NumberEntity):
"""Representation of a number for configuring a VeSync humidifier."""

Expand Down

0 comments on commit 0afbef7

Please sign in to comment.