Add Evil Genius Labs integration (#58720)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Paulus Schoutsen
2021-11-08 08:56:27 -08:00
committed by GitHub
co-authored by Martin Hjelmare
parent 089353e949
commit 296f678d52
21 changed files with 971 additions and 0 deletions
@@ -0,0 +1 @@
"""Tests for the Evil Genius Labs integration."""
@@ -0,0 +1,49 @@
"""Test helpers for Evil Genius Labs."""
import json
from unittest.mock import patch
import pytest
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry, load_fixture
@pytest.fixture(scope="session")
def data_fixture():
"""Fixture data."""
data = json.loads(load_fixture("data.json", "evil_genius_labs"))
return {item["name"]: item for item in data}
@pytest.fixture(scope="session")
def info_fixture():
"""Fixture info."""
return json.loads(load_fixture("info.json", "evil_genius_labs"))
@pytest.fixture
def config_entry(hass):
"""Evil genius labs config entry."""
entry = MockConfigEntry(domain="evil_genius_labs", data={"host": "192.168.1.113"})
entry.add_to_hass(hass)
return entry
@pytest.fixture
async def setup_evil_genius_labs(
hass, config_entry, data_fixture, info_fixture, platforms
):
"""Test up Evil Genius Labs instance."""
with patch(
"pyevilgenius.EvilGeniusDevice.get_data",
return_value=data_fixture,
), patch(
"pyevilgenius.EvilGeniusDevice.get_info",
return_value=info_fixture,
), patch(
"homeassistant.components.evil_genius_labs.PLATFORMS", platforms
):
assert await async_setup_component(hass, "evil_genius_labs", {})
await hass.async_block_till_done()
yield
@@ -0,0 +1,331 @@
[
{
"name": "name",
"label": "Name",
"type": "Label",
"value": "Fibonacci256-23D4"
},
{ "name": "power", "label": "Power", "type": "Boolean", "value": 1 },
{
"name": "brightness",
"label": "Brightness",
"type": "Number",
"value": 128,
"min": 1,
"max": 255
},
{
"name": "pattern",
"label": "Pattern",
"type": "Select",
"value": 70,
"options": [
"Pride",
"Pride Fibonacci",
"Color Waves",
"Color Waves Fibonacci",
"Pride Playground",
"Pride Playground Fibonacci",
"Color Waves Playground",
"Color Waves Playground Fibonacci",
"Wheel",
"Swirl Fibonacci",
"Fire Fibonacci",
"Water Fibonacci",
"Emitter Fibonacci",
"Pacifica",
"Pacifica Fibonacci",
"Angle Palette",
"Radius Palette",
"X Axis Palette",
"Y Axis Palette",
"XY Axis Palette",
"Angle Gradient Palette",
"Radius Gradient Palette",
"X Axis Gradient Palette",
"Y Axis Gradient Palette",
"XY Axis Gradient Palette",
"Fire Noise",
"Fire Noise 2",
"Lava Noise",
"Rainbow Noise",
"Rainbow Stripe Noise",
"Party Noise",
"Forest Noise",
"Cloud Noise",
"Ocean Noise",
"Black & White Noise",
"Black & Blue Noise",
"Analog Clock",
"Spiral Analog Clock 13",
"Spiral Analog Clock 21",
"Spiral Analog Clock 34",
"Spiral Analog Clock 55",
"Spiral Analog Clock 89",
"Spiral Analog Clock 21 & 34",
"Spiral Analog Clock 13, 21 & 34",
"Spiral Analog Clock 34, 21 & 13",
"Pride Playground",
"Color Waves Playground",
"Rainbow Twinkles",
"Snow Twinkles",
"Cloud Twinkles",
"Incandescent Twinkles",
"Retro C9 Twinkles",
"Red & White Twinkles",
"Blue & White Twinkles",
"Red, Green & White Twinkles",
"Fairy Light Twinkles",
"Snow 2 Twinkles",
"Holly Twinkles",
"Ice Twinkles",
"Party Twinkles",
"Forest Twinkles",
"Lava Twinkles",
"Fire Twinkles",
"Cloud 2 Twinkles",
"Ocean Twinkles",
"Rainbow",
"Rainbow With Glitter",
"Solid Rainbow",
"Confetti",
"Sinelon",
"Beat",
"Juggle",
"Fire",
"Water",
"Strand Test",
"Solid Color"
]
},
{
"name": "palette",
"label": "Palette",
"type": "Select",
"value": 0,
"options": [
"Rainbow",
"Rainbow Stripe",
"Cloud",
"Lava",
"Ocean",
"Forest",
"Party",
"Heat"
]
},
{
"name": "speed",
"label": "Speed",
"type": "Number",
"value": 30,
"min": 1,
"max": 255
},
{ "name": "autoplaySection", "label": "Autoplay", "type": "Section" },
{ "name": "autoplay", "label": "Autoplay", "type": "Boolean", "value": 0 },
{
"name": "autoplayDuration",
"label": "Autoplay Duration",
"type": "Number",
"value": 10,
"min": 0,
"max": 255
},
{ "name": "clock", "label": "Clock", "type": "Section" },
{ "name": "showClock", "label": "Show Clock", "type": "Boolean", "value": 0 },
{
"name": "clockBackgroundFade",
"label": "Background Fade",
"type": "Number",
"value": 240,
"min": 0,
"max": 255
},
{ "name": "solidColorSection", "label": "Solid Color", "type": "Section" },
{
"name": "solidColor",
"label": "Color",
"type": "Color",
"value": "0,0,255"
},
{ "name": "prideSection", "label": "Pride & ColorWaves", "type": "Section" },
{
"name": "saturationBpm",
"label": "Saturation BPM",
"type": "Number",
"value": 87,
"min": 0,
"max": 255
},
{
"name": "saturationMin",
"label": "Saturation Min",
"type": "Number",
"value": 220,
"min": 0,
"max": 255
},
{
"name": "saturationMax",
"label": "Saturation Max",
"type": "Number",
"value": 250,
"min": 0,
"max": 255
},
{
"name": "brightDepthBpm",
"label": "Brightness Depth BPM",
"type": "Number",
"value": 1,
"min": 0,
"max": 255
},
{
"name": "brightDepthMin",
"label": "Brightness Depth Min",
"type": "Number",
"value": 96,
"min": 0,
"max": 255
},
{
"name": "brightDepthMax",
"label": "Brightness Depth Max",
"type": "Number",
"value": 224,
"min": 0,
"max": 255
},
{
"name": "brightThetaIncBpm",
"label": "Bright Theta Inc BPM",
"type": "Number",
"value": 203,
"min": 0,
"max": 255
},
{
"name": "brightThetaIncMin",
"label": "Bright Theta Inc Min",
"type": "Number",
"value": 25,
"min": 0,
"max": 255
},
{
"name": "brightThetaIncMax",
"label": "Bright Theta Inc Max",
"type": "Number",
"value": 40,
"min": 0,
"max": 255
},
{
"name": "msMultiplierBpm",
"label": "Time Multiplier BPM",
"type": "Number",
"value": 147,
"min": 0,
"max": 255
},
{
"name": "msMultiplierMin",
"label": "Time Multiplier Min",
"type": "Number",
"value": 23,
"min": 0,
"max": 255
},
{
"name": "msMultiplierMax",
"label": "Time Multiplier Max",
"type": "Number",
"value": 60,
"min": 0,
"max": 255
},
{
"name": "hueIncBpm",
"label": "Hue Inc BPM",
"type": "Number",
"value": 113,
"min": 0,
"max": 255
},
{
"name": "hueIncMin",
"label": "Hue Inc Min",
"type": "Number",
"value": 1,
"min": 0,
"max": 255
},
{
"name": "hueIncMax",
"label": "Hue Inc Max",
"type": "Number",
"value": 12,
"min": 0,
"max": 255
},
{
"name": "sHueBpm",
"label": "S Hue BPM",
"type": "Number",
"value": 2,
"min": 0,
"max": 255
},
{
"name": "sHueMin",
"label": "S Hue Min",
"type": "Number",
"value": 5,
"min": 0,
"max": 255
},
{
"name": "sHueMax",
"label": "S Hue Max",
"type": "Number",
"value": 9,
"min": 0,
"max": 255
},
{ "name": "fireSection", "label": "Fire & Water", "type": "Section" },
{
"name": "cooling",
"label": "Cooling",
"type": "Number",
"value": 49,
"min": 0,
"max": 255
},
{
"name": "sparking",
"label": "Sparking",
"type": "Number",
"value": 60,
"min": 0,
"max": 255
},
{ "name": "twinklesSection", "label": "Twinkles", "type": "Section" },
{
"name": "twinkleSpeed",
"label": "Twinkle Speed",
"type": "Number",
"value": 4,
"min": 0,
"max": 8
},
{
"name": "twinkleDensity",
"label": "Twinkle Density",
"type": "Number",
"value": 5,
"min": 0,
"max": 8
}
]
@@ -0,0 +1,30 @@
{
"millis": 62099724,
"vcc": 3005,
"wiFiChipId": "1923d4",
"flashChipId": "1640d8",
"flashChipSize": 4194304,
"flashChipRealSize": 4194304,
"sdkVersion": "2.2.2-dev(38a443e)",
"coreVersion": "2_7_4",
"bootVersion": 6,
"cpuFreqMHz": 160,
"freeHeap": 21936,
"sketchSize": 476352,
"freeSketchSpace": 1617920,
"resetReason": "External System",
"isConnected": true,
"wiFiSsidDefault": "My Wi-Fi",
"wiFiSSID": "My Wi-Fi",
"localIP": "192.168.1.113",
"gatewayIP": "192.168.1.1",
"subnetMask": "255.255.255.0",
"dnsIP": "192.168.1.1",
"hostname": "ESP-1923D4",
"macAddress": "BC:FF:4D:19:23:D4",
"autoConnect": true,
"softAPSSID": "FaryLink_1923D4",
"softAPIP": "(IP unset)",
"BSSID": "FC:EC:DA:77:1A:CE",
"softAPmacAddress": "BE:FF:4D:19:23:D4"
}
@@ -0,0 +1,85 @@
"""Test the Evil Genius Labs config flow."""
from unittest.mock import patch
import aiohttp
from homeassistant import config_entries
from homeassistant.components.evil_genius_labs.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
async def test_form(hass: HomeAssistant, data_fixture, info_fixture) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] is None
with patch(
"pyevilgenius.EvilGeniusDevice.get_data",
return_value=data_fixture,
), patch(
"pyevilgenius.EvilGeniusDevice.get_info",
return_value=info_fixture,
), patch(
"homeassistant.components.evil_genius_labs.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
},
)
await hass.async_block_till_done()
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
assert result2["title"] == "Fibonacci256-23D4"
assert result2["data"] == {
"host": "1.1.1.1",
}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
"""Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"pyevilgenius.EvilGeniusDevice.get_data",
side_effect=aiohttp.ClientError,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
},
)
assert result2["type"] == RESULT_TYPE_FORM
assert result2["errors"] == {"base": "cannot_connect"}
async def test_form_unknown(hass: HomeAssistant) -> None:
"""Test we handle unknown error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"pyevilgenius.EvilGeniusDevice.get_data",
side_effect=ValueError("BOOM"),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
},
)
assert result2["type"] == RESULT_TYPE_FORM
assert result2["errors"] == {"base": "unknown"}
@@ -0,0 +1,13 @@
"""Test evil genius labs init."""
import pytest
from homeassistant import config_entries
from homeassistant.components.evil_genius_labs import PLATFORMS
@pytest.mark.parametrize("platforms", [PLATFORMS])
async def test_setup_unload_entry(hass, setup_evil_genius_labs, config_entry):
"""Test setting up and unloading a config entry."""
assert len(hass.states.async_entity_ids()) == 1
assert await hass.config_entries.async_unload(config_entry.entry_id)
assert config_entry.state == config_entries.ConfigEntryState.NOT_LOADED
@@ -0,0 +1,76 @@
"""Test Evil Genius Labs light."""
from unittest.mock import patch
import pytest
@pytest.mark.parametrize("platforms", [("light",)])
async def test_works(hass, setup_evil_genius_labs):
"""Test it works."""
state = hass.states.get("light.fibonacci256_23d4")
assert state is not None
assert state.state == "on"
assert state.attributes["brightness"] == 128
@pytest.mark.parametrize("platforms", [("light",)])
async def test_turn_on_color(hass, setup_evil_genius_labs):
"""Test turning on with a color."""
with patch(
"pyevilgenius.EvilGeniusDevice.set_path_value"
) as mock_set_path_value, patch(
"pyevilgenius.EvilGeniusDevice.set_rgb_color"
) as mock_set_rgb_color:
await hass.services.async_call(
"light",
"turn_on",
{
"entity_id": "light.fibonacci256_23d4",
"brightness": 100,
"rgb_color": (10, 20, 30),
},
blocking=True,
)
assert len(mock_set_path_value.mock_calls) == 2
mock_set_path_value.mock_calls[0][1] == ("brightness", 100)
mock_set_path_value.mock_calls[1][1] == ("power", 1)
assert len(mock_set_rgb_color.mock_calls) == 1
mock_set_rgb_color.mock_calls[0][1] == (10, 20, 30)
@pytest.mark.parametrize("platforms", [("light",)])
async def test_turn_on_effect(hass, setup_evil_genius_labs):
"""Test turning on with an effect."""
with patch("pyevilgenius.EvilGeniusDevice.set_path_value") as mock_set_path_value:
await hass.services.async_call(
"light",
"turn_on",
{
"entity_id": "light.fibonacci256_23d4",
"effect": "Pride Playground",
},
blocking=True,
)
assert len(mock_set_path_value.mock_calls) == 2
mock_set_path_value.mock_calls[0][1] == ("pattern", 4)
mock_set_path_value.mock_calls[1][1] == ("power", 1)
@pytest.mark.parametrize("platforms", [("light",)])
async def test_turn_off(hass, setup_evil_genius_labs):
"""Test turning off."""
with patch("pyevilgenius.EvilGeniusDevice.set_path_value") as mock_set_path_value:
await hass.services.async_call(
"light",
"turn_off",
{
"entity_id": "light.fibonacci256_23d4",
},
blocking=True,
)
assert len(mock_set_path_value.mock_calls) == 1
mock_set_path_value.mock_calls[0][1] == ("power", 0)