Skip to content

Commit

Permalink
Merge pull request #149 from webdjoe/esl100mc
Browse files Browse the repository at this point in the history
Add Valceno device, improve color handling, add Oasis Mist Humidifier, improve tests
  • Loading branch information
webdjoe authored Nov 3, 2022
2 parents c65c7ef + 30e4384 commit 8861ec5
Show file tree
Hide file tree
Showing 17 changed files with 1,487 additions and 559 deletions.
3 changes: 3 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ disable=
format,
abstract-class-little-used,
abstract-method,
arguments-differ,
cyclic-import,
duplicate-code,
global-statement,
inconsistent-return-statements,
invalid-name,
locally-disabled,
no-self-use,
not-an-iterable,
redefined-variable-type,
too-few-public-methods,
Expand Down
33 changes: 29 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ pip install pyvesync
2. Classic 300S
3. LUH-D301S-WEU Dual (200S)
4. LV600S
5. OasisMist LUS-04515-WUS

## Usage

Expand Down Expand Up @@ -350,16 +351,23 @@ Compatible levels for each model:

**Properties**

`VeSyncBulb.color` - Returns a dataclass with HSV and RGB attributes that are named tuples

```
VeSyncBulb.color.rbg = namedtuple('RGB', ['red', 'green', 'blue'])
VeSyncBulb.color.hsv = namedtuple('HSV', ['hue', 'saturation', 'value'])
```

`VeSyncBulb.color_hsv` - Returns a named tuple with HSV values

`VeSyncBulb.color_rgb` - Returns a named tuple with RGB values

`VeSyncBulb.brightness` - Return brightness in percentage (int values from 1 - 100)

`VeSyncBulb.color_temp_pct` - Return white temperature in percentage (int values from 0 - 100)

`VeSyncBulb.color_temp_kelvin` - Return white temperature in Kelvin (int values from 2700-6500)

`VeSyncBulb.color_value_hsv` - Return color value in HSV format (float 0.0-360.0, float 0.0-100.0, int 0-100 )

`VeSyncBulb.color_value_rgb` - Return color value in RGB format (float values up to 255.0, 255.0, 255.0 )

`VeSyncBulb.color_mode` - Return bulb color mode (string values: 'white' , 'hsv' )

`VeSyncBulb.color_hue` - Return color hue (float values from 0.0 - 360.0)
Expand All @@ -368,12 +376,29 @@ Compatible levels for each model:

`VeSyncBulb.color_value` - Return color value (int values from 0 - 100)

*The following properties are also still available for backwards compatibility*

`VeSyncBulb.color_value_hsv` - Return color value in HSV format (float 0.0-360.0, float 0.0-100.0, int 0-100 )

`VeSyncBulb.color_value_rgb` - Return color value in RGB format (float values up to 255.0, 255.0, 255.0 )


**Methods**

`VeSyncBulb.set_brightness(brightness)`
- Set bulb brightness (int values from 0 - 100)
- (also used to set Color Value when in color mode)

`VeSyncBulb.set_hsv(hue, saturation, value)`
- Set bulb color in HSV format
- Arguments: hue (numeric) 0 - 360, saturation (numeric) 0-100, value (numeric) 0-100
- Returns bool

`VeSyncBulb.set_rgb(red, green, blue)`
- Set bulb color in RGB format
- Arguments: red (numeric) 0-255, green (numeric) 0-255, blue (numeric) 0-255
- Returns bool

`VeSyncBulb.set_color_mode(color_mode)`
- Set bulb color mode (string values: `white` , `hsv` )
- `color` may be used as an alias to `hsv`
Expand Down
4 changes: 2 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ jobs:
vmImage: 'ubuntu-20.04'
strategy:
matrix:
Python36:
python.version: '3.6'
Python37:
python.version: '3.7'
Python38:
python.version: '3.8'
Python39:
python.version: '3.9'

steps:
- task: UsePythonVersion@0
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

setup(
name='pyvesync',
version='2.0.5',
version='2.1.0',
description='pyvesync is a library to manage Etekcity\
Devices and Levoit Air Purifier',
long_description=long_description,
Expand Down
131 changes: 131 additions & 0 deletions src/pyvesync/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
import logging
import time
import json
import colorsys
from dataclasses import dataclass, field, InitVar
from typing import NamedTuple, Optional, Union
import requests


logger = logging.getLogger(__name__)

API_BASE_URL = 'https://smartapi.vesync.com'
Expand All @@ -22,6 +26,8 @@
USER_TYPE = '1'
BYPASS_APP_V = "VeSync 3.0.51"

NUMERIC = Optional[Union[int, float, str]]


class Helpers:
"""VeSync Helper Functions."""
Expand Down Expand Up @@ -276,3 +282,128 @@ def bypass_header():
'Content-Type': 'application/json; charset=UTF-8',
'User-Agent': 'okhttp/3.12.1',
}

@staticmethod
def named_tuple_to_str(named_tuple: NamedTuple) -> str:
"""Convert named tuple to string."""
tuple_str = ''
for key, val in named_tuple._asdict().items():
tuple_str += f'{key}: {val}, '
return tuple_str


class HSV(NamedTuple):
"""HSV color space."""

hue: float
saturation: float
value: float


class RGB(NamedTuple):
"""RGB color space."""

red: float
green: float
blue: float


@dataclass
class Color:
"""Dataclass for color values.
For HSV, pass hue as value in degrees 0-360, saturation and value as values
between 0 and 100.
For RGB, pass red, green and blue as values between 0 and 255.
To instantiate pass kw arguments for colors hue, saturation and value or
red, green and blue.
Instance attributes are:
hsv (nameduple) : hue (0-360), saturation (0-100), value (0-100)
rgb (namedtuple) : red (0-255), green (0-255), blue
"""

red: InitVar[NUMERIC] = field(default=None, repr=False, compare=False)
green: InitVar[NUMERIC] = field(default=None, repr=False, compare=False)
blue: InitVar[NUMERIC] = field(default=None, repr=False, compare=False)
hue: InitVar[NUMERIC] = field(default=None, repr=False, compare=False)
saturation: InitVar[NUMERIC] = field(default=None, repr=False,
compare=False)
value: InitVar[NUMERIC] = field(default=None, repr=False, compare=False)
hsv: HSV = field(init=False)
rgb: RGB = field(init=False)

def __post_init__(self, red, green, blue, hue, saturation, value):
"""Check HSV or RGB Values and create named tuples."""
if any(x is not None for x in [hue, saturation, value]):
self.hsv = HSV(*self.valid_hsv(hue, saturation, value))
self.rgb = self.hsv_to_rgb(hue, saturation, value)
elif any(x is not None for x in [red, green, blue]):
self.rgb = RGB(*self.valid_rgb(red, green, blue))
self.hsv = self.rgb_to_hsv(red, green, blue)
else:
logger.error('No color values provided')

@staticmethod
def min_max(value: Union[int, float, str], min_val: float,
max_val: float, default: float) -> float:
"""Check if value is within min and max values."""
try:
val = max(min_val, (min(max_val, round(float(value), 2))))
except (ValueError, TypeError):
val = default
return val

@classmethod
def valid_hsv(cls, h: Union[int, float, str],
s: Union[int, float, str],
v: Union[int, float, str]) -> tuple:
"""Check if HSV values are valid."""
valid_hue = float(cls.min_max(h, 0, 360, 360))
valid_saturation = float(cls.min_max(s, 0, 100, 100))
valid_value = float(cls.min_max(v, 0, 100, 100))
return (
valid_hue,
valid_saturation,
valid_value
)

@classmethod
def valid_rgb(cls, r: float, g: float, b: float) -> list:
"""Check if RGB values are valid."""
rgb = []
for val in (r, g, b):
valid_val = cls.min_max(val, 0, 255, 255)
rgb.append(valid_val)
return rgb

@staticmethod
def hsv_to_rgb(hue, saturation, value) -> RGB:
"""Convert HSV to RGB."""
return RGB(
*tuple(round(i * 255, 0) for i in colorsys.hsv_to_rgb(
hue / 360,
saturation / 100,
value / 100
))
)

@staticmethod
def rgb_to_hsv(red, green, blue) -> HSV:
"""Convert RGB to HSV."""
hsv_tuple = colorsys.rgb_to_hsv(
red / 255,
green / 255,
blue / 255
)
hsv_factors = [360, 100, 100]

return HSV(
float(round(hsv_tuple[0] * hsv_factors[0], 2)),
float(round(hsv_tuple[1] * hsv_factors[1], 2)),
float(round(hsv_tuple[2] * hsv_factors[2], 0)),
)
32 changes: 25 additions & 7 deletions src/pyvesync/vesync.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,9 @@ class VeSync: # pylint: disable=function-redefined

def __init__(self, username, password, time_zone=DEFAULT_TZ, debug=False):
"""Initialize VeSync class with username, password and time zone."""
self.debug = debug
self._debug = debug
if debug:
logger.setLevel(logging.DEBUG)
bulb_mods.logger.setLevel(logging.DEBUG)
switch_mods.logger.setLevel(logging.DEBUG)
outlet_mods.logger.setLevel(logging.DEBUG)
fan_mods.logger.setLevel(logging.DEBUG)
helpermodule.logger.setLevel(logging.DEBUG)
self.debug = debug
self.username = username
self.password = password
self.token = None
Expand Down Expand Up @@ -115,6 +110,29 @@ def __init__(self, username, password, time_zone=DEFAULT_TZ, debug=False):
self.time_zone = DEFAULT_TZ
logger.debug('Time zone is not a string')

@property
def debug(self) -> bool:
"""Return debug flag."""
return self._debug

@debug.setter
def debug(self, new_flag: bool) -> None:
"""Set debug flag."""
log_modules = [bulb_mods,
switch_mods,
outlet_mods,
fan_mods,
helpermodule]
if new_flag:
logger.setLevel(logging.DEBUG)
for m in log_modules:
m.logger.setLevel(logging.DEBUG)
elif new_flag is False:
logger.setLevel(logging.WARNING)
for m in log_modules:
m.logger.setLevel(logging.WARNING)
self._debug = new_flag

@property
def energy_update_interval(self) -> int:
"""Return energy update interval."""
Expand Down
Loading

0 comments on commit 8861ec5

Please sign in to comment.