mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add WiLight Fan (#39541)
* Add WiLight Fan Add fan to WiLigt integration * Updated fan.py and test_fan.py * Creating new fan test * Update homeassistant/components/wilight/__init__.py OK! Done! Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/wilight/fan.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/wilight/fan.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/wilight/fan.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * As MartinHjelmare requested * Update fan.py * Update tests/components/wilight/test_fan.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update tests/components/wilight/test_fan.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update test_fan.py As Martin Hjelmare suggested Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
edfb8c3423
commit
d174c8265e
@ -10,7 +10,7 @@ from .const import DOMAIN
|
|||||||
from .parent_device import WiLightParent
|
from .parent_device import WiLightParent
|
||||||
|
|
||||||
# List the platforms that you want to support.
|
# List the platforms that you want to support.
|
||||||
PLATFORMS = ["light"]
|
PLATFORMS = ["fan", "light"]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: dict):
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
|
@ -15,7 +15,7 @@ CONF_MODEL_NAME = "model_name"
|
|||||||
WILIGHT_MANUFACTURER = "All Automacao Ltda"
|
WILIGHT_MANUFACTURER = "All Automacao Ltda"
|
||||||
|
|
||||||
# List the components supported by this integration.
|
# List the components supported by this integration.
|
||||||
ALLOWED_WILIGHT_COMPONENTS = ["light"]
|
ALLOWED_WILIGHT_COMPONENTS = ["light", "fan"]
|
||||||
|
|
||||||
|
|
||||||
class WiLightFlowHandler(ConfigFlow, domain=DOMAIN):
|
class WiLightFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
|
122
homeassistant/components/wilight/fan.py
Normal file
122
homeassistant/components/wilight/fan.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
"""Support for WiLight Fan."""
|
||||||
|
|
||||||
|
from pywilight.const import (
|
||||||
|
DOMAIN,
|
||||||
|
FAN_V1,
|
||||||
|
ITEM_FAN,
|
||||||
|
WL_DIRECTION_FORWARD,
|
||||||
|
WL_DIRECTION_OFF,
|
||||||
|
WL_DIRECTION_REVERSE,
|
||||||
|
WL_SPEED_HIGH,
|
||||||
|
WL_SPEED_LOW,
|
||||||
|
WL_SPEED_MEDIUM,
|
||||||
|
)
|
||||||
|
|
||||||
|
from homeassistant.components.fan import (
|
||||||
|
DIRECTION_FORWARD,
|
||||||
|
SPEED_HIGH,
|
||||||
|
SPEED_LOW,
|
||||||
|
SPEED_MEDIUM,
|
||||||
|
SUPPORT_DIRECTION,
|
||||||
|
SUPPORT_SET_SPEED,
|
||||||
|
FanEntity,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from . import WiLightDevice
|
||||||
|
|
||||||
|
SUPPORTED_SPEEDS = [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||||
|
|
||||||
|
SUPPORTED_FEATURES = SUPPORT_SET_SPEED | SUPPORT_DIRECTION
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
|
||||||
|
):
|
||||||
|
"""Set up WiLight lights from a config entry."""
|
||||||
|
parent = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
# Handle a discovered WiLight device.
|
||||||
|
entities = []
|
||||||
|
for item in parent.api.items:
|
||||||
|
if item["type"] != ITEM_FAN:
|
||||||
|
continue
|
||||||
|
index = item["index"]
|
||||||
|
item_name = item["name"]
|
||||||
|
if item["sub_type"] != FAN_V1:
|
||||||
|
continue
|
||||||
|
entity = WiLightFan(parent.api, index, item_name)
|
||||||
|
entities.append(entity)
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class WiLightFan(WiLightDevice, FanEntity):
|
||||||
|
"""Representation of a WiLights fan."""
|
||||||
|
|
||||||
|
def __init__(self, api_device, index, item_name):
|
||||||
|
"""Initialize the device."""
|
||||||
|
super().__init__(api_device, index, item_name)
|
||||||
|
# Initialize the WiLights fan.
|
||||||
|
self._direction = WL_DIRECTION_FORWARD
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Flag supported features."""
|
||||||
|
return SUPPORTED_FEATURES
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""Return the icon of device based on its type."""
|
||||||
|
return "mdi:fan"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return true if device is on."""
|
||||||
|
return self._status.get("direction", WL_DIRECTION_OFF) != WL_DIRECTION_OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed(self) -> str:
|
||||||
|
"""Return the current speed."""
|
||||||
|
return self._status.get("speed", SPEED_HIGH)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed_list(self) -> list:
|
||||||
|
"""Get the list of available speeds."""
|
||||||
|
return SUPPORTED_SPEEDS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_direction(self) -> str:
|
||||||
|
"""Return the current direction of the fan."""
|
||||||
|
if "direction" in self._status:
|
||||||
|
if self._status["direction"] != WL_DIRECTION_OFF:
|
||||||
|
self._direction = self._status["direction"]
|
||||||
|
return self._direction
|
||||||
|
|
||||||
|
async def async_turn_on(self, speed: str = None, **kwargs):
|
||||||
|
"""Turn on the fan."""
|
||||||
|
if speed is None:
|
||||||
|
await self._client.set_fan_direction(self._index, self._direction)
|
||||||
|
else:
|
||||||
|
await self.async_set_speed(speed)
|
||||||
|
|
||||||
|
async def async_set_speed(self, speed: str):
|
||||||
|
"""Set the speed of the fan."""
|
||||||
|
wl_speed = WL_SPEED_HIGH
|
||||||
|
if speed == SPEED_LOW:
|
||||||
|
wl_speed = WL_SPEED_LOW
|
||||||
|
if speed == SPEED_MEDIUM:
|
||||||
|
wl_speed = WL_SPEED_MEDIUM
|
||||||
|
await self._client.set_fan_speed(self._index, wl_speed)
|
||||||
|
|
||||||
|
async def async_set_direction(self, direction: str):
|
||||||
|
"""Set the direction of the fan."""
|
||||||
|
wl_direction = WL_DIRECTION_REVERSE
|
||||||
|
if direction == DIRECTION_FORWARD:
|
||||||
|
wl_direction = WL_DIRECTION_FORWARD
|
||||||
|
await self._client.set_fan_direction(self._index, wl_direction)
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs):
|
||||||
|
"""Turn the fan off."""
|
||||||
|
await self._client.set_fan_direction(self._index, WL_DIRECTION_OFF)
|
@ -3,7 +3,7 @@
|
|||||||
"name": "WiLight",
|
"name": "WiLight",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/wilight",
|
"documentation": "https://www.home-assistant.io/integrations/wilight",
|
||||||
"requirements": ["pywilight==0.0.65"],
|
"requirements": ["pywilight==0.0.66"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "All Automacao Ltda"
|
"manufacturer": "All Automacao Ltda"
|
||||||
|
@ -84,11 +84,9 @@ class WiLightParent:
|
|||||||
async def async_reset(self):
|
async def async_reset(self):
|
||||||
"""Reset api."""
|
"""Reset api."""
|
||||||
|
|
||||||
# If the initialization was wrong.
|
# If the initialization was not wrong.
|
||||||
if self._api is None:
|
if self._api is not None:
|
||||||
return True
|
self._api.client.stop()
|
||||||
|
|
||||||
self._api.client.stop()
|
|
||||||
|
|
||||||
|
|
||||||
def create_api_device(host):
|
def create_api_device(host):
|
||||||
|
@ -1892,7 +1892,7 @@ pywebpush==1.9.2
|
|||||||
pywemo==0.6.1
|
pywemo==0.6.1
|
||||||
|
|
||||||
# homeassistant.components.wilight
|
# homeassistant.components.wilight
|
||||||
pywilight==0.0.65
|
pywilight==0.0.66
|
||||||
|
|
||||||
# homeassistant.components.xeoma
|
# homeassistant.components.xeoma
|
||||||
pyxeoma==1.4.1
|
pyxeoma==1.4.1
|
||||||
|
@ -939,7 +939,7 @@ pywebpush==1.9.2
|
|||||||
pywemo==0.6.1
|
pywemo==0.6.1
|
||||||
|
|
||||||
# homeassistant.components.wilight
|
# homeassistant.components.wilight
|
||||||
pywilight==0.0.65
|
pywilight==0.0.66
|
||||||
|
|
||||||
# homeassistant.components.zerproc
|
# homeassistant.components.zerproc
|
||||||
pyzerproc==0.4.7
|
pyzerproc==0.4.7
|
||||||
|
@ -21,7 +21,6 @@ from tests.common import MockConfigEntry
|
|||||||
from tests.components.wilight import (
|
from tests.components.wilight import (
|
||||||
CONF_COMPONENTS,
|
CONF_COMPONENTS,
|
||||||
HOST,
|
HOST,
|
||||||
MOCK_SSDP_DISCOVERY_INFO_LIGHT_FAN,
|
|
||||||
MOCK_SSDP_DISCOVERY_INFO_MISSING_MANUFACTORER,
|
MOCK_SSDP_DISCOVERY_INFO_MISSING_MANUFACTORER,
|
||||||
MOCK_SSDP_DISCOVERY_INFO_P_B,
|
MOCK_SSDP_DISCOVERY_INFO_P_B,
|
||||||
MOCK_SSDP_DISCOVERY_INFO_WRONG_MANUFACTORER,
|
MOCK_SSDP_DISCOVERY_INFO_WRONG_MANUFACTORER,
|
||||||
@ -32,7 +31,7 @@ from tests.components.wilight import (
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="dummy_get_components_from_model_clear")
|
@pytest.fixture(name="dummy_get_components_from_model_clear")
|
||||||
def mock_dummy_get_components_from_model():
|
def mock_dummy_get_components_from_model_clear():
|
||||||
"""Mock a clear components list."""
|
"""Mock a clear components list."""
|
||||||
components = []
|
components = []
|
||||||
with patch(
|
with patch(
|
||||||
@ -42,6 +41,17 @@ def mock_dummy_get_components_from_model():
|
|||||||
yield components
|
yield components
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="dummy_get_components_from_model_wrong")
|
||||||
|
def mock_dummy_get_components_from_model_wrong():
|
||||||
|
"""Mock a clear components list."""
|
||||||
|
components = ["wrong"]
|
||||||
|
with patch(
|
||||||
|
"pywilight.get_components_from_model",
|
||||||
|
return_value=components,
|
||||||
|
):
|
||||||
|
yield components
|
||||||
|
|
||||||
|
|
||||||
async def test_show_ssdp_form(hass: HomeAssistantType) -> None:
|
async def test_show_ssdp_form(hass: HomeAssistantType) -> None:
|
||||||
"""Test that the ssdp confirmation form is served."""
|
"""Test that the ssdp confirmation form is served."""
|
||||||
|
|
||||||
@ -96,10 +106,12 @@ async def test_ssdp_not_wilight_abort_3(
|
|||||||
assert result["reason"] == "not_wilight_device"
|
assert result["reason"] == "not_wilight_device"
|
||||||
|
|
||||||
|
|
||||||
async def test_ssdp_not_supported_abort(hass: HomeAssistantType) -> None:
|
async def test_ssdp_not_supported_abort(
|
||||||
|
hass: HomeAssistantType, dummy_get_components_from_model_wrong
|
||||||
|
) -> None:
|
||||||
"""Test that the ssdp aborts not_supported."""
|
"""Test that the ssdp aborts not_supported."""
|
||||||
|
|
||||||
discovery_info = MOCK_SSDP_DISCOVERY_INFO_LIGHT_FAN.copy()
|
discovery_info = MOCK_SSDP_DISCOVERY_INFO_P_B.copy()
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={CONF_SOURCE: SOURCE_SSDP}, data=discovery_info
|
DOMAIN, context={CONF_SOURCE: SOURCE_SSDP}, data=discovery_info
|
||||||
)
|
)
|
||||||
|
206
tests/components/wilight/test_fan.py
Normal file
206
tests/components/wilight/test_fan.py
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
"""Tests for the WiLight integration."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import pywilight
|
||||||
|
|
||||||
|
from homeassistant.components.fan import (
|
||||||
|
ATTR_DIRECTION,
|
||||||
|
ATTR_SPEED,
|
||||||
|
DIRECTION_FORWARD,
|
||||||
|
DIRECTION_REVERSE,
|
||||||
|
DOMAIN as FAN_DOMAIN,
|
||||||
|
SERVICE_SET_DIRECTION,
|
||||||
|
SERVICE_SET_SPEED,
|
||||||
|
SPEED_HIGH,
|
||||||
|
SPEED_LOW,
|
||||||
|
SPEED_MEDIUM,
|
||||||
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_ON,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
|
from . import (
|
||||||
|
HOST,
|
||||||
|
UPNP_MAC_ADDRESS,
|
||||||
|
UPNP_MODEL_NAME_LIGHT_FAN,
|
||||||
|
UPNP_MODEL_NUMBER,
|
||||||
|
UPNP_SERIAL,
|
||||||
|
WILIGHT_ID,
|
||||||
|
setup_integration,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="dummy_device_from_host_light_fan")
|
||||||
|
def mock_dummy_device_from_host_light_fan():
|
||||||
|
"""Mock a valid api_devce."""
|
||||||
|
|
||||||
|
device = pywilight.wilight_from_discovery(
|
||||||
|
f"http://{HOST}:45995/wilight.xml",
|
||||||
|
UPNP_MAC_ADDRESS,
|
||||||
|
UPNP_MODEL_NAME_LIGHT_FAN,
|
||||||
|
UPNP_SERIAL,
|
||||||
|
UPNP_MODEL_NUMBER,
|
||||||
|
)
|
||||||
|
|
||||||
|
device.set_dummy(True)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"pywilight.device_from_host",
|
||||||
|
return_value=device,
|
||||||
|
):
|
||||||
|
yield device
|
||||||
|
|
||||||
|
|
||||||
|
async def test_loading_light_fan(
|
||||||
|
hass: HomeAssistantType,
|
||||||
|
dummy_device_from_host_light_fan,
|
||||||
|
) -> None:
|
||||||
|
"""Test the WiLight configuration entry loading."""
|
||||||
|
|
||||||
|
entry = await setup_integration(hass)
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == WILIGHT_ID
|
||||||
|
|
||||||
|
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
||||||
|
|
||||||
|
# First segment of the strip
|
||||||
|
state = hass.states.get("fan.wl000000000099_2")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
entry = entity_registry.async_get("fan.wl000000000099_2")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "WL000000000099_1"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_on_off_fan_state(
|
||||||
|
hass: HomeAssistantType, dummy_device_from_host_light_fan
|
||||||
|
) -> None:
|
||||||
|
"""Test the change of state of the fan switches."""
|
||||||
|
await setup_integration(hass)
|
||||||
|
|
||||||
|
# Turn on
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: "fan.wl000000000099_2"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("fan.wl000000000099_2")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
# Turn on with speed
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_SPEED: SPEED_LOW, ATTR_ENTITY_ID: "fan.wl000000000099_2"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("fan.wl000000000099_2")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
assert state.attributes.get(ATTR_SPEED) == SPEED_LOW
|
||||||
|
|
||||||
|
# Turn off
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{ATTR_ENTITY_ID: "fan.wl000000000099_2"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("fan.wl000000000099_2")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
|
async def test_speed_fan_state(
|
||||||
|
hass: HomeAssistantType, dummy_device_from_host_light_fan
|
||||||
|
) -> None:
|
||||||
|
"""Test the change of speed of the fan switches."""
|
||||||
|
await setup_integration(hass)
|
||||||
|
|
||||||
|
# Set speed Low
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
SERVICE_SET_SPEED,
|
||||||
|
{ATTR_SPEED: SPEED_LOW, ATTR_ENTITY_ID: "fan.wl000000000099_2"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("fan.wl000000000099_2")
|
||||||
|
assert state
|
||||||
|
assert state.attributes.get(ATTR_SPEED) == SPEED_LOW
|
||||||
|
|
||||||
|
# Set speed Medium
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
SERVICE_SET_SPEED,
|
||||||
|
{ATTR_SPEED: SPEED_MEDIUM, ATTR_ENTITY_ID: "fan.wl000000000099_2"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("fan.wl000000000099_2")
|
||||||
|
assert state
|
||||||
|
assert state.attributes.get(ATTR_SPEED) == SPEED_MEDIUM
|
||||||
|
|
||||||
|
# Set speed High
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
SERVICE_SET_SPEED,
|
||||||
|
{ATTR_SPEED: SPEED_HIGH, ATTR_ENTITY_ID: "fan.wl000000000099_2"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("fan.wl000000000099_2")
|
||||||
|
assert state
|
||||||
|
assert state.attributes.get(ATTR_SPEED) == SPEED_HIGH
|
||||||
|
|
||||||
|
|
||||||
|
async def test_direction_fan_state(
|
||||||
|
hass: HomeAssistantType, dummy_device_from_host_light_fan
|
||||||
|
) -> None:
|
||||||
|
"""Test the change of direction of the fan switches."""
|
||||||
|
await setup_integration(hass)
|
||||||
|
|
||||||
|
# Set direction Forward
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
SERVICE_SET_DIRECTION,
|
||||||
|
{ATTR_DIRECTION: DIRECTION_FORWARD, ATTR_ENTITY_ID: "fan.wl000000000099_2"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("fan.wl000000000099_2")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
assert state.attributes.get(ATTR_DIRECTION) == DIRECTION_FORWARD
|
||||||
|
|
||||||
|
# Set direction Reverse
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
SERVICE_SET_DIRECTION,
|
||||||
|
{ATTR_DIRECTION: DIRECTION_REVERSE, ATTR_ENTITY_ID: "fan.wl000000000099_2"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("fan.wl000000000099_2")
|
||||||
|
assert state
|
||||||
|
assert state.attributes.get(ATTR_DIRECTION) == DIRECTION_REVERSE
|
Loading…
x
Reference in New Issue
Block a user