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

Introduce wiz_light integration for WiZ bulbs #44522

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,9 @@ omit =
homeassistant/components/wiffi/*
homeassistant/components/wink/*
homeassistant/components/wirelesstag/*
homeassistant/components/wiz_light/__init__.py
homeassistant/components/wiz_light/const.py
homeassistant/components/wiz_light/light.py
homeassistant/components/wolflink/__init__.py
homeassistant/components/wolflink/sensor.py
homeassistant/components/wolflink/const.py
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ homeassistant/components/websocket_api/* @home-assistant/core
homeassistant/components/wiffi/* @mampfes
homeassistant/components/wilight/* @leofig-rj
homeassistant/components/withings/* @vangorra
homeassistant/components/wiz_light/* @sbidy
homeassistant/components/wled/* @frenck
homeassistant/components/wolflink/* @adamkrol93
homeassistant/components/workday/* @fabaff
Expand Down
45 changes: 45 additions & 0 deletions homeassistant/components/wiz_light/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""WiZ Light integration."""
import logging

from pywizlight import wizlight

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS
from homeassistant.core import HomeAssistant

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

PLATFORMS = ["light"]


async def async_setup(hass):
"""Old way of setting up the wiz_light component."""
hass.data[DOMAIN] = {}

return True


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up the wiz_light integration from a config entry."""
ip_address = entry.data.get(CONF_IP_ADDRESS)
_LOGGER.debug("Get bulb with IP: %s", ip_address)
bulb = wizlight(ip_address)
hass.data[DOMAIN][entry.unique_id] = bulb

hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "light")
)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
# unload srp client
hass.data[DOMAIN][entry.unique_id] = None
# Remove config entry
await hass.config_entries.async_forward_entry_unload(entry, "light")

return True
197 changes: 197 additions & 0 deletions homeassistant/components/wiz_light/bulblibrary.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Bulb Library for importing possible bulb types
MartinHjelmare marked this conversation as resolved.
Show resolved Hide resolved
# TEMPLATE:
# vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
# <Name>:
# name: <Name>
# features:
# brightness: true / false
# color: true / false
# effect: true / false
# color_tmp: true / false
# kelvin_range:
# min: 2200
# max: 6500
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Version: "0.1"
# Only Brightness
ESP01_SHDW_01:
name: ESP01_SHDW_01
features:
brightness: true
color: false
effect: false
color_tmp: false
kelvin_range:
min: 2200
max: 6500

ESP01_SHDW1_31:
name: ESP01_SHDW1_31
features:
brightness: true
color: false
effect: false
color_tmp: false
kelvin_range:
min: 2200
max: 6500
# ----------
# Brightness and Effects
ESP06_SHDW9_01:
name: ESP06_SHDW9_01
features:
brightness: true
color: false
effect: true
color_tmp: false
kelvin_range:
min: 2200
max: 6500

ESP06_SHDW1_01:
name: ESP06_SHDW1_01
features:
brightness: true
color: false
effect: true
color_tmp: false
kelvin_range:
min: 2200
max: 6500
# ---------
# Brightness and Color Temp
ESP01_SHTW1C_31:
name: ESP01_SHTW1C_31
features:
brightness: true
color: false
effect: false
color_tmp: true
kelvin_range:
min: 2200
max: 6500

ESP17_SHTW9_01:
name: ESP17_SHTW9_01
features:
brightness: true
color: false
effect: false
color_tmp: true
kelvin_range:
min: 2000
max: 5000
# ----------------
# Brightness, Color Temp and Effect
ESP56_SHTW3_01:
name: ESP56_SHTW3_01
features:
brightness: true
color: false
effect: true
color_tmp: true
kelvin_range:
min: 2200
max: 6500

ESP15_SHTW1_01I:
name: ESP15_SHTW1_01I
features:
brightness: true
color: false
effect: true
color_tmp: true
kelvin_range:
min: 2200
max: 6500

ESP03_SHTW1C_01:
name: ESP03_SHTW1C_01
features:
brightness: true
color: false
effect: true
color_tmp: true
kelvin_range:
min: 2700
max: 6500

ESP03_SHTW1W_01:
name: ESP03_SHTW1W_01
features:
brightness: true
color: false
effect: true
color_tmp: true
kelvin_range:
min: 2700
max: 6500
# -----------------
# Brightness, Color Temp, Color and Effect / all features
# Added #50
ESP01_SHRGB1C_31:
name: ESP01_SHRGB1C_31
features:
brightness: true
color: true
effect: true
color_tmp: true
kelvin_range:
min: 2200
max: 6500
# Test device
ESP01_SHRGB_03:
name: ESP01_SHRGB_03
features:
brightness: true
color: true
effect: true
color_tmp: true
kelvin_range:
min: 2700
max: 6500

ESP03_SHRGBP_31:
name: ESP03_SHRGBP_31
features:
brightness: true
color: true
effect: true
color_tmp: true
kelvin_range:
min: 2000
max: 6500
# Added #54
ESP03_SHRGB1C_01:
name: ESP03_SHRGB1C_01
features:
brightness: true
color: true
effect: true
color_tmp: true
kelvin_range:
min: 2200
max: 6500
# Added #55
ESP03_SHRGB1W_01:
name: ESP03_SHRGB1W_01
features:
brightness: true
color: true
effect: true
color_tmp: true
kelvin_range:
min: 2200
max: 6500
# Added #59
ESP03_SHRGB3_01ABI:
name: ESP03_SHRGB3_01ABI
features:
brightness: true
color: true
effect: true
color_tmp: true
kelvin_range:
min: 2500
max: 6500
# -----------------
62 changes: 62 additions & 0 deletions homeassistant/components/wiz_light/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Config flow for wiz_light."""
import logging
import re

from pywizlight import wizlight
from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_IP_ADDRESS, CONF_NAME
from homeassistant.data_entry_flow import AbortFlow

from .const import DEFAULT_NAME, DOMAIN # pylint: disable=unused-import
Copy link
Contributor

Choose a reason for hiding this comment

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

Both of these imports are used. Why does pylint need to be disabled?

Copy link
Contributor Author

@sbidy sbidy Dec 30, 2020

Choose a reason for hiding this comment

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

I don't know why this pups up in the test stack. In my devcontainer pylint doesn't shows me an error.
Seems to be a false positive.


_LOGGER = logging.getLogger(__name__)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for WiZ Light."""

VERSION = 1
config = {
vol.Required(CONF_IP_ADDRESS): str,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
}

async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
errors = {}

if user_input is not None:
try:
if self.is_valid_ip(user_input[CONF_IP_ADDRESS]):
bulb = wizlight(user_input[CONF_IP_ADDRESS])
mac = await bulb.getMac()
await self.async_set_unique_id(mac)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=user_input[CONF_NAME], data=user_input
)
return self.async_abort(reason="no_IP")
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be more typical for a config flow to return the async_show_form with errors[CONF_IP_ADDRESS] = "no_IP" to allow the user to correct the problem.

except WizLightTimeOutError:
return self.async_abort(reason="bulb_time_out")
except ConnectionRefusedError:
return self.async_abort(reason="can_not_connect")
except WizLightConnectionError:
return self.async_abort(reason="no_wiz_light")
except AbortFlow:
return self.async_abort(reason="single_instance_allowed")
return self.async_show_form(
step_id="user", data_schema=vol.Schema(self.config), errors=errors
)

async def async_step_import(self, import_config):
"""Import from config."""
return await self.async_step_user(user_input=import_config)

@staticmethod
def is_valid_ip(ip_address) -> bool:
"""Check the IP address."""
ipv = re.match(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$", ip_address)
return bool(ipv) and all(map(lambda n: 0 <= int(n) <= 255, ipv.groups()))
3 changes: 3 additions & 0 deletions homeassistant/components/wiz_light/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Constants for the SRP Energy integration."""
DOMAIN = "wiz_light"
DEFAULT_NAME = "WiZ Bulb"
Loading