Skip to content

Commit

Permalink
Merge branch 'fan'
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexxIT committed Feb 22, 2020
2 parents 56093cb + f95d2cf commit ee8de90
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 18 deletions.
18 changes: 12 additions & 6 deletions custom_components/sonoff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,20 @@ def add_device(devicecfg: dict, state: dict):
load_platform(hass, device_class, DOMAIN, info, hass_config)
else:
# read multichannel device_class
for channels, component in enumerate(device_class, 1):
for i, component in enumerate(device_class, 1):
# read device with several channels
if isinstance(component, dict):
channels = component['channels']
component = component['device_class']

if isinstance(channels, int):
channels = [channels]
if 'device_class' in component:
# backward compatibility
channels = component['channels']
component = component['device_class']
else:
component, channels = list(component.items())[0]

if isinstance(channels, int):
channels = [channels]
else:
channels = [i]

info = {'deviceid': deviceid, 'channels': channels}
load_platform(hass, component, DOMAIN, info, hass_config)
Expand Down
133 changes: 131 additions & 2 deletions custom_components/sonoff/fan.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
from . import DOMAIN
from typing import Optional

from homeassistant.components.fan import FanEntity, SUPPORT_SET_SPEED, \
SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SPEED_OFF

from . import DOMAIN, EWeLinkDevice
from .toggle import EWeLinkToggle

IFAN02_CHANNELS = [2, 3, 4]
IFAN02_STATES = {
SPEED_OFF: {2: False},
SPEED_LOW: {2: True, 3: False, 4: False},
SPEED_MEDIUM: {2: True, 3: True, 4: False},
SPEED_HIGH: {2: True, 3: False, 4: True}
}


def setup_platform(hass, config, add_entities, discovery_info=None):
if discovery_info is None:
Expand All @@ -9,4 +22,120 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
deviceid = discovery_info['deviceid']
channels = discovery_info['channels']
device = hass.data[DOMAIN][deviceid]
add_entities([EWeLinkToggle(device, channels)])
if device.config['type'] == 'fan_light':
add_entities([SonoffFan03(device)])
elif channels == IFAN02_CHANNELS:
add_entities([SonoffFan02(device)])
else:
add_entities([EWeLinkToggle(device, channels)])


class SonoffFanBase(FanEntity):
def __init__(self, device: EWeLinkDevice):
self.device = device
self._attrs = {}
self._name = None
self._speed = None

self._update(device)

device.listen(self._update)

async def async_added_to_hass(self) -> None:
# Присваиваем имя устройства только на этом этапе, чтоб в `entity_id`
# было "sonoff_{unique_id}". Если имя присвоить в конструкторе - в
# `entity_id` попадёт имя в латинице.
self._name = self.device.name()

def _update(self, device: EWeLinkDevice):
"""Обновление от устройства.
:param device: Устройство в котором произошло обновление
"""
pass

@property
def should_poll(self) -> bool:
# Устройство само присылает обновление своего состояния по Multicast.
return False

@property
def unique_id(self) -> Optional[str]:
return self.device.deviceid

@property
def name(self) -> Optional[str]:
return self._name

@property
def speed(self) -> Optional[str]:
return self._speed

@property
def speed_list(self) -> list:
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]

@property
def supported_features(self):
return SUPPORT_SET_SPEED


class SonoffFan02(SonoffFanBase):
def _update(self, device: EWeLinkDevice):
state = device.is_on(IFAN02_CHANNELS)

if state[0]:
if not state[1] and not state[2]:
self._speed = SPEED_LOW
elif state[1] and not state[2]:
self._speed = SPEED_MEDIUM
elif not state[1] and state[2]:
self._speed = SPEED_HIGH
else:
raise Exception("Wrong iFan02 state")
else:
self._speed = SPEED_OFF

if self.hass:
self.schedule_update_ha_state()

def set_speed(self, speed: str) -> None:
channels = IFAN02_STATES.get(speed)
self.device.turn_bulk(channels)

def turn_on(self, speed: Optional[str] = None, **kwargs) -> None:
if speed:
self.set_speed(speed)
else:
self.device.turn_on([2])

def turn_off(self, **kwargs) -> None:
self.device.turn_off([2])


class SonoffFan03(SonoffFanBase):
def _update(self, device: EWeLinkDevice):
if 'fan' not in device.state:
return

if device.state['fan'] == 'on':
speed = device.state.get('speed', 1)
self._speed = self.speed_list[speed]
else:
self._speed = SPEED_OFF

if self.hass:
self.schedule_update_ha_state()

def set_speed(self, speed: str) -> None:
speed = self.speed_list.index(speed)
self.device.send('fan', {'fan': 'on', 'speed': speed})

def turn_on(self, speed: Optional[str] = None, **kwargs) -> None:
if speed:
self.set_speed(speed)
else:
self.device.send('fan', {'fan': 'on'})

def turn_off(self, **kwargs) -> None:
self.device.send('fan', {'fan': 'off'})
31 changes: 25 additions & 6 deletions custom_components/sonoff/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from homeassistant.components.light import SUPPORT_BRIGHTNESS, \
ATTR_BRIGHTNESS

from . import DOMAIN, EWeLinkDevice, utils
from . import DOMAIN, EWeLinkDevice
from .toggle import ATTRS, EWeLinkToggle

_LOGGER = logging.getLogger(__name__)
Expand All @@ -16,15 +16,34 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
deviceid = discovery_info['deviceid']
channels = discovery_info['channels']
device = hass.data[DOMAIN][deviceid]
if channels and len(channels) >= 2:
if device.config['type'] == 'fan_light':
add_entities([SonoffFan03Light(device)])
elif device.config['type'] == 'light':
add_entities([SonoffD1(device)])
elif channels and len(channels) >= 2:
add_entities([EWeLinkLightGroup(device, channels)])
elif utils.guess_device_class(device.config) == 'light':
add_entities([EWeLinkLight(device)])
else:
add_entities([EWeLinkToggle(device, channels)])


class EWeLinkLight(EWeLinkToggle):
class SonoffFan03Light(EWeLinkToggle):
def _update(self, device: EWeLinkDevice):
if 'light' not in device.state:
return

self._is_on = device.state['light'] == 'on'

if self.hass:
self.schedule_update_ha_state()

def turn_on(self, **kwargs) -> None:
self.device.send('light', {'light': 'on'})

def turn_off(self, **kwargs) -> None:
self.device.send('light', {'light': 'off'})


class SonoffD1(EWeLinkToggle):
"""Sonoff D1"""

def __init__(self, device: EWeLinkDevice, channels: list = None):
Expand Down Expand Up @@ -66,7 +85,7 @@ def turn_on(self, **kwargs) -> None:
'mode': 0})


class EWeLinkLightGroup(EWeLinkLight):
class EWeLinkLightGroup(SonoffD1):
"""Отличается от обычного переключателя настройкой яркости. Логично
использовать только для двух и более каналов. Умеет запоминать яркость на
момент выключения.
Expand Down
14 changes: 10 additions & 4 deletions custom_components/sonoff/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def decrypt(payload: dict, devicekey: str):
29: SWITCH2,
30: SWITCH3,
31: SWITCH4,
34: ['light', {'fan': [2, 3, 4]}], # Sonoff iFan02 and iFan03
44: 'light', # Sonoff D1
77: SWITCHX, # Sonoff Micro
78: SWITCHX,
Expand All @@ -167,17 +168,22 @@ def decrypt(payload: dict, devicekey: str):
}

TYPES = {
'plug': SWITCH,
'plug': SWITCH, # Basic, Mini
'enhanced_plug': SWITCH, # Sonoff Pow R2?
'th_plug': SWITCH, # Sonoff TH?
'strip': SWITCH4,
'light': 'light',
'rf': 'remote'
'strip': SWITCH4, # 4CH Pro R2, Micro!, iFan02!
'light': 'light', # D1
'rf': 'remote', # RF Bridge 433
'fan_light': ['light', 'fan'], # iFan03
}


def guess_device_class(config: dict):
"""Get device_class from uiid (from eWeLink Servers) or from zeroconf type.
Sonoff iFan02 and iFan03 both have uiid 34. But different types (strip and
fan_light) and different local API for each type. Without uiid iFan02 will
be displayed as 4 switches.
"""
uiid = config.get('uiid')
type_ = config.get('type')
Expand Down

0 comments on commit ee8de90

Please sign in to comment.