Add Z-Wave.Me integration (#65473)
* Add support of Z-Wave.Me Z-Way and RaZberry server (#61182) Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: LawfulChaos <kerbalspacema@gmail.com> * Add switch platform to Z-Wave.Me integration (#64957) Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Dmitry Vlasov <kerbalspacema@gmail.com> * Add button platform to Z-Wave.Me integration (#65109) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> Co-authored-by: Dmitry Vlasov <kerbalspacema@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Fix button controller access (#65117) * Add lock platform to Z-Wave.Me integration #65109 (#65114) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> Co-authored-by: Dmitry Vlasov <kerbalspacema@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Add sensor platform to Z-Wave.Me integration (#65132) * Sensor Entity * Sensor fixes * Apply suggestions from code review Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Inline descriotion according to review proposal * State Classes for sensor * Generic sensor * Generic sensor Co-authored-by: Dmitry Vlasov <kerbalspacema@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Add binary sensor platform to Z-Wave.Me integration (#65306) * Binary Sensor Entity * Update docstring Co-authored-by: Dmitry Vlasov <kerbalspacema@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Add Light Entity platform to Z-Wave.Me integration (#65331) * Light Entity * mypy fix * Fixes, ZWaveMePlatforms enum * Apply suggestions from code review Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Fixes * Fixes * Fixes Co-authored-by: Dmitry Vlasov <kerbalspacema@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Add Thermostat platform to Z-Wave.Me integration #65331 (#65371) * Climate entity * Climate entity * Apply suggestions from code review Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Climate entity fix * Clean up * cleanup * Import order fix * Correct naming Co-authored-by: Dmitry Vlasov <kerbalspacema@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Correct zwave_me .coveragerc (#65491) Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: LawfulChaos <kerbalspacema@gmail.com> Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>pull/65958/head
parent
b1015296d9
commit
3c5a667d97
10
.coveragerc
10
.coveragerc
|
@ -1410,6 +1410,16 @@ omit =
|
|||
homeassistant/components/zwave/util.py
|
||||
homeassistant/components/zwave_js/discovery.py
|
||||
homeassistant/components/zwave_js/sensor.py
|
||||
homeassistant/components/zwave_me/__init__.py
|
||||
homeassistant/components/zwave_me/binary_sensor.py
|
||||
homeassistant/components/zwave_me/button.py
|
||||
homeassistant/components/zwave_me/climate.py
|
||||
homeassistant/components/zwave_me/helpers.py
|
||||
homeassistant/components/zwave_me/light.py
|
||||
homeassistant/components/zwave_me/lock.py
|
||||
homeassistant/components/zwave_me/number.py
|
||||
homeassistant/components/zwave_me/sensor.py
|
||||
homeassistant/components/zwave_me/switch.py
|
||||
|
||||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
|
|
|
@ -1111,6 +1111,8 @@ homeassistant/components/zwave/* @home-assistant/z-wave
|
|||
tests/components/zwave/* @home-assistant/z-wave
|
||||
homeassistant/components/zwave_js/* @home-assistant/z-wave
|
||||
tests/components/zwave_js/* @home-assistant/z-wave
|
||||
homeassistant/components/zwave_me/* @lawfulchaos @Z-Wave-Me
|
||||
tests/components/zwave_me/* @lawfulchaos @Z-Wave-Me
|
||||
|
||||
# Individual files
|
||||
homeassistant/components/demo/weather @fabaff
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
"""The Z-Wave-Me WS integration."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from zwave_me_ws import ZWaveMe, ZWaveMeData
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_TOKEN, CONF_URL
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN, PLATFORMS, ZWaveMePlatform
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
ZWAVE_ME_PLATFORMS = [platform.value for platform in ZWaveMePlatform]
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry):
|
||||
"""Set up Z-Wave-Me from a config entry."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
controller = hass.data[DOMAIN][entry.entry_id] = ZWaveMeController(hass, entry)
|
||||
if await controller.async_establish_connection():
|
||||
hass.async_create_task(async_setup_platforms(hass, entry, controller))
|
||||
return True
|
||||
raise ConfigEntryNotReady()
|
||||
|
||||
|
||||
async def async_unload_entry(hass, entry):
|
||||
"""Unload a config entry."""
|
||||
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
controller = hass.data[DOMAIN].pop(entry.entry_id)
|
||||
await controller.zwave_api.close_ws()
|
||||
return unload_ok
|
||||
|
||||
|
||||
class ZWaveMeController:
|
||||
"""Main ZWave-Me API class."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: ConfigEntry) -> None:
|
||||
"""Create the API instance."""
|
||||
self.device_ids: set = set()
|
||||
self._hass = hass
|
||||
self.config = config
|
||||
self.zwave_api = ZWaveMe(
|
||||
on_device_create=self.on_device_create,
|
||||
on_device_update=self.on_device_update,
|
||||
on_new_device=self.add_device,
|
||||
token=self.config.data[CONF_TOKEN],
|
||||
url=self.config.data[CONF_URL],
|
||||
platforms=ZWAVE_ME_PLATFORMS,
|
||||
)
|
||||
self.platforms_inited = False
|
||||
|
||||
async def async_establish_connection(self):
|
||||
"""Get connection status."""
|
||||
is_connected = await self.zwave_api.get_connection()
|
||||
return is_connected
|
||||
|
||||
def add_device(self, device: ZWaveMeData) -> None:
|
||||
"""Send signal to create device."""
|
||||
if device.deviceType in ZWAVE_ME_PLATFORMS and self.platforms_inited:
|
||||
if device.id in self.device_ids:
|
||||
dispatcher_send(self._hass, f"ZWAVE_ME_INFO_{device.id}", device)
|
||||
else:
|
||||
dispatcher_send(
|
||||
self._hass, f"ZWAVE_ME_NEW_{device.deviceType.upper()}", device
|
||||
)
|
||||
self.device_ids.add(device.id)
|
||||
|
||||
def on_device_create(self, devices: list) -> None:
|
||||
"""Create multiple devices."""
|
||||
for device in devices:
|
||||
self.add_device(device)
|
||||
|
||||
def on_device_update(self, new_info: ZWaveMeData) -> None:
|
||||
"""Send signal to update device."""
|
||||
dispatcher_send(self._hass, f"ZWAVE_ME_INFO_{new_info.id}", new_info)
|
||||
|
||||
|
||||
async def async_setup_platforms(
|
||||
hass: HomeAssistant, entry: ConfigEntry, controller: ZWaveMeController
|
||||
) -> None:
|
||||
"""Set up platforms."""
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_setup(entry, platform)
|
||||
for platform in PLATFORMS
|
||||
]
|
||||
)
|
||||
controller.platforms_inited = True
|
||||
|
||||
await hass.async_add_executor_job(controller.zwave_api.get_devices)
|
||||
|
||||
|
||||
class ZWaveMeEntity(Entity):
|
||||
"""Representation of a ZWaveMe device."""
|
||||
|
||||
def __init__(self, controller, device):
|
||||
"""Initialize the device."""
|
||||
self.controller = controller
|
||||
self.device = device
|
||||
self._attr_name = device.title
|
||||
self._attr_unique_id = f"{self.controller.config.unique_id}-{self.device.id}"
|
||||
self._attr_should_poll = False
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Connect to an updater."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, f"ZWAVE_ME_INFO_{self.device.id}", self.get_new_data
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def get_new_data(self, new_data):
|
||||
"""Update info in the HAss."""
|
||||
self.device = new_data
|
||||
self._attr_available = not new_data.isFailed
|
||||
self.async_write_ha_state()
|
|
@ -0,0 +1,75 @@
|
|||
"""Representation of a sensorBinary."""
|
||||
from __future__ import annotations
|
||||
|
||||
from zwave_me_ws import ZWaveMeData
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASS_MOTION,
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import ZWaveMeController, ZWaveMeEntity
|
||||
from .const import DOMAIN, ZWaveMePlatform
|
||||
|
||||
BINARY_SENSORS_MAP: dict[str, BinarySensorEntityDescription] = {
|
||||
"generic": BinarySensorEntityDescription(
|
||||
key="generic",
|
||||
),
|
||||
"motion": BinarySensorEntityDescription(
|
||||
key="motion",
|
||||
device_class=DEVICE_CLASS_MOTION,
|
||||
),
|
||||
}
|
||||
DEVICE_NAME = ZWaveMePlatform.BINARY_SENSOR
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the binary sensor platform."""
|
||||
|
||||
@callback
|
||||
def add_new_device(new_device: ZWaveMeData) -> None:
|
||||
controller: ZWaveMeController = hass.data[DOMAIN][config_entry.entry_id]
|
||||
description = BINARY_SENSORS_MAP.get(
|
||||
new_device.probeType, BINARY_SENSORS_MAP["generic"]
|
||||
)
|
||||
sensor = ZWaveMeBinarySensor(controller, new_device, description)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
sensor,
|
||||
]
|
||||
)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ZWaveMeBinarySensor(ZWaveMeEntity, BinarySensorEntity):
|
||||
"""Representation of a ZWaveMe binary sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
controller: ZWaveMeController,
|
||||
device: ZWaveMeData,
|
||||
description: BinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the device."""
|
||||
super().__init__(controller=controller, device=device)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the state of the sensor."""
|
||||
return self.device.level == "on"
|
|
@ -0,0 +1,40 @@
|
|||
"""Representation of a toggleButton."""
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.button import ButtonEntity
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from . import ZWaveMeEntity
|
||||
from .const import DOMAIN, ZWaveMePlatform
|
||||
|
||||
DEVICE_NAME = ZWaveMePlatform.BUTTON
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the number platform."""
|
||||
|
||||
@callback
|
||||
def add_new_device(new_device):
|
||||
controller = hass.data[DOMAIN][config_entry.entry_id]
|
||||
button = ZWaveMeButton(controller, new_device)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
button,
|
||||
]
|
||||
)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ZWaveMeButton(ZWaveMeEntity, ButtonEntity):
|
||||
"""Representation of a ZWaveMe button."""
|
||||
|
||||
def press(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
self.controller.zwave_api.send_command(self.device.id, "on")
|
|
@ -0,0 +1,101 @@
|
|||
"""Representation of a thermostat."""
|
||||
from __future__ import annotations
|
||||
|
||||
from zwave_me_ws import ZWaveMeData
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
HVAC_MODE_HEAT,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import ZWaveMeEntity
|
||||
from .const import DOMAIN, ZWaveMePlatform
|
||||
|
||||
TEMPERATURE_DEFAULT_STEP = 0.5
|
||||
|
||||
DEVICE_NAME = ZWaveMePlatform.CLIMATE
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the climate platform."""
|
||||
|
||||
@callback
|
||||
def add_new_device(new_device: ZWaveMeData) -> None:
|
||||
"""Add a new device."""
|
||||
controller = hass.data[DOMAIN][config_entry.entry_id]
|
||||
climate = ZWaveMeClimate(controller, new_device)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
climate,
|
||||
]
|
||||
)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ZWaveMeClimate(ZWaveMeEntity, ClimateEntity):
|
||||
"""Representation of a ZWaveMe sensor."""
|
||||
|
||||
def set_temperature(self, **kwargs) -> None:
|
||||
"""Set new target temperature."""
|
||||
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
|
||||
return
|
||||
|
||||
self.controller.zwave_api.send_command(
|
||||
self.device.id, f"exact?level={temperature}"
|
||||
)
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the temperature_unit."""
|
||||
return self.device.scaleTitle
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> float:
|
||||
"""Return the state of the sensor."""
|
||||
return self.device.level
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
"""Return min temperature for the device."""
|
||||
return self.device.max
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
"""Return max temperature for the device."""
|
||||
return self.device.min
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> list[str]:
|
||||
"""Return the list of available operation modes."""
|
||||
return [HVAC_MODE_HEAT]
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return the current mode."""
|
||||
return HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return the supported features."""
|
||||
return SUPPORT_TARGET_TEMPERATURE
|
||||
|
||||
@property
|
||||
def target_temperature_step(self) -> float:
|
||||
"""Return the supported step of target temperature."""
|
||||
return TEMPERATURE_DEFAULT_STEP
|
|
@ -0,0 +1,84 @@
|
|||
"""Config flow to configure ZWaveMe integration."""
|
||||
|
||||
import logging
|
||||
|
||||
from url_normalize import url_normalize
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_TOKEN, CONF_URL
|
||||
|
||||
from . import helpers
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ZWaveMeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""ZWaveMe integration config flow."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize flow."""
|
||||
self.url = None
|
||||
self.token = None
|
||||
self.uuid = None
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user or started with zeroconf."""
|
||||
errors = {}
|
||||
if self.url is None:
|
||||
schema = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_URL): str,
|
||||
vol.Required(CONF_TOKEN): str,
|
||||
}
|
||||
)
|
||||
else:
|
||||
schema = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_TOKEN): str,
|
||||
}
|
||||
)
|
||||
|
||||
if user_input is not None:
|
||||
if self.url is None:
|
||||
self.url = user_input[CONF_URL]
|
||||
|
||||
self.token = user_input[CONF_TOKEN]
|
||||
if not self.url.startswith(("ws://", "wss://")):
|
||||
self.url = f"ws://{self.url}"
|
||||
self.url = url_normalize(self.url, default_scheme="ws")
|
||||
if self.uuid is None:
|
||||
self.uuid = await helpers.get_uuid(self.url, self.token)
|
||||
if self.uuid is not None:
|
||||
await self.async_set_unique_id(self.uuid, raise_on_progress=False)
|
||||
self._abort_if_unique_id_configured()
|
||||
else:
|
||||
errors["base"] = "no_valid_uuid_set"
|
||||
|
||||
if not errors:
|
||||
return self.async_create_entry(
|
||||
title=self.url,
|
||||
data={CONF_URL: self.url, CONF_TOKEN: self.token},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=schema,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_zeroconf(self, discovery_info):
|
||||
"""
|
||||
Handle a discovered Z-Wave accessory - get url to pass into user step.
|
||||
|
||||
This flow is triggered by the discovery component.
|
||||
"""
|
||||
self.url = discovery_info.host
|
||||
self.uuid = await helpers.get_uuid(self.url)
|
||||
if self.uuid is None:
|
||||
return self.async_abort(reason="no_valid_uuid_set")
|
||||
|
||||
await self.async_set_unique_id(self.uuid)
|
||||
self._abort_if_unique_id_configured()
|
||||
return await self.async_step_user()
|
|
@ -0,0 +1,32 @@
|
|||
"""Constants for ZWaveMe."""
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
from homeassistant.const import Platform
|
||||
|
||||
# Base component constants
|
||||
DOMAIN = "zwave_me"
|
||||
|
||||
|
||||
class ZWaveMePlatform(StrEnum):
|
||||
"""Included ZWaveMe platforms."""
|
||||
|
||||
BINARY_SENSOR = "sensorBinary"
|
||||
BUTTON = "toggleButton"
|
||||
CLIMATE = "thermostat"
|
||||
LOCK = "doorlock"
|
||||
NUMBER = "switchMultilevel"
|
||||
SWITCH = "switchBinary"
|
||||
SENSOR = "sensorMultilevel"
|
||||
RGBW_LIGHT = "switchRGBW"
|
||||
RGB_LIGHT = "switchRGB"
|
||||
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.CLIMATE,
|
||||
Platform.LIGHT,
|
||||
Platform.LOCK,
|
||||
Platform.NUMBER,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
]
|
|
@ -0,0 +1,14 @@
|
|||
"""Helpers for zwave_me config flow."""
|
||||
from __future__ import annotations
|
||||
|
||||
from zwave_me_ws import ZWaveMe
|
||||
|
||||
|
||||
async def get_uuid(url: str, token: str | None = None) -> str | None:
|
||||
"""Get an uuid from Z-Wave-Me."""
|
||||
conn = ZWaveMe(url=url, token=token)
|
||||
uuid = None
|
||||
if await conn.get_connection():
|
||||
uuid = await conn.get_uuid()
|
||||
await conn.close_ws()
|
||||
return uuid
|
|
@ -0,0 +1,85 @@
|
|||
"""Representation of an RGB light."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from zwave_me_ws import ZWaveMeData
|
||||
|
||||
from homeassistant.components.light import ATTR_RGB_COLOR, COLOR_MODE_RGB, LightEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import ZWaveMeEntity
|
||||
from .const import DOMAIN, ZWaveMePlatform
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the rgb platform."""
|
||||
|
||||
@callback
|
||||
def add_new_device(new_device: ZWaveMeData) -> None:
|
||||
"""Add a new device."""
|
||||
controller = hass.data[DOMAIN][config_entry.entry_id]
|
||||
rgb = ZWaveMeRGB(controller, new_device)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
rgb,
|
||||
]
|
||||
)
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, f"ZWAVE_ME_NEW_{ZWaveMePlatform.RGB_LIGHT.upper()}", add_new_device
|
||||
)
|
||||
async_dispatcher_connect(
|
||||
hass, f"ZWAVE_ME_NEW_{ZWaveMePlatform.RGBW_LIGHT.upper()}", add_new_device
|
||||
)
|
||||
|
||||
|
||||
class ZWaveMeRGB(ZWaveMeEntity, LightEntity):
|
||||
"""Representation of a ZWaveMe light."""
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on."""
|
||||
self.controller.zwave_api.send_command(self.device.id, "off")
|
||||
|
||||
def turn_on(self, **kwargs: Any):
|
||||
"""Turn the device on."""
|
||||
color = kwargs.get(ATTR_RGB_COLOR)
|
||||
|
||||
if color is None:
|
||||
color = (122, 122, 122)
|
||||
cmd = "exact?red={}&green={}&blue={}".format(*color)
|
||||
self.controller.zwave_api.send_command(self.device.id, cmd)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if the light is on."""
|
||||
return self.device.level == "on"
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
"""Return the brightness of a device."""
|
||||
return max(self.device.color.values())
|
||||
|
||||
@property
|
||||
def rgb_color(self) -> tuple[int, int, int]:
|
||||
"""Return the rgb color value [int, int, int]."""
|
||||
rgb = self.device.color
|
||||
return rgb["r"], rgb["g"], rgb["b"]
|
||||
|
||||
@property
|
||||
def supported_color_modes(self) -> set:
|
||||
"""Return all color modes."""
|
||||
return {COLOR_MODE_RGB}
|
||||
|
||||
@property
|
||||
def color_mode(self) -> str:
|
||||
"""Return current color mode."""
|
||||
return COLOR_MODE_RGB
|
|
@ -0,0 +1,60 @@
|
|||
"""Representation of a doorlock."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from zwave_me_ws import ZWaveMeData
|
||||
|
||||
from homeassistant.components.lock import LockEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import ZWaveMeEntity
|
||||
from .const import DOMAIN, ZWaveMePlatform
|
||||
|
||||
DEVICE_NAME = ZWaveMePlatform.LOCK
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the lock platform."""
|
||||
|
||||
@callback
|
||||
def add_new_device(new_device: ZWaveMeData) -> None:
|
||||
"""Add a new device."""
|
||||
controller = hass.data[DOMAIN][config_entry.entry_id]
|
||||
lock = ZWaveMeLock(controller, new_device)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
lock,
|
||||
]
|
||||
)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ZWaveMeLock(ZWaveMeEntity, LockEntity):
|
||||
"""Representation of a ZWaveMe lock."""
|
||||
|
||||
@property
|
||||
def is_locked(self) -> bool:
|
||||
"""Return the state of the lock."""
|
||||
return self.device.level == "close"
|
||||
|
||||
def unlock(self, **kwargs: Any) -> None:
|
||||
"""Send command to unlock the lock."""
|
||||
self.controller.zwave_api.send_command(self.device.id, "open")
|
||||
|
||||
def lock(self, **kwargs: Any) -> None:
|
||||
"""Send command to lock the lock."""
|
||||
self.controller.zwave_api.send_command(self.device.id, "close")
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"domain": "zwave_me",
|
||||
"name": "Z-Wave.Me",
|
||||
"documentation": "https://www.home-assistant.io/integrations/zwave_me",
|
||||
"iot_class": "local_push",
|
||||
"requirements": [
|
||||
"zwave_me_ws==0.1.23",
|
||||
"url-normalize==1.4.1"
|
||||
],
|
||||
"after_dependencies": ["zeroconf"],
|
||||
"zeroconf": [{"type":"_hap._tcp.local.", "name": "*z.wave-me*"}],
|
||||
"config_flow": true,
|
||||
"codeowners": [
|
||||
"@lawfulchaos",
|
||||
"@Z-Wave-Me"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
"""Representation of a switchMultilevel."""
|
||||
from homeassistant.components.number import NumberEntity
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from . import ZWaveMeEntity
|
||||
from .const import DOMAIN, ZWaveMePlatform
|
||||
|
||||
DEVICE_NAME = ZWaveMePlatform.NUMBER
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the number platform."""
|
||||
|
||||
@callback
|
||||
def add_new_device(new_device):
|
||||
controller = hass.data[DOMAIN][config_entry.entry_id]
|
||||
switch = ZWaveMeNumber(controller, new_device)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
switch,
|
||||
]
|
||||
)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ZWaveMeNumber(ZWaveMeEntity, NumberEntity):
|
||||
"""Representation of a ZWaveMe Multilevel Switch."""
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self.device.level
|
||||
|
||||
def set_value(self, value: float) -> None:
|
||||
"""Update the current value."""
|
||||
self.controller.zwave_api.send_command(
|
||||
self.device.id, f"exact?level={str(round(value))}"
|
||||
)
|
|
@ -0,0 +1,120 @@
|
|||
"""Representation of a sensorMultilevel."""
|
||||
from __future__ import annotations
|
||||
|
||||
from zwave_me_ws import ZWaveMeData
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ELECTRIC_POTENTIAL_VOLT,
|
||||
ENERGY_KILO_WATT_HOUR,
|
||||
LIGHT_LUX,
|
||||
POWER_WATT,
|
||||
SIGNAL_STRENGTH_DECIBELS,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import ZWaveMeController, ZWaveMeEntity
|
||||
from .const import DOMAIN, ZWaveMePlatform
|
||||
|
||||
SENSORS_MAP: dict[str, SensorEntityDescription] = {
|
||||
"meterElectric_watt": SensorEntityDescription(
|
||||
key="meterElectric_watt",
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=POWER_WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"meterElectric_kilowatt_hour": SensorEntityDescription(
|
||||
key="meterElectric_kilowatt_hour",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
"meterElectric_voltage": SensorEntityDescription(
|
||||
key="meterElectric_voltage",
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"light": SensorEntityDescription(
|
||||
key="light",
|
||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||
native_unit_of_measurement=LIGHT_LUX,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"noise": SensorEntityDescription(
|
||||
key="noise",
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"currentTemperature": SensorEntityDescription(
|
||||
key="currentTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"temperature": SensorEntityDescription(
|
||||
key="temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"generic": SensorEntityDescription(
|
||||
key="generic",
|
||||
),
|
||||
}
|
||||
DEVICE_NAME = ZWaveMePlatform.SENSOR
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the sensor platform."""
|
||||
|
||||
@callback
|
||||
def add_new_device(new_device: ZWaveMeData) -> None:
|
||||
controller: ZWaveMeController = hass.data[DOMAIN][config_entry.entry_id]
|
||||
description = SENSORS_MAP.get(new_device.probeType, SENSORS_MAP["generic"])
|
||||
sensor = ZWaveMeSensor(controller, new_device, description)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
sensor,
|
||||
]
|
||||
)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ZWaveMeSensor(ZWaveMeEntity, SensorEntity):
|
||||
"""Representation of a ZWaveMe sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
controller: ZWaveMeController,
|
||||
device: ZWaveMeData,
|
||||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the device."""
|
||||
super().__init__(controller=controller, device=device)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def native_value(self) -> str:
|
||||
"""Return the state of the sensor."""
|
||||
return self.device.level
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Input IP address of Z-Way server and Z-Way access token. IP address can be prefixed with wss:// if HTTPS should be used instead of HTTP. To get the token go to the Z-Way user interface > Menu > Settings > User > API token. It is suggested to create a new user for Home Assistant and grant access to devices you need to control from Home Assistant. It is also possible to use remote access via find.z-wave.me to connect a remote Z-Way. Input wss://find.z-wave.me in IP field and copy the token with Global scope (log-in to Z-Way via find.z-wave.me for this).",
|
||||
"data": {
|
||||
"url": "[%key:common::config_flow::data::url%]",
|
||||
"token": "Token"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"no_valid_uuid_set": "No valid UUID set"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"no_valid_uuid_set": "No valid UUID set"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
"""Representation of a switchBinary."""
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
SwitchDeviceClass,
|
||||
SwitchEntity,
|
||||
SwitchEntityDescription,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from . import ZWaveMeEntity
|
||||
from .const import DOMAIN, ZWaveMePlatform
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DEVICE_NAME = ZWaveMePlatform.SWITCH
|
||||
|
||||
SWITCH_MAP: dict[str, SwitchEntityDescription] = {
|
||||
"generic": SwitchEntityDescription(
|
||||
key="generic",
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the switch platform."""
|
||||
|
||||
@callback
|
||||
def add_new_device(new_device):
|
||||
controller = hass.data[DOMAIN][config_entry.entry_id]
|
||||
switch = ZWaveMeSwitch(controller, new_device, SWITCH_MAP["generic"])
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
switch,
|
||||
]
|
||||
)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, f"ZWAVE_ME_NEW_{DEVICE_NAME.upper()}", add_new_device
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ZWaveMeSwitch(ZWaveMeEntity, SwitchEntity):
|
||||
"""Representation of a ZWaveMe binary switch."""
|
||||
|
||||
def __init__(self, controller, device, description):
|
||||
"""Initialize the device."""
|
||||
super().__init__(controller, device)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the state of the switch."""
|
||||
return self.device.level == "on"
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
self.controller.zwave_api.send_command(self.device.id, "on")
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity off."""
|
||||
self.controller.zwave_api.send_command(self.device.id, "off")
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured",
|
||||
"no_valid_uuid_set": "No valid UUID set"
|
||||
},
|
||||
"error": {
|
||||
"no_valid_uuid_set": "No valid UUID set"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"token": "Token",
|
||||
"url": "URL"
|
||||
},
|
||||
"description": "Input IP address of Z-Way server and Z-Way access token. IP address can be prefixed with wss:// if HTTPS should be used instead of HTTP. To get the token go to the Z-Way user interface > Menu > Settings > User > API token. It is suggested to create a new user for Home Assistant and grant access to devices you need to control from Home Assistant. It is also possible to use remote access via find.z-wave.me to connect a remote Z-Way. Input wss://find.z-wave.me in IP field and copy the token with Global scope (log-in to Z-Way via find.z-wave.me for this)."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -378,5 +378,6 @@ FLOWS = [
|
|||
"zerproc",
|
||||
"zha",
|
||||
"zwave",
|
||||
"zwave_js"
|
||||
"zwave_js",
|
||||
"zwave_me"
|
||||
]
|
||||
|
|
|
@ -144,6 +144,10 @@ ZEROCONF = {
|
|||
"_hap._tcp.local.": [
|
||||
{
|
||||
"domain": "homekit_controller"
|
||||
},
|
||||
{
|
||||
"domain": "zwave_me",
|
||||
"name": "*z.wave-me*"
|
||||
}
|
||||
],
|
||||
"_homekit._tcp.local.": [
|
||||
|
|
|
@ -2408,6 +2408,7 @@ upcloud-api==2.0.0
|
|||
|
||||
# homeassistant.components.huawei_lte
|
||||
# homeassistant.components.syncthru
|
||||
# homeassistant.components.zwave_me
|
||||
url-normalize==1.4.1
|
||||
|
||||
# homeassistant.components.uscis
|
||||
|
@ -2562,3 +2563,6 @@ zm-py==0.5.2
|
|||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.34.0
|
||||
|
||||
# homeassistant.components.zwave_me
|
||||
zwave_me_ws==0.1.23
|
||||
|
|
|
@ -1475,6 +1475,7 @@ upcloud-api==2.0.0
|
|||
|
||||
# homeassistant.components.huawei_lte
|
||||
# homeassistant.components.syncthru
|
||||
# homeassistant.components.zwave_me
|
||||
url-normalize==1.4.1
|
||||
|
||||
# homeassistant.components.uvc
|
||||
|
@ -1578,3 +1579,6 @@ zigpy==0.43.0
|
|||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.34.0
|
||||
|
||||
# homeassistant.components.zwave_me
|
||||
zwave_me_ws==0.1.23
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
"""Tests for the zwave_me integration."""
|
|
@ -0,0 +1,184 @@
|
|||
"""Test the zwave_me config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.components.zwave_me.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import (
|
||||
RESULT_TYPE_ABORT,
|
||||
RESULT_TYPE_CREATE_ENTRY,
|
||||
RESULT_TYPE_FORM,
|
||||
FlowResult,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
MOCK_ZEROCONF_DATA = zeroconf.ZeroconfServiceInfo(
|
||||
host="ws://192.168.1.14",
|
||||
hostname="mock_hostname",
|
||||
name="mock_name",
|
||||
port=1234,
|
||||
properties={
|
||||
"deviceid": "aa:bb:cc:dd:ee:ff",
|
||||
"manufacturer": "fake_manufacturer",
|
||||
"model": "fake_model",
|
||||
"serialNumber": "fake_serial",
|
||||
},
|
||||
type="mock_type",
|
||||
)
|
||||
|
||||
|
||||
async def test_form(hass: HomeAssistant) -> None:
|
||||
"""Test we get the form."""
|
||||
with patch(
|
||||
"homeassistant.components.zwave_me.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry, patch(
|
||||
"homeassistant.components.zwave_me.helpers.get_uuid",
|
||||
return_value="test_uuid",
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"url": "192.168.1.14",
|
||||
"token": "test-token",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["title"] == "ws://192.168.1.14"
|
||||
assert result2["data"] == {
|
||||
"url": "ws://192.168.1.14",
|
||||
"token": "test-token",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_zeroconf(hass: HomeAssistant):
|
||||
"""Test starting a flow from zeroconf."""
|
||||
with patch(
|
||||
"homeassistant.components.zwave_me.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry, patch(
|
||||
"homeassistant.components.zwave_me.helpers.get_uuid",
|
||||
return_value="test_uuid",
|
||||
):
|
||||
result: FlowResult = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=MOCK_ZEROCONF_DATA,
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"token": "test-token",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["title"] == "ws://192.168.1.14"
|
||||
assert result2["data"] == {
|
||||
"url": "ws://192.168.1.14",
|
||||
"token": "test-token",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_error_handling_zeroconf(hass: HomeAssistant):
|
||||
"""Test getting proper errors from no uuid."""
|
||||
with patch("homeassistant.components.zwave_me.helpers.get_uuid", return_value=None):
|
||||
result: FlowResult = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=MOCK_ZEROCONF_DATA,
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "no_valid_uuid_set"
|
||||
|
||||
|
||||
async def test_handle_error_user(hass: HomeAssistant):
|
||||
"""Test getting proper errors from no uuid."""
|
||||
with patch("homeassistant.components.zwave_me.helpers.get_uuid", return_value=None):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"url": "192.168.1.15",
|
||||
"token": "test-token",
|
||||
},
|
||||
)
|
||||
assert result2["errors"] == {"base": "no_valid_uuid_set"}
|
||||
|
||||
|
||||
async def test_duplicate_user(hass: HomeAssistant):
|
||||
"""Test getting proper errors from duplicate uuid."""
|
||||
entry: MockConfigEntry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="ZWave_me",
|
||||
data={
|
||||
"url": "ws://192.168.1.15",
|
||||
"token": "test-token",
|
||||
},
|
||||
unique_id="test_uuid",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.zwave_me.helpers.get_uuid",
|
||||
return_value="test_uuid",
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"url": "192.168.1.15",
|
||||
"token": "test-token",
|
||||
},
|
||||
)
|
||||
assert result2["type"] == RESULT_TYPE_ABORT
|
||||
assert result2["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_duplicate_zeroconf(hass: HomeAssistant):
|
||||
"""Test getting proper errors from duplicate uuid."""
|
||||
entry: MockConfigEntry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="ZWave_me",
|
||||
data={
|
||||
"url": "ws://192.168.1.14",
|
||||
"token": "test-token",
|
||||
},
|
||||
unique_id="test_uuid",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zwave_me.helpers.get_uuid",
|
||||
return_value="test_uuid",
|
||||
):
|
||||
|
||||
result: FlowResult = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=MOCK_ZEROCONF_DATA,
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured"
|
Loading…
Reference in New Issue