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

Add support of Z-Wave.Me Z-Way and RaZberry server #61182

Merged
merged 77 commits into from Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
85204ea
Z.Wave-Me integration with number platform
lawfulchaos Dec 7, 2021
e5161d0
Merge branch 'home-assistant:dev' into dev
PoltoS Dec 7, 2021
f338a84
zwave_me CODEOWNER updated
PoltoS Dec 7, 2021
281c811
Added Z-Wave-Me as codeowner
lawfulchaos Dec 7, 2021
d314034
Fix codestyle
lawfulchaos Dec 7, 2021
314dabc
Update homeassistant/components/zwave_me/__init__.py
PoltoS Dec 7, 2021
c43da4b
Update homeassistant/components/zwave_me/__init__.py
PoltoS Dec 7, 2021
c3b5d77
Update homeassistant/components/zwave_me/__init__.py
lawfulchaos Dec 7, 2021
f5be4aa
Description of IP and Token fields
PoltoS Dec 7, 2021
442272d
Apply suggestions from code review
PoltoS Dec 7, 2021
9956137
Apply suggestions from code review
PoltoS Dec 7, 2021
ad89049
Applied revision suggestions
PoltoS Dec 7, 2021
3b25ea0
Merge branch 'home-assistant:dev' into dev
PoltoS Dec 7, 2021
202c72f
Apply suggestions from code review
PoltoS Dec 8, 2021
4cce889
Accepted revision suggestions
PoltoS Dec 8, 2021
4c789d7
Merge branch 'dev' of github.com:Z-Wave-Me/ha-core into dev
PoltoS Dec 8, 2021
3e0b7fc
unused command removed
PoltoS Dec 8, 2021
0e3f004
Fixes
lawfulchaos Dec 9, 2021
bed8da4
Test python 3.8 fix
lawfulchaos Dec 12, 2021
dfaee4c
Style fixes
lawfulchaos Dec 12, 2021
92a8fb6
Reflect zwave_me_update
lawfulchaos Dec 12, 2021
8500a2e
Merge branch 'home-assistant:dev' into dev
PoltoS Dec 12, 2021
e53b0c6
Merge branch 'home-assistant:dev' into dev
PoltoS Dec 13, 2021
d48b2d8
Reflect zwave_me_update
lawfulchaos Dec 14, 2021
79ccb24
Merge remote-tracking branch 'origin/dev' into dev
lawfulchaos Dec 14, 2021
3f41cdf
Unique ID
lawfulchaos Dec 14, 2021
cd28b70
Removed blank line
PoltoS Dec 14, 2021
735b2d1
Test amend - new url formatting
lawfulchaos Dec 14, 2021
7ce294a
Merge remote-tracking branch 'origin/dev' into dev
lawfulchaos Dec 14, 2021
ed793aa
New fixes
lawfulchaos Dec 16, 2021
aa98d0d
Removed blank line
PoltoS Dec 14, 2021
601e62e
New fixes
lawfulchaos Dec 16, 2021
b83d8de
Library update
lawfulchaos Dec 16, 2021
ed212fd
Setup platform changes
lawfulchaos Dec 17, 2021
cade21d
Merge branch 'dev' of https://github.com/Z-Wave-Me/ha-core into dev
lawfulchaos Dec 17, 2021
b620751
Proper mock in test
lawfulchaos Dec 17, 2021
0bac192
Linters fix
lawfulchaos Dec 17, 2021
dad796c
Executability fix
lawfulchaos Dec 17, 2021
170c326
Minor fixes
lawfulchaos Dec 17, 2021
51ea78f
Merge remote-tracking branch 'origin/dev' into dev
lawfulchaos Dec 17, 2021
7640dd8
Merge branch 'dev' into dev
lawfulchaos Dec 17, 2021
a575d8d
UUID implementation
lawfulchaos Dec 30, 2021
52b93b6
Minor fixes
lawfulchaos Dec 30, 2021
55facad
Reflect library change
lawfulchaos Dec 30, 2021
fe3822c
Minor fixes
lawfulchaos Dec 30, 2021
e1efe7d
Update homeassistant/components/zwave_me/__init__.py
lawfulchaos Jan 11, 2022
ca8c97c
Minor fixes
lawfulchaos Jan 11, 2022
34da6cf
test getting errors
lawfulchaos Jan 11, 2022
84176cf
workaround patch
lawfulchaos Jan 11, 2022
a820d72
Update homeassistant/components/zwave_me/config_flow.py
lawfulchaos Jan 11, 2022
c41ef1e
Update tests/components/zwave_me/test_config_flow.py
lawfulchaos Jan 11, 2022
2c8301f
Update tests/components/zwave_me/test_config_flow.py
lawfulchaos Jan 11, 2022
5619b34
Update tests/components/zwave_me/test_config_flow.py
lawfulchaos Jan 12, 2022
4e41e86
Update tests/components/zwave_me/test_config_flow.py
lawfulchaos Jan 12, 2022
a1a564b
Update homeassistant/components/zwave_me/config_flow.py
lawfulchaos Jan 12, 2022
9547020
Update tests/components/zwave_me/test_config_flow.py
lawfulchaos Jan 12, 2022
3006c9e
Translation
lawfulchaos Jan 12, 2022
62d8f89
Minor translation fixes
lawfulchaos Jan 13, 2022
d4c0779
Test checking errors
lawfulchaos Jan 14, 2022
18327d2
Test formatting
lawfulchaos Jan 15, 2022
c47bb5d
Test formatting
lawfulchaos Jan 15, 2022
1531c56
Up requirements
lawfulchaos Jan 17, 2022
f56b4d3
Test checking errors
lawfulchaos Jan 17, 2022
9988644
New link to the documentation page
PoltoS Jan 17, 2022
d0d8e70
Merge branch 'dev' into dev
lawfulchaos Jan 19, 2022
1db46a3
Test fixes
lawfulchaos Jan 19, 2022
cb78281
uuid - no ip
lawfulchaos Jan 20, 2022
a14918d
Fix formatting
MartinHjelmare Jan 20, 2022
da18e08
Fix patches
MartinHjelmare Jan 20, 2022
473f6ba
Ignore helpers
MartinHjelmare Jan 20, 2022
2ce7e03
Run script gen requirements all
MartinHjelmare Jan 20, 2022
803952a
Add missing abort reason
MartinHjelmare Jan 20, 2022
e3f1a15
Clean up constants
MartinHjelmare Jan 20, 2022
4baad5e
Clarify get_uuid parameters
MartinHjelmare Jan 20, 2022
7664cad
Handle discovery in progress on user flow
MartinHjelmare Jan 21, 2022
7de3373
Add description back
MartinHjelmare Jan 21, 2022
1846301
Merge branch 'home-assistant:dev' into dev
lawfulchaos Jan 23, 2022
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
3 changes: 3 additions & 0 deletions .coveragerc
Expand Up @@ -1390,6 +1390,9 @@ 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/helpers.py
homeassistant/components/zwave_me/number.py

[report]
# Regexes for lines to exclude from consideration
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -1106,6 +1106,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
122 changes: 122 additions & 0 deletions homeassistant/components/zwave_me/__init__.py
@@ -0,0 +1,122 @@
"""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, ZWAVE_PLATFORMS

_LOGGER = logging.getLogger(__name__)


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():
PoltoS marked this conversation as resolved.
Show resolved Hide resolved
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_PLATFORMS,
)
self.platforms_inited = False

async def async_establish_connection(self):
"""Get connection status."""
is_connected = await self.zwave_api.get_connection()
PoltoS marked this conversation as resolved.
Show resolved Hide resolved
return is_connected

def add_device(self, device: ZWaveMeData) -> None:
"""Send signal to create device."""
if device.deviceType in ZWAVE_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
PoltoS marked this conversation as resolved.
Show resolved Hide resolved
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()
84 changes: 84 additions & 0 deletions homeassistant/components/zwave_me/config_flow.py
@@ -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")

PoltoS marked this conversation as resolved.
Show resolved Hide resolved
await self.async_set_unique_id(self.uuid)
self._abort_if_unique_id_configured()
return await self.async_step_user()
PoltoS marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 14 additions & 0 deletions homeassistant/components/zwave_me/const.py
@@ -0,0 +1,14 @@
"""Constants for ZWaveMe."""

from homeassistant.const import Platform

# Base component constants
DOMAIN = "zwave_me"

ZWAVE_PLATFORMS = [
"switchMultilevel",
]

PLATFORMS = [
Platform.NUMBER,
]
14 changes: 14 additions & 0 deletions homeassistant/components/zwave_me/helpers.py
@@ -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
17 changes: 17 additions & 0 deletions homeassistant/components/zwave_me/manifest.json
@@ -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.22",
"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"
]
}
45 changes: 45 additions & 0 deletions homeassistant/components/zwave_me/number.py
@@ -0,0 +1,45 @@
"""Representation of a switchMultilevel."""
PoltoS marked this conversation as resolved.
Show resolved Hide resolved
from homeassistant.components.number import NumberEntity
PoltoS marked this conversation as resolved.
Show resolved Hide resolved
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect

from . import ZWaveMeEntity
from .const import DOMAIN

DEVICE_NAME = "switchMultilevel"


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the number platform."""

@callback
def add_new_device(new_device):
PoltoS marked this conversation as resolved.
Show resolved Hide resolved
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))}"
)
20 changes: 20 additions & 0 deletions homeassistant/components/zwave_me/strings.json
@@ -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"
}
}
}
20 changes: 20 additions & 0 deletions homeassistant/components/zwave_me/translations/en.json
@@ -0,0 +1,20 @@
{
MartinHjelmare marked this conversation as resolved.
Show resolved Hide resolved
"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)."
}
}
}
}
3 changes: 2 additions & 1 deletion homeassistant/generated/config_flows.py
Expand Up @@ -375,5 +375,6 @@
"zerproc",
"zha",
"zwave",
"zwave_js"
"zwave_js",
"zwave_me"
]
4 changes: 4 additions & 0 deletions homeassistant/generated/zeroconf.py
Expand Up @@ -134,6 +134,10 @@
"_hap._tcp.local.": [
{
"domain": "homekit_controller"
},
{
"domain": "zwave_me",
"name": "*z.wave-me*"
}
],
"_homekit._tcp.local.": [
Expand Down
4 changes: 4 additions & 0 deletions requirements_all.txt
Expand Up @@ -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
Expand Down Expand Up @@ -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.22