Skip to content

Commit 15200f4

Browse files
committed
Legrand Whole Home Lighting Integration - Initial Commit
1 parent a09213b commit 15200f4

27 files changed

+1230
-0
lines changed

.strict-typing

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ homeassistant.components.lamarzocco.*
297297
homeassistant.components.lametric.*
298298
homeassistant.components.laundrify.*
299299
homeassistant.components.lawn_mower.*
300+
homeassistant.components.lc7001.*
300301
homeassistant.components.lcn.*
301302
homeassistant.components.ld2410_ble.*
302303
homeassistant.components.led_ble.*

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""The Legrand Whole Home Lighting integration."""
2+
3+
import logging
4+
5+
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, Platform
6+
from homeassistant.core import HomeAssistant
7+
8+
from .common import LcConfigEntry
9+
from .engine.engine import ConnectionState, Engine
10+
11+
_PLATFORMS: list[Platform] = [Platform.LIGHT]
12+
13+
_LOGGER = logging.getLogger(__name__)
14+
15+
16+
async def async_setup_entry(hass: HomeAssistant, entry: LcConfigEntry) -> bool:
17+
"""Set up Legrand Whole Home Lighting from a config entry."""
18+
19+
engine = Engine(
20+
host=entry.data[CONF_HOST],
21+
port=entry.data[CONF_PORT],
22+
password=entry.data[CONF_PASSWORD],
23+
)
24+
25+
try:
26+
engine.connect()
27+
engine.start()
28+
await engine.waitForState(ConnectionState.Ready)
29+
except TimeoutError:
30+
return False
31+
32+
entry.runtime_data = engine
33+
await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS)
34+
35+
return True
36+
37+
38+
async def async_unload_entry(hass: HomeAssistant, entry: LcConfigEntry) -> bool:
39+
"""Unload a config entry."""
40+
41+
return await hass.config_entries.async_unload_platforms(entry, _PLATFORMS)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Common types and definitions."""
2+
3+
from homeassistant.config_entries import ConfigEntry
4+
5+
from .engine.engine import Engine
6+
7+
type LcConfigEntry = ConfigEntry[Engine]
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""Config flow for the Legrand Whole Home Lighting integration."""
2+
3+
import logging
4+
from typing import Any
5+
6+
import voluptuous as vol
7+
8+
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
9+
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
10+
from homeassistant.exceptions import HomeAssistantError, IntegrationError
11+
12+
from .const import DEFAULT_HOST, DEFAULT_PORT, DEVICE_HUB, DOMAIN
13+
from .engine.engine import ConnectionState, Engine
14+
15+
_LOGGER = logging.getLogger(__name__)
16+
17+
STEP_USER_DATA_SCHEMA = vol.Schema(
18+
{
19+
vol.Optional(CONF_HOST, default=DEFAULT_HOST): str,
20+
vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
21+
vol.Required(CONF_PASSWORD): str,
22+
}
23+
)
24+
25+
26+
class LcConfigFlow(ConfigFlow, domain=DOMAIN):
27+
"""Handle a config flow for Legrand Whole Home Lighting."""
28+
29+
VERSION = 1
30+
31+
def __init__(self) -> None:
32+
"""Initialize."""
33+
super().__init__()
34+
self.data: dict[str, Any] = {}
35+
36+
async def validate_input(self, data: dict[str, Any]) -> dict[str, Any]:
37+
"""Validate the user input allows us to connect."""
38+
39+
host = data[CONF_HOST]
40+
port = data[CONF_PORT]
41+
password = data[CONF_PASSWORD]
42+
43+
if host is None:
44+
raise ConfigEntryNotReady
45+
if port is None:
46+
raise ConfigEntryNotReady
47+
if password is None:
48+
raise ConfigEntryNotReady
49+
50+
try:
51+
engine = Engine(host=host, port=port, password=password)
52+
53+
engine.connect()
54+
engine.start()
55+
56+
await engine.waitForState(ConnectionState.Ready)
57+
mac = engine.systemInfo.MACAddress
58+
59+
except TimeoutError as error:
60+
raise CannotConnect from error
61+
62+
finally:
63+
engine.disconnect()
64+
65+
# Return info that you want to store in the config entry.
66+
return {"title": DEVICE_HUB, "mac": mac}
67+
68+
async def async_step_user(
69+
self, user_input: dict[str, Any] | None = None
70+
) -> ConfigFlowResult:
71+
"""Handle the initial step."""
72+
73+
errors: dict[str, str] = {}
74+
if user_input:
75+
try:
76+
info = await self.validate_input(data=user_input)
77+
78+
except CannotConnect:
79+
errors["base"] = "cannot_connect"
80+
81+
except InvalidAuth:
82+
errors["base"] = "invalid_auth"
83+
84+
except Exception:
85+
_LOGGER.exception("Unexpected exception")
86+
errors["base"] = "unknown"
87+
88+
else:
89+
await self.async_set_unique_id(info["mac"])
90+
self._abort_if_unique_id_configured()
91+
return self.async_create_entry(title=info["title"], data=user_input)
92+
93+
return self.async_show_form(
94+
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
95+
)
96+
97+
async def async_step_reconfigure(
98+
self, user_input: dict[str, Any]
99+
) -> ConfigFlowResult:
100+
"""Handle reconfiguration."""
101+
return await self.async_step_user()
102+
103+
104+
class CannotConnect(HomeAssistantError):
105+
"""Error to indicate we cannot connect."""
106+
107+
108+
class InvalidAuth(HomeAssistantError):
109+
"""Error to indicate there is invalid auth."""
110+
111+
112+
class ConfigEntryNotReady(IntegrationError):
113+
"""Error to indicate there is invalid config."""
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Constants for the Legrand Whole Home Lighting integration."""
2+
3+
DOMAIN = "lc7001"
4+
5+
DEVICE_HUB = "LC7001"
6+
7+
DEFAULT_HOST = "LCM1.local"
8+
DEFAULT_PORT = 2112
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Diagnostics support for Acaia."""
2+
3+
from __future__ import annotations
4+
5+
import logging
6+
from typing import Any
7+
8+
from homeassistant.components.diagnostics import async_redact_data
9+
from homeassistant.core import HomeAssistant
10+
11+
from .common import LcConfigEntry
12+
13+
_LOGGER = logging.getLogger(__name__)
14+
15+
16+
async def async_get_config_entry_diagnostics(
17+
hass: HomeAssistant,
18+
entry: LcConfigEntry,
19+
) -> dict[str, Any]:
20+
"""Return diagnostics for a config entry."""
21+
engine = entry.runtime_data
22+
23+
# collect all data sources
24+
diagnostics: dict[str, Any] = {
25+
"config_entry": async_redact_data(entry, {}),
26+
"model": engine.systemInfo.Model,
27+
"device_state": engine.state.name,
28+
"mac": engine.systemInfo.MACAddress,
29+
}
30+
31+
_LOGGER.debug("Diagnostics: %s", diagnostics)
32+
33+
return diagnostics
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""The Engine for the LC7001 hub."""
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""A BroadcastMemory packet."""
2+
3+
from typing import Any
4+
5+
from .packet import Packet
6+
7+
8+
class BroadcastMemory(Packet):
9+
"""A BroadcastMemory packet."""
10+
11+
_service_name = "BroadcastMemory"
12+
13+
def __init__(self, **kwargs: Any) -> None:
14+
"""Initialize a BroadcastMemory packet."""
15+
super().__init__(**kwargs)
16+
17+
self.FreeMemory = kwargs.get("FreeMemory")
18+
self.FreeMemLowWater = kwargs.get("FreeMemLowWater")
19+
self.Malloc_Count = kwargs.get("Malloc_Count")
20+
self.Free_Count = kwargs.get("Free_Count")
21+
self.JsonConnections = kwargs.get("JsonConnections")
22+
self.StaticRamUsage = kwargs.get("StaticRamUsage")
23+
self.PeakRamUsage = kwargs.get("PeakRamUsage")
24+
self.CurrentTime = kwargs.get("CurrentTime")
25+
self.uSec = kwargs.get("uSec")
26+
self.Seqno = kwargs.get("Seqno")

0 commit comments

Comments
 (0)