Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added component named switcher_kis switcher water heater integration. #22325

Merged
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
fbbbced
Added component named switcher_kis switcher water heater integration.
TomerFi Mar 23, 2019
5d2044f
Fixed conflicts.
TomerFi Apr 6, 2019
4820ba6
Updated requirements.
TomerFi Apr 6, 2019
17cef18
Added manifest.json file and updated CODEOWNERS.
TomerFi Apr 6, 2019
cb00473
Fixed requirements_all.txt.
TomerFi Apr 6, 2019
8aaa421
Better component tests.
TomerFi Apr 10, 2019
bb21856
Removed unnecessary parameter from fixture function.
TomerFi Apr 10, 2019
6ee2968
Removed tests section from mypy.ini.
TomerFi Apr 11, 2019
d0391d7
Remove unused ENTITY_ID_FORMAT.
TomerFi Apr 11, 2019
c9f28a5
Stop udp bridge when failed to setup the component.
TomerFi Apr 11, 2019
e70407e
Replace DISCOVERY_ constants prefix with DATA_.
TomerFi Apr 11, 2019
d65ed8f
Various change requests.
TomerFi Apr 11, 2019
0736cf1
Fixed constant name change remifications.
TomerFi Apr 11, 2019
26815c8
Added explicit name to fixture.
TomerFi Apr 11, 2019
d62f99f
Various change requests.
TomerFi Apr 11, 2019
38619e8
More various change requests.
TomerFi Apr 11, 2019
011dd16
Added EventType for homeassistant.core.Event.
TomerFi Apr 12, 2019
f778981
Switched from event driven data distribution to dispatcher type plus …
TomerFi Apr 12, 2019
6a50f3f
Removed name and icon keys from the component configuration.
TomerFi Apr 12, 2019
3495e05
Various change requests.
TomerFi Apr 13, 2019
0bd8b08
Various change reqeusts and clean-ups.
TomerFi Apr 13, 2019
e7b2330
Removed unnecessary DEPENDENCIES constant from swith platform.
TomerFi Apr 13, 2019
c4441c8
Replaced configuration data guard with assert.
TomerFi Apr 13, 2019
8babc45
Removed unused constants.
TomerFi Apr 13, 2019
de6a41f
Removed confusing type casting for mypy sake.
TomerFi Apr 13, 2019
105df9f
Refactor property device_name to name.
TomerFi Apr 13, 2019
b1c68d9
Removed None guard effecting mypy only.
TomerFi Apr 13, 2019
dccbe44
Removed unnecessary function from switch entity.
TomerFi Apr 13, 2019
6709f09
Removed None guard in use by mypy only.
TomerFi Apr 13, 2019
77458d1
Removed unused constant.
TomerFi Apr 13, 2019
5237b97
Removed unnecessary context manager.
TomerFi Apr 13, 2019
a53ee0d
Stopped messing around with mypy.ini.
TomerFi Apr 13, 2019
f3812d3
Referring to typing.TYPE_CHECKING for non-runtime imports.
TomerFi Apr 13, 2019
c18e742
Added test requierment correctyly.
TomerFi Apr 13, 2019
c518dc4
Replaced queue.get() with queue.get_nowait() to avoid backing up inte…
TomerFi Apr 13, 2019
cb7ab11
Revert changes in mypy.ini.
TomerFi Apr 14, 2019
02704fb
Changed attributes content to device properties instead of entity pro…
TomerFi Apr 15, 2019
f63e694
Fixed typo in constant name.
TomerFi Apr 15, 2019
5b30d84
Remove unnecessary async keyword from callable.
TomerFi Apr 15, 2019
0557f5a
Waiting for tasks on event loop to end.
TomerFi Apr 15, 2019
e26245e
Added callback decorator to callable.
TomerFi Apr 19, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Expand Up @@ -565,6 +565,7 @@ omit =
homeassistant/components/swiss_public_transport/sensor.py
homeassistant/components/swisscom/device_tracker.py
homeassistant/components/switchbot/switch.py
homeassistant/components/switcher_kis/switch.py
homeassistant/components/switchmate/switch.py
homeassistant/components/syncthru/sensor.py
homeassistant/components/synology/camera.py
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -199,6 +199,7 @@ homeassistant/components/sun/* @home-assistant/core
homeassistant/components/swiss_hydrological_data/* @fabaff
homeassistant/components/swiss_public_transport/* @fabaff
homeassistant/components/switchbot/* @danielhiversen
homeassistant/components/switcher_kis/* @tomerfi
homeassistant/components/switchmate/* @danielhiversen
homeassistant/components/synology_srm/* @aerialls
homeassistant/components/syslog/* @fabaff
Expand Down
91 changes: 91 additions & 0 deletions homeassistant/components/switcher_kis/__init__.py
@@ -0,0 +1,91 @@
"""Home Assistant Switcher Component."""

from asyncio import QueueEmpty, TimeoutError as Asyncio_TimeoutError, wait_for
from datetime import datetime, timedelta
from logging import getLogger
from typing import Dict, Optional

import voluptuous as vol

from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import EventType, HomeAssistantType

_LOGGER = getLogger(__name__)

DOMAIN = 'switcher_kis'

CONF_DEVICE_ID = 'device_id'
CONF_DEVICE_PASSWORD = 'device_password'
CONF_PHONE_ID = 'phone_id'

DATA_DEVICE = 'device'

SIGNAL_SWITCHER_DEVICE_UPDATE = 'switcher_device_update'

ATTR_AUTO_OFF_SET = 'auto_off_set'
ATTR_ELECTRIC_CURRNET = 'electric_current'
TomerFi marked this conversation as resolved.
Show resolved Hide resolved
ATTR_REMAINING_TIME = 'remaining_time'

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_PHONE_ID): cv.string,
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Required(CONF_DEVICE_PASSWORD): cv.string
})
}, extra=vol.ALLOW_EXTRA)


async def async_setup(hass: HomeAssistantType, config: Dict) -> bool:
"""Set up the switcher component."""
from aioswitcher.bridge import SwitcherV2Bridge

phone_id = config[DOMAIN][CONF_PHONE_ID]
device_id = config[DOMAIN][CONF_DEVICE_ID]
device_password = config[DOMAIN][CONF_DEVICE_PASSWORD]

v2bridge = SwitcherV2Bridge(
hass.loop, phone_id, device_id, device_password)

await v2bridge.start()

async def async_stop_bridge(event: EventType) -> None:
"""On homeassistant stop, gracefully stop the bridge if running."""
await v2bridge.stop()

hass.async_add_job(hass.bus.async_listen_once(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't use hass.async_add_job anymore. It's legacy.

Why do we need to add a job for this at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MartinHjelmare
Much appreciated!
I addressed this and the other issues you referenced in a new PR I'm currently working on.
I'll notify you once I'll create the new pull request.

Thank you very much.
:-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new PR is here.
Besides resolving all the change requests from this PR, it also adds a couple of services and update the requirement version.
:-)

EVENT_HOMEASSISTANT_STOP, async_stop_bridge))

try:
device_data = await wait_for(
v2bridge.queue.get(), timeout=5.0, loop=hass.loop)
except (Asyncio_TimeoutError, RuntimeError):
_LOGGER.exception("failed to get response from device")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We start logging messages with capital letter.

await v2bridge.stop()
return False
andrewsayre marked this conversation as resolved.
Show resolved Hide resolved

hass.data[DOMAIN] = {
DATA_DEVICE: device_data
}

hass.async_create_task(async_load_platform(
hass, SWITCH_DOMAIN, DOMAIN, None, config))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass en emtpy dict as discovery_info, ie fourth argument.


async def device_updates(timestamp: Optional[datetime]) -> None:
TomerFi marked this conversation as resolved.
Show resolved Hide resolved
"""Use for updating the device data from the queue."""
if v2bridge.running:
try:
device_new_data = v2bridge.queue.get_nowait()
if device_new_data:
async_dispatcher_send(
hass, SIGNAL_SWITCHER_DEVICE_UPDATE, device_new_data)
except QueueEmpty:
pass

async_track_time_interval(hass, device_updates, timedelta(seconds=4))

return True
12 changes: 12 additions & 0 deletions homeassistant/components/switcher_kis/manifest.json
@@ -0,0 +1,12 @@
{
"domain": "switcher_kis",
"name": "Switcher",
"documentation": "https://www.home-assistant.io/components/switcher_kis/",
"codeowners": [
"@tomerfi"
],
"requirements": [
"aioswitcher==2019.3.21"
],
"dependencies": []
}
157 changes: 157 additions & 0 deletions homeassistant/components/switcher_kis/switch.py
@@ -0,0 +1,157 @@
"""Home Assistant Switcher Component Switch platform."""

from logging import getLogger
from typing import Callable, Dict, TYPE_CHECKING

from homeassistant.components.switch import ATTR_CURRENT_POWER_W, SwitchDevice
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.typing import HomeAssistantType

from . import (
ATTR_AUTO_OFF_SET, ATTR_ELECTRIC_CURRNET, ATTR_REMAINING_TIME,
DATA_DEVICE, DOMAIN, SIGNAL_SWITCHER_DEVICE_UPDATE)

if TYPE_CHECKING:
from aioswitcher.devices import SwitcherV2Device
from aioswitcher.api.messages import SwitcherV2ControlResponseMSG


_LOGGER = getLogger(__name__)

PROPERTIES_TO_DEVICE_ATTRIBUTES = {
'current_power_w': ATTR_CURRENT_POWER_W,
'electric_current': ATTR_ELECTRIC_CURRNET,
'remaining_time': ATTR_REMAINING_TIME,
'auto_off_set': ATTR_AUTO_OFF_SET
}


async def async_setup_platform(hass: HomeAssistantType, config: Dict,
async_add_entities: Callable,
discovery_info: Dict) -> None:
"""Set up the switcher platform for the switch component."""
assert DOMAIN in hass.data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead check if discovery_info is None and return if so.

async_add_entities([SwitcherControl(hass.data[DOMAIN][DATA_DEVICE])])


class SwitcherControl(SwitchDevice):
"""Home Assistant switch entity."""

def __init__(self, device_data: 'SwitcherV2Device') -> None:
"""Initialize the entity."""
self._self_initiated = False
self._device_data = device_data
self._state = device_data.state

@property
def electric_current(self) -> float:
TomerFi marked this conversation as resolved.
Show resolved Hide resolved
"""Return the electric current."""
return self._device_data.electric_current

@property
def remaining_time(self) -> str:
"""Return the remaining time to off command."""
return self._device_data.remaining_time

@property
def auto_off_set(self) -> str:
"""Return the auto off configuration set."""
return self._device_data.auto_off_set

@property
def name(self) -> str:
"""Return the device's name."""
return self._device_data.name

@property
def should_poll(self) -> bool:
"""Return False, entity pushes its state to HA."""
return False

@property
def unique_id(self) -> str:
"""Return a unique ID."""
return "{}-{}".format(
self._device_data.device_id, self._device_data.mac_addr)

@property
def is_on(self) -> bool:
"""Return True if entity is on."""
from aioswitcher.consts import STATE_ON as SWITCHER_STATE_ON
return self._state == SWITCHER_STATE_ON

@property
def current_power_w(self) -> int:
"""Return the current power usage in W."""
return self._device_data.power_consumption

@property
def device_state_attributes(self) -> Dict:
"""Return the optional state attributes."""
from aioswitcher.consts import WAITING_TEXT

attribs = {}

for prop, attr in PROPERTIES_TO_DEVICE_ATTRIBUTES.items():
value = getattr(self, prop)
if value and value is not WAITING_TEXT:
attribs[attr] = value

return attribs

@property
def available(self) -> bool:
"""Return True if entity is available."""
from aioswitcher.consts import (STATE_OFF as SWITCHER_STATE_OFF,
STATE_ON as SWITCHER_STATE_ON)
return self._state in [SWITCHER_STATE_ON, SWITCHER_STATE_OFF]

async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
async_dispatcher_connect(
self.hass, SIGNAL_SWITCHER_DEVICE_UPDATE, self.async_update_data)

async def async_update_data(self, device_data: 'SwitcherV2Device') -> None:
"""Update the entity data."""
if device_data:
if self._self_initiated:
andrewsayre marked this conversation as resolved.
Show resolved Hide resolved
self._self_initiated = False
else:
self._device_data = device_data
self._state = self._device_data.state
self.async_schedule_update_ha_state()

async def async_turn_on(self, **kwargs: Dict) -> None:
"""Turn the entity on.

This method must be run in the event loop and returns a coroutine.
"""
await self._control_device(True)

async def async_turn_off(self, **kwargs: Dict) -> None:
TomerFi marked this conversation as resolved.
Show resolved Hide resolved
"""Turn the entity off.

This method must be run in the event loop and returns a coroutine.
"""
await self._control_device(False)

async def _control_device(self, send_on: bool) -> None:
"""Turn the entity on or off."""
from aioswitcher.api import SwitcherV2Api
from aioswitcher.consts import (COMMAND_OFF, COMMAND_ON,
STATE_OFF as SWITCHER_STATE_OFF,
STATE_ON as SWITCHER_STATE_ON)

response = None # type: SwitcherV2ControlResponseMSG
async with SwitcherV2Api(
self.hass.loop, self._device_data.ip_addr,
self._device_data.phone_id, self._device_data.device_id,
self._device_data.device_password) as swapi:
response = await swapi.control_device(
COMMAND_ON if send_on else COMMAND_OFF)

if response and response.successful:
self._self_initiated = True
self._state = \
SWITCHER_STATE_ON if send_on else SWITCHER_STATE_OFF
self.async_schedule_update_ha_state()
1 change: 1 addition & 0 deletions homeassistant/helpers/typing.py
Expand Up @@ -7,6 +7,7 @@

GPSType = Tuple[float, float]
ConfigType = Dict[str, Any]
EventType = homeassistant.core.Event
HomeAssistantType = homeassistant.core.HomeAssistant
ServiceDataType = Dict[str, Any]
TemplateVarsType = Optional[Dict[str, Any]]
Expand Down
1 change: 0 additions & 1 deletion mypy.ini
Expand Up @@ -19,4 +19,3 @@ disallow_untyped_defs = false
[mypy-homeassistant.util.yaml]
warn_return_any = false
disallow_untyped_calls = false

TomerFi marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions requirements_all.txt
Expand Up @@ -142,6 +142,9 @@ aiolifx_effects==0.2.1
# homeassistant.components.hunterdouglas_powerview
aiopvapi==1.6.14

# homeassistant.components.switcher_kis
aioswitcher==2019.3.21

# homeassistant.components.unifi
aiounifi==4

Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Expand Up @@ -51,6 +51,9 @@ aiohttp_cors==0.7.0
# homeassistant.components.hue
aiohue==1.9.1

# homeassistant.components.switcher_kis
aioswitcher==2019.3.21

# homeassistant.components.unifi
aiounifi==4

Expand Down
1 change: 1 addition & 0 deletions script/gen_requirements_all.py
Expand Up @@ -48,6 +48,7 @@
'aiohttp_cors',
'aiohue',
'aiounifi',
'aioswitcher',
'apns2',
'av',
'axis',
Expand Down
1 change: 1 addition & 0 deletions tests/components/switcher_kis/__init__.py
@@ -0,0 +1 @@
"""Test cases and object for the Switcher integration tests."""