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

Commit

Permalink
Merge remote-tracking branch 'origin/main' into fix/api-calls
Browse files Browse the repository at this point in the history
  • Loading branch information
vlebourl committed Apr 18, 2023
2 parents 99bab1a + 52af65f commit 2c2c353
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 33 deletions.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs)
[![GitHub release](https://img.shields.io/github/release/iMicknl/ha-tahoma.svg)](https://GitHub.com/vlebourl/vesync-bpo/releases/)
[![Discord](https://img.shields.io/discord/968515496217567293?label=Discord)](https://discord.gg/MbDM9WQf)
[![GitHub release](https://img.shields.io/github/v/release/vlebourl/custom_vesync.svg)](https://GitHub.com/vlebourl/custom_vesync/releases/)

# VeSync custom component for Home Assistant

Custom component for Home Assistant to interact with smart devices via the VeSync platform.
This integration is heavily based on [VeSync_bpo](https://github.com/borpin/vesync-bpo) and relies on [pyvesync](https://github.com/webdjoe/pyvesync) under the hood.

## Installation

Expand Down Expand Up @@ -35,4 +35,13 @@ logger:
pyvesync: debug
```
This integration is heavily based on [VeSync_bpo](https://github.com/borpin/vesync-bpo) and [pyvesync](https://github.com/webdjoe/pyvesync)
### Contributing
All contributions are very welcomed!
Please make sure to install `pre-commit` and run the pre-commit hook before submitting a PR.

```sh
pip install pre-commit
pre-commit install
pre-commit run --all-files
```
1 change: 1 addition & 0 deletions custom_components/vesync/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
VS_MODES = "modes"

VS_MODE_AUTO = "auto"
VS_MODE_HUMIDITY = "humidity"
VS_MODE_MANUAL = "manual"
VS_MODE_SLEEP = "sleep"

Expand Down
96 changes: 67 additions & 29 deletions custom_components/vesync/humidifier.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
"""Support for VeSync humidifiers."""
from __future__ import annotations

import logging
from typing import Any, Mapping

from homeassistant.components.humidifier import HumidifierEntity
from homeassistant.components.humidifier.const import (
Expand All @@ -11,21 +15,33 @@
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from pyvesync.vesyncfan import VeSyncHumid200300S

from .common import VeSyncDevice
from .const import (
DOMAIN,
VS_DISCOVERY,
VS_HUMIDIFIERS,
VS_MODE_AUTO,
VS_MODE_HUMIDITY,
VS_MODE_MANUAL,
VS_MODE_SLEEP,
VS_TO_HA_ATTRIBUTES,
)

_LOGGER = logging.getLogger(__name__)


MAX_HUMIDITY = 80
MIN_HUMIDITY = 30

MODES = [MODE_AUTO, MODE_NORMAL, MODE_SLEEP]

VS_TO_HA_MODE_MAP = {
VS_MODE_MANUAL: MODE_NORMAL,
VS_MODE_HUMIDITY: MODE_AUTO,
VS_MODE_SLEEP: MODE_SLEEP,
}

HA_TO_VS_MODE_MAP = {v: k for k, v in VS_TO_HA_MODE_MAP.items()}


async def async_setup_entry(
Expand Down Expand Up @@ -62,57 +78,72 @@ def _setup_entities(devices, async_add_entities, coordinator):
)


def _get_ha_mode(vs_mode: str) -> str | None:
ha_mode = VS_TO_HA_MODE_MAP.get(vs_mode)
if ha_mode is None:
_LOGGER.warning("Unknown mode '%s'", vs_mode)
return ha_mode


def _get_vs_mode(ha_mode: str) -> str | None:
vs_mode = HA_TO_VS_MODE_MAP.get(ha_mode)
if vs_mode is None:
_LOGGER.warning("Unknown mode '%s'", ha_mode)
return vs_mode


class VeSyncHumidifierHA(VeSyncDevice, HumidifierEntity):
"""Representation of a VeSync humidifier."""

_attr_max_humidity = MAX_HUMIDITY
_attr_min_humidity = MIN_HUMIDITY

def __init__(self, humidifier, coordinator):
def __init__(self, humidifier: VeSyncHumid200300S, coordinator):
"""Initialize the VeSync humidifier device."""
super().__init__(humidifier, coordinator)
self.smarthumidifier = humidifier

@property
def available_modes(self):
def available_modes(self) -> list[str]:
"""Return the available mist modes."""
return MODES
modes = []
for vs_mode in self.smarthumidifier.mist_modes:
ha_mode = _get_ha_mode(vs_mode)

if ha_mode is None:
continue

modes.append(ha_mode)

return modes

@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_MODES

@property
def target_humidity(self):
def target_humidity(self) -> int:
"""Return the humidity we try to reach."""
return self.smarthumidifier.config["auto_target_humidity"]

@property
def mode(self):
def mode(self) -> str | None:
"""Get the current preset mode."""
if self.smarthumidifier.details["mode"] == VS_MODE_AUTO:
return MODE_AUTO
if self.smarthumidifier.details["mode"] == VS_MODE_MANUAL:
return (
MODE_SLEEP
if self.smarthumidifier.details["mist_level"] == 1
else MODE_NORMAL
)
return None
return _get_ha_mode(self.smarthumidifier.details["mode"])

@property
def is_on(self):
def is_on(self) -> bool:
"""Return True if humidifier is on."""
return self.smarthumidifier.enabled # device_status is always on

@property
def unique_info(self):
def unique_info(self) -> str:
"""Return the ID of this humidifier."""
return self.smarthumidifier.uuid

@property
def extra_state_attributes(self):
def extra_state_attributes(self) -> Mapping[str, Any]:
"""Return the state attributes of the humidifier."""

attr = {}
Expand All @@ -125,32 +156,39 @@ def extra_state_attributes(self):
attr[k] = v
return attr

def set_humidity(self, humidity):
def set_humidity(self, humidity: int) -> None:
"""Set the target humidity of the device."""
if humidity not in range(self.min_humidity, self.max_humidity + 1):
raise ValueError(
"{humidity} is not between {self.min_humidity} and {self.max_humidity} (inclusive)"
)
self.smarthumidifier.set_humidity(humidity)
success = self.smarthumidifier.set_humidity(humidity)
if not success:
raise ValueError("An error occurred while setting humidity.")
self.schedule_update_ha_state()

def set_mode(self, mode):
def set_mode(self, mode: str) -> None:
"""Set the mode of the device."""
if mode not in self.available_modes:
raise ValueError(
"{mode} is not one of the valid available modes: {self.available_modes}"
)
if mode == MODE_AUTO:
self.smarthumidifier.set_humidity_mode(VS_MODE_AUTO)
else:
self.smarthumidifier.set_mist_level(
1 if mode == MODE_SLEEP else 2
) # this sets manual mode at the same time.
success = self.smarthumidifier.set_humidity_mode(_get_vs_mode(mode))
if not success:
raise ValueError("An error occurred while setting mode.")
self.schedule_update_ha_state()

def turn_on(
self,
**kwargs,
) -> None:
"""Turn the device on."""
self.smarthumidifier.turn_on()
success = self.smarthumidifier.turn_on()
if not success:
raise ValueError("An error occurred while turning on.")

def turn_off(self, **kwargs) -> None:
"""Turn the device off."""
success = self.smarthumidifier.turn_off()
if not success:
raise ValueError("An error occurred while turning off.")
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pyvesync==2.1.6
pyvesync==2.1.6

0 comments on commit 2c2c353

Please sign in to comment.