MySensors: Fix linter errors
This commit is contained in:
parent
af28bc1e1b
commit
fbac292ba6
17 changed files with 415 additions and 204 deletions
|
@ -284,7 +284,7 @@ homeassistant/components/mpd/* @fabaff
|
|||
homeassistant/components/mqtt/* @home-assistant/core @emontnemery
|
||||
homeassistant/components/msteams/* @peroyvind
|
||||
homeassistant/components/myq/* @bdraco
|
||||
homeassistant/components/mysensors/* @MartinHjelmare
|
||||
homeassistant/components/mysensors/* @MartinHjelmare @functionpointer
|
||||
homeassistant/components/mystrom/* @fabaff
|
||||
homeassistant/components/neato/* @dshokouhi @Santobert
|
||||
homeassistant/components/nederlandse_spoorwegen/* @YarmoM
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
"""Connect to a MySensors gateway via pymysensors API."""
|
||||
import logging
|
||||
from typing import Optional, Dict, List, Type, Callable, Union, Tuple
|
||||
from typing import Callable, Dict, List, Optional, Tuple, Type, Union
|
||||
|
||||
import voluptuous as vol
|
||||
from mysensors import BaseAsyncGateway
|
||||
import voluptuous as vol
|
||||
|
||||
from .device import MySensorsEntity, MySensorsDevice
|
||||
from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_topic
|
||||
from homeassistant.const import CONF_OPTIMISTIC
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from ... import config_entries
|
||||
from ...config_entries import ConfigEntry
|
||||
from ...helpers.typing import ConfigType, HomeAssistantType
|
||||
from .const import (
|
||||
ATTR_DEVICES,
|
||||
CONF_BAUD_RATE,
|
||||
|
@ -25,15 +27,15 @@ from .const import (
|
|||
CONF_TOPIC_OUT_PREFIX,
|
||||
CONF_VERSION,
|
||||
DOMAIN,
|
||||
MYSENSORS_GATEWAYS, SensorType, PLATFORM_TYPES, SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT, GatewayId,
|
||||
MYSENSORS_GATEWAYS,
|
||||
MYSENSORS_ON_UNLOAD,
|
||||
SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT,
|
||||
DevId,
|
||||
GatewayId,
|
||||
SensorType,
|
||||
)
|
||||
from .device import get_mysensors_devices
|
||||
from .gateway import finish_setup, get_mysensors_gateway, setup_gateway, gw_stop
|
||||
from .const import DevId
|
||||
from ... import config_entries
|
||||
from ...config_entries import ConfigEntry
|
||||
from ...helpers.typing import HomeAssistantType, ConfigType
|
||||
from .device import MySensorsDevice, MySensorsEntity, get_mysensors_devices
|
||||
from .gateway import finish_setup, get_mysensors_gateway, gw_stop, setup_gateway
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -119,41 +121,53 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass, config: ConfigType) -> bool:
|
||||
"""Set up the MySensors component."""
|
||||
if config is None or DOMAIN not in config:
|
||||
#when configured via ConfigEntry, hass calls async_setup(hass,None) and then calls async_setup_entry(...).
|
||||
#so in async_setup we have to check if there are any ConfigEntries and then return True. This lets async_setup_entry run.
|
||||
# when configured via ConfigEntry, hass calls async_setup(hass,None) and then calls async_setup_entry(...).
|
||||
# so in async_setup we have to check if there are any ConfigEntries and then return True. This lets async_setup_entry run.
|
||||
return bool(hass.config_entries.async_entries(DOMAIN))
|
||||
|
||||
config = config[DOMAIN]
|
||||
user_inputs = [{
|
||||
CONF_DEVICE: gw[CONF_DEVICE],
|
||||
CONF_PERSISTENCE: gw.get(CONF_PERSISTENCE_FILE,None),
|
||||
CONF_BAUD_RATE: gw.get(CONF_BAUD_RATE, None),
|
||||
CONF_TCP_PORT: gw.get(CONF_TCP_PORT, None),
|
||||
CONF_TOPIC_OUT_PREFIX: gw.get(CONF_TOPIC_OUT_PREFIX, None),
|
||||
CONF_TOPIC_IN_PREFIX: gw.get(CONF_TOPIC_IN_PREFIX, None),
|
||||
user_inputs = [
|
||||
{
|
||||
CONF_DEVICE: gw[CONF_DEVICE],
|
||||
CONF_PERSISTENCE: gw.get(CONF_PERSISTENCE_FILE, None),
|
||||
CONF_BAUD_RATE: gw.get(CONF_BAUD_RATE, None),
|
||||
CONF_TCP_PORT: gw.get(CONF_TCP_PORT, None),
|
||||
CONF_TOPIC_OUT_PREFIX: gw.get(CONF_TOPIC_OUT_PREFIX, None),
|
||||
CONF_TOPIC_IN_PREFIX: gw.get(CONF_TOPIC_IN_PREFIX, None),
|
||||
CONF_OPTIMISTIC: config.get(CONF_OPTIMISTIC, None),
|
||||
CONF_RETAIN: config.get(CONF_RETAIN, None),
|
||||
CONF_VERSION: config.get(CONF_VERSION, None),
|
||||
# nodes config ignored at this time. renaming nodes can now be done from the frontend.
|
||||
}
|
||||
for gw in config[CONF_GATEWAYS]
|
||||
]
|
||||
user_inputs = [
|
||||
{k: v for k, v in userinput.items() if v is not None}
|
||||
for userinput in user_inputs
|
||||
]
|
||||
|
||||
CONF_OPTIMISTIC: config.get(CONF_OPTIMISTIC, None),
|
||||
CONF_RETAIN: config.get(CONF_RETAIN, None),
|
||||
CONF_VERSION: config.get(CONF_VERSION, None),
|
||||
#nodes config ignored at this time. renaming nodes can now be done from the frontend.
|
||||
} for gw in config[CONF_GATEWAYS]]
|
||||
user_inputs = [{k: v for k, v in userinput.items() if v is not None} for userinput in user_inputs]
|
||||
|
||||
#there is an actual configuration in configuration.yaml, so we have to process it
|
||||
# there is an actual configuration in configuration.yaml, so we have to process it
|
||||
for user_input in user_inputs:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=user_input
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=user_input,
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
|
||||
"""Set up an instance of the MySensors integration.
|
||||
|
||||
Every instance has a connection to exactly one Gateway.
|
||||
"""
|
||||
_LOGGER.debug("async_setup_entry: %s (id: %s)", entry.title, entry.unique_id)
|
||||
gateway = await setup_gateway(hass, entry)
|
||||
|
||||
|
@ -169,16 +183,19 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
|
|||
for platform in SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT:
|
||||
await hass.config_entries.async_forward_entry_setup(entry, platform)
|
||||
await finish_setup(hass, entry, gateway)
|
||||
|
||||
hass.async_create_task(finish())
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
|
||||
"""Remove an instance of the MySensors integration."""
|
||||
_LOGGER.debug("unload entry: %s (id: %s)", entry.title, entry.unique_id)
|
||||
|
||||
gateway = get_mysensors_gateway(hass, entry.unique_id)
|
||||
if not gateway:
|
||||
_LOGGER.error("cant unload configentry %s, no gateway found", entry.unique_id)
|
||||
_LOGGER.error("can't unload configentry %s, no gateway found", entry.unique_id)
|
||||
return False
|
||||
|
||||
for platform in SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT:
|
||||
|
@ -196,7 +213,13 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo
|
|||
return True
|
||||
|
||||
|
||||
async def on_unload(hass: HomeAssistantType, entry: Union[ConfigEntry,GatewayId], fnct: Callable) -> None:
|
||||
async def on_unload(
|
||||
hass: HomeAssistantType, entry: Union[ConfigEntry, GatewayId], fnct: Callable
|
||||
) -> None:
|
||||
"""Register a callback to be called when entry is unloaded.
|
||||
|
||||
This function is used by platforms to cleanup after themselves
|
||||
"""
|
||||
if isinstance(entry, GatewayId):
|
||||
uniqueid = entry
|
||||
else:
|
||||
|
@ -206,16 +229,19 @@ async def on_unload(hass: HomeAssistantType, entry: Union[ConfigEntry,GatewayId]
|
|||
hass.data[key] = []
|
||||
hass.data[key].append(fnct)
|
||||
|
||||
|
||||
@callback
|
||||
def setup_mysensors_platform(
|
||||
hass,
|
||||
domain: str, # hass platform name
|
||||
discovery_info: Optional[Dict[str, List[DevId]]],
|
||||
device_class: Union[Type[MySensorsDevice], Dict[SensorType, Type[MySensorsEntity]]],
|
||||
device_args: Optional[Tuple] = None, # extra arguments that will be given to the entity constructor
|
||||
device_args: Optional[
|
||||
Tuple
|
||||
] = None, # extra arguments that will be given to the entity constructor
|
||||
async_add_entities: Callable = None,
|
||||
) -> Optional[List[MySensorsDevice]]:
|
||||
"""Set up a MySensors platform
|
||||
"""Set up a MySensors platform.
|
||||
|
||||
Sets up a bunch of instances of a single platform that is supported by this integration.
|
||||
The function is given a list of DevId, each one describing an instance to set up.
|
||||
|
@ -234,7 +260,11 @@ def setup_mysensors_platform(
|
|||
for dev_id in new_dev_ids:
|
||||
devices: Dict[DevId, MySensorsDevice] = get_mysensors_devices(hass, domain)
|
||||
if dev_id in devices:
|
||||
_LOGGER.debug("skipping setup of %s for platform %s as it already exists", dev_id, domain)
|
||||
_LOGGER.debug(
|
||||
"skipping setup of %s for platform %s as it already exists",
|
||||
dev_id,
|
||||
domain,
|
||||
)
|
||||
continue
|
||||
gateway_id, node_id, child_id, value_type = dev_id
|
||||
gateway: Optional[BaseAsyncGateway] = get_mysensors_gateway(hass, gateway_id)
|
||||
|
|
|
@ -31,12 +31,18 @@ SENSORS = {
|
|||
}
|
||||
|
||||
|
||||
async def async_setup_platform(hass: HomeAssistantType, config, async_add_entities, discovery_info=None):
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistantType, config, async_add_entities, discovery_info=None
|
||||
):
|
||||
"""Set up the mysensors platform for binary sensors."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable
|
||||
):
|
||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||
|
||||
async def async_discover(discovery_info):
|
||||
"""Discover and add a MySensors binary_sensor."""
|
||||
mysensors.setup_mysensors_platform(
|
||||
|
@ -47,9 +53,16 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry,
|
|||
async_add_entities=async_add_entities,
|
||||
)
|
||||
|
||||
await on_unload(hass, config_entry, async_dispatcher_connect(
|
||||
hass, MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN), async_discover
|
||||
))
|
||||
await on_unload(
|
||||
hass,
|
||||
config_entry,
|
||||
async_dispatcher_connect(
|
||||
hass,
|
||||
MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN),
|
||||
async_discover,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class MySensorsBinarySensor(mysensors.device.MySensorsEntity, BinarySensorEntity):
|
||||
"""Representation of a MySensors Binary Sensor child node."""
|
||||
|
|
|
@ -39,12 +39,18 @@ FAN_LIST = ["Auto", "Min", "Normal", "Max"]
|
|||
OPERATION_LIST = [HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT]
|
||||
|
||||
|
||||
async def async_setup_platform(hass: HomeAssistantType, config, async_add_entities, discovery_info=None):
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistantType, config, async_add_entities, discovery_info=None
|
||||
):
|
||||
"""Set up the mysensors climate."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable
|
||||
):
|
||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||
|
||||
async def async_discover(discovery_info):
|
||||
"""Discover and add a MySensors climate."""
|
||||
mysensors.setup_mysensors_platform(
|
||||
|
@ -55,9 +61,15 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry,
|
|||
async_add_entities=async_add_entities,
|
||||
)
|
||||
|
||||
await on_unload(hass, config_entry, async_dispatcher_connect(
|
||||
hass, MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN), async_discover
|
||||
))
|
||||
await on_unload(
|
||||
hass,
|
||||
config_entry,
|
||||
async_dispatcher_connect(
|
||||
hass,
|
||||
MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN),
|
||||
async_discover,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity):
|
||||
|
|
|
@ -1,35 +1,48 @@
|
|||
"""Config flow for MySensors."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from mysensors import BaseAsyncGateway
|
||||
|
||||
from homeassistant.components.mysensors import CONF_DEVICE, NODE_SCHEMA, DEFAULT_BAUD_RATE, DEFAULT_TCP_PORT, \
|
||||
CONF_NODE_NAME, is_persistence_file
|
||||
|
||||
from .const import DOMAIN, CONF_PERSISTENCE_FILE, CONF_BAUD_RATE, CONF_TCP_PORT, CONF_TOPIC_IN_PREFIX, \
|
||||
CONF_TOPIC_OUT_PREFIX, CONF_NODES
|
||||
from collections import OrderedDict
|
||||
import voluptuous as vol
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import hashlib
|
||||
import async_timeout
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from ..mqtt import valid_subscribe_topic, valid_publish_topic
|
||||
import async_timeout
|
||||
from mysensors import BaseAsyncGateway
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.mysensors import (
|
||||
CONF_DEVICE,
|
||||
DEFAULT_BAUD_RATE,
|
||||
DEFAULT_TCP_PORT,
|
||||
is_persistence_file,
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import (
|
||||
CONF_OPTIMISTIC,
|
||||
CONF_PERSISTENCE,
|
||||
CONF_RETAIN,
|
||||
CONF_VERSION,
|
||||
DEFAULT_VERSION,
|
||||
)
|
||||
from ... import config_entries
|
||||
from ...config_entries import ConfigEntry
|
||||
from ...core import callback
|
||||
from .gateway import _get_gateway, MQTT_COMPONENT, is_serial_port, is_socket_address
|
||||
from . import CONFIG_SCHEMA, CONF_VERSION, CONF_GATEWAYS, CONF_RETAIN, CONF_PERSISTENCE, CONF_OPTIMISTIC, GATEWAY_SCHEMA, DEFAULT_VERSION
|
||||
from ...data_entry_flow import RESULT_TYPE_CREATE_ENTRY
|
||||
from ..mqtt import valid_publish_topic, valid_subscribe_topic
|
||||
from .const import (
|
||||
CONF_BAUD_RATE,
|
||||
CONF_PERSISTENCE_FILE,
|
||||
CONF_TCP_PORT,
|
||||
CONF_TOPIC_IN_PREFIX,
|
||||
CONF_TOPIC_OUT_PREFIX,
|
||||
DOMAIN,
|
||||
)
|
||||
from .gateway import MQTT_COMPONENT, _get_gateway, is_serial_port, is_socket_address
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def try_connect(hass, user_input, uniqueid):
|
||||
if user_input[CONF_DEVICE]==MQTT_COMPONENT:
|
||||
return True#dont validate mqtt. mqtt gateways dont send ready messages :(
|
||||
async def try_connect(hass, user_input, uniqueid: str) -> bool:
|
||||
"""Try to connect to a gateway and report if it worked."""
|
||||
if user_input[CONF_DEVICE] == MQTT_COMPONENT:
|
||||
return True # dont validate mqtt. mqtt gateways dont send ready messages :(
|
||||
else:
|
||||
u = user_input.copy()
|
||||
u[CONF_PERSISTENCE] = False
|
||||
|
@ -68,8 +81,9 @@ async def try_connect(hass, user_input, uniqueid):
|
|||
return False
|
||||
|
||||
|
||||
|
||||
class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow."""
|
||||
|
||||
async def async_step_import(self, user_input):
|
||||
"""Import a config entry.
|
||||
|
||||
|
@ -78,19 +92,23 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
"""
|
||||
return await self.async_step_user(user_input=user_input)
|
||||
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Create a config entry from frontend user input."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
is_mqtt = user_input[CONF_DEVICE] == MQTT_COMPONENT
|
||||
is_serial = False
|
||||
try:
|
||||
if user_input[CONF_DEVICE]!=MQTT_COMPONENT:
|
||||
if user_input[CONF_DEVICE] != MQTT_COMPONENT:
|
||||
try:
|
||||
await self.hass.async_add_executor_job(is_serial_port, user_input[CONF_DEVICE])
|
||||
await self.hass.async_add_executor_job(
|
||||
is_serial_port, user_input[CONF_DEVICE]
|
||||
)
|
||||
is_serial = True
|
||||
except vol.Invalid:
|
||||
await self.hass.async_add_executor_job(is_socket_address, user_input[CONF_DEVICE])
|
||||
await self.hass.async_add_executor_job(
|
||||
is_socket_address, user_input[CONF_DEVICE]
|
||||
)
|
||||
is_serial = False
|
||||
except vol.Invalid:
|
||||
errors[CONF_DEVICE] = "invalid_device"
|
||||
|
@ -105,7 +123,10 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
valid_publish_topic(user_input[CONF_TOPIC_OUT_PREFIX])
|
||||
except vol.Invalid:
|
||||
errors[CONF_TOPIC_OUT_PREFIX] = "invalid_publish_topic"
|
||||
if not is_mqtt and (CONF_TCP_PORT in user_input and (user_input[CONF_TCP_PORT] < 1 or user_input[CONF_TCP_PORT] > 65535)):
|
||||
if not is_mqtt and (
|
||||
CONF_TCP_PORT in user_input
|
||||
and (user_input[CONF_TCP_PORT] < 1 or user_input[CONF_TCP_PORT] > 65535)
|
||||
):
|
||||
errors[CONF_TCP_PORT] = "invalid_port"
|
||||
try:
|
||||
if CONF_PERSISTENCE_FILE in user_input:
|
||||
|
@ -115,20 +136,26 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
uniquestr = user_input[CONF_DEVICE]
|
||||
if is_mqtt:
|
||||
uniquestr += user_input[CONF_TOPIC_IN_PREFIX] + user_input[CONF_TOPIC_OUT_PREFIX]
|
||||
uniquestr += (
|
||||
user_input[CONF_TOPIC_IN_PREFIX] + user_input[CONF_TOPIC_OUT_PREFIX]
|
||||
)
|
||||
elif not is_serial:
|
||||
uniquestr += str(user_input[CONF_TCP_PORT])
|
||||
gateway_id = hashlib.sha256(uniquestr.encode()).hexdigest()[:8]
|
||||
await self.async_set_unique_id(gateway_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
#if no errors so far, try to connect
|
||||
if not errors and not await try_connect(self.hass, user_input, uniqueid=gateway_id):
|
||||
# if no errors so far, try to connect
|
||||
if not errors and not await try_connect(
|
||||
self.hass, user_input, uniqueid=gateway_id
|
||||
):
|
||||
errors["base"] = "cannot_connect"
|
||||
|
||||
if not errors:
|
||||
_LOGGER.info("config_flow completed for %s", gateway_id)
|
||||
return self.async_create_entry(title=f"{user_input[CONF_DEVICE]}", data=user_input)
|
||||
return self.async_create_entry(
|
||||
title=f"{user_input[CONF_DEVICE]}", data=user_input
|
||||
)
|
||||
|
||||
schema = OrderedDict()
|
||||
schema[vol.Optional(CONF_OPTIMISTIC, default=False)] = bool
|
||||
|
@ -137,16 +164,13 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
schema[vol.Optional(CONF_VERSION, default=DEFAULT_VERSION)] = str
|
||||
|
||||
schema[vol.Required(CONF_DEVICE, default="127.0.0.1")] = str
|
||||
#schema[vol.Optional(CONF_PERSISTENCE_FILE)] = str
|
||||
schema[vol.Optional(CONF_BAUD_RATE, default=DEFAULT_BAUD_RATE)] = cv.positive_int
|
||||
# schema[vol.Optional(CONF_PERSISTENCE_FILE)] = str
|
||||
schema[
|
||||
vol.Optional(CONF_BAUD_RATE, default=DEFAULT_BAUD_RATE)
|
||||
] = cv.positive_int
|
||||
schema[vol.Optional(CONF_TCP_PORT, default=DEFAULT_TCP_PORT)] = int
|
||||
schema[vol.Optional(CONF_TOPIC_IN_PREFIX)] = str
|
||||
schema[vol.Optional(CONF_TOPIC_OUT_PREFIX)] = str
|
||||
|
||||
schema = vol.Schema(schema)
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=schema,
|
||||
errors=errors
|
||||
)
|
||||
|
||||
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""MySensors constants."""
|
||||
from collections import defaultdict
|
||||
from typing import Set, Dict, Tuple, List
|
||||
from typing import Dict, List, Set, Tuple
|
||||
|
||||
ATTR_DEVICES: str = "devices"
|
||||
|
||||
|
@ -62,7 +62,9 @@ BINARY_SENSOR_TYPES: Dict[SensorType, Set[ValueType]] = {
|
|||
|
||||
CLIMATE_TYPES: Dict[SensorType, Set[ValueType]] = {"S_HVAC": {"V_HVAC_FLOW_STATE"}}
|
||||
|
||||
COVER_TYPES: Dict[SensorType, Set[ValueType]] = {"S_COVER": {"V_DIMMER", "V_PERCENTAGE", "V_LIGHT", "V_STATUS"}}
|
||||
COVER_TYPES: Dict[SensorType, Set[ValueType]] = {
|
||||
"S_COVER": {"V_DIMMER", "V_PERCENTAGE", "V_LIGHT", "V_STATUS"}
|
||||
}
|
||||
|
||||
DEVICE_TRACKER_TYPES: Dict[SensorType, Set[ValueType]] = {"S_GPS": {"V_POSITION"}}
|
||||
|
||||
|
@ -138,7 +140,7 @@ FLAT_PLATFORM_TYPES: Dict[Tuple[str, SensorType], Set[ValueType]] = {
|
|||
}
|
||||
"""flatter version of PLATFORM_TYPES
|
||||
|
||||
dict mapping tuples of hass platform name and mysensors s_type to mysensors v_type
|
||||
dict mapping tuples of hass platform name and mysensors s_type to mysensors v_type
|
||||
"""
|
||||
|
||||
TYPE_TO_PLATFORMS: Dict[SensorType, List[str]] = defaultdict(list)
|
||||
|
|
|
@ -14,12 +14,17 @@ from homeassistant.helpers.typing import HomeAssistantType
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(hass: HomeAssistantType, config, async_add_entities, discovery_info=None):
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistantType, config, async_add_entities, discovery_info=None
|
||||
):
|
||||
"""Set up the mysensors platform for covers."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable
|
||||
):
|
||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||
|
||||
async def async_discover(discovery_info):
|
||||
"""Discover and add a MySensors cover."""
|
||||
|
@ -31,9 +36,15 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry,
|
|||
async_add_entities=async_add_entities,
|
||||
)
|
||||
|
||||
await on_unload(hass, config_entry, async_dispatcher_connect(
|
||||
hass, MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN), async_discover
|
||||
))
|
||||
await on_unload(
|
||||
hass,
|
||||
config_entry,
|
||||
async_dispatcher_connect(
|
||||
hass,
|
||||
MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN),
|
||||
async_discover,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity):
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
"""Handle MySensors devices."""
|
||||
from functools import partial
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from mysensors import Sensor, BaseAsyncGateway
|
||||
from mysensors import BaseAsyncGateway, Sensor
|
||||
from mysensors.sensor import ChildSensor
|
||||
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from .const import DevId, PLATFORM_TYPES
|
||||
from .const import DOMAIN
|
||||
|
||||
from .const import CHILD_CALLBACK, NODE_CALLBACK, UPDATE_DELAY
|
||||
from .const import (
|
||||
CHILD_CALLBACK,
|
||||
DOMAIN,
|
||||
NODE_CALLBACK,
|
||||
PLATFORM_TYPES,
|
||||
UPDATE_DELAY,
|
||||
DevId,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -33,7 +38,7 @@ class MySensorsDevice:
|
|||
self.gateway: BaseAsyncGateway = gateway
|
||||
self.node_id: int = node_id
|
||||
self.child_id: int = child_id
|
||||
self.value_type: int = value_type # value_type as int. string variant can be looked up in gateway consts
|
||||
self.value_type: int = value_type # value_type as int. string variant can be looked up in gateway consts
|
||||
self.child_type = self._mysensors_childsensor.type
|
||||
self._values = {}
|
||||
self._update_scheduled = False
|
||||
|
@ -41,26 +46,35 @@ class MySensorsDevice:
|
|||
|
||||
@property
|
||||
def dev_id(self) -> DevId:
|
||||
"""Return the DevId of this device.
|
||||
|
||||
It is used to route incoming MySensors messages to the correct device/entity.
|
||||
"""
|
||||
return self.gateway_id, self.node_id, self.child_id, self.value_type
|
||||
|
||||
@property
|
||||
def logger(self):
|
||||
def _logger(self):
|
||||
return logging.getLogger(f"{__name__}.{self.name}")
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Remove this entity from home assistant."""
|
||||
for platform in PLATFORM_TYPES:
|
||||
platform_str = MYSENSORS_PLATFORM_DEVICES.format(platform)
|
||||
if platform_str in self.hass.data:
|
||||
platform_dict = self.hass.data[platform_str]
|
||||
if self.dev_id in platform_dict:
|
||||
if platform_dict[self.dev_id] is not self:
|
||||
self.logger.warning("possible duplicate device: %s", self.dev_id)
|
||||
self._logger.warning(
|
||||
"possible duplicate device: %s", self.dev_id
|
||||
)
|
||||
del platform_dict[self.dev_id]
|
||||
self.logger.debug("deleted %s from platform %s", self.dev_id, platform)
|
||||
|
||||
self._logger.debug(
|
||||
"deleted %s from platform %s", self.dev_id, platform
|
||||
)
|
||||
|
||||
@property
|
||||
def gateway_id(self) -> str:
|
||||
"""Return the id of the gateway that this device belongs to."""
|
||||
return self.gateway.unique_id
|
||||
|
||||
@property
|
||||
|
@ -73,28 +87,29 @@ class MySensorsDevice:
|
|||
|
||||
@property
|
||||
def sketch_name(self) -> str:
|
||||
"""Return the name of the sketch running on the whole node (will be the same for several entities!)."""
|
||||
return self._mysensors_sensor.sketch_name
|
||||
|
||||
@property
|
||||
def sketch_version(self) -> str:
|
||||
"""Return the version of the sketch running on the whole node (will be the same for several entities!)."""
|
||||
return self._mysensors_sensor.sketch_version
|
||||
|
||||
@property
|
||||
def node_name(self) -> str:
|
||||
"""Name of the whole node (will be the same for several entities!)"""
|
||||
"""Name of the whole node (will be the same for several entities!)."""
|
||||
return f"{self.sketch_name} {self.node_id}"
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
"""Return a unique ID for use in home assistant."""
|
||||
return f"mys{self.gateway_id}-{self.node_id}-{self.child_id}-{self.value_type}"
|
||||
|
||||
@property
|
||||
def device_info(self) -> Optional[Dict[str, Any]]:
|
||||
"""Return a dict that allows home assistant to puzzle all entities belonging to a node together."""
|
||||
return {
|
||||
"identifiers": {
|
||||
(DOMAIN, f"mys{self.gateway_id}-{self.node_id}")
|
||||
},
|
||||
"identifiers": {(DOMAIN, f"mys{self.gateway_id}-{self.node_id}")},
|
||||
"name": self.node_name,
|
||||
"manufacturer": DOMAIN,
|
||||
"model": self.node_name,
|
||||
|
@ -176,11 +191,12 @@ class MySensorsDevice:
|
|||
|
||||
|
||||
def get_mysensors_devices(hass, domain: str) -> Dict[DevId, MySensorsDevice]:
|
||||
"""Return MySensors devices for a hass platform name"""
|
||||
"""Return MySensors devices for a hass platform name."""
|
||||
if MYSENSORS_PLATFORM_DEVICES.format(domain) not in hass.data:
|
||||
hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)] = {}
|
||||
return hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)]
|
||||
|
||||
|
||||
class MySensorsEntity(MySensorsDevice, Entity):
|
||||
"""Representation of a MySensors entity."""
|
||||
|
||||
|
@ -202,7 +218,9 @@ class MySensorsEntity(MySensorsDevice, Entity):
|
|||
"""Register update callback."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, CHILD_CALLBACK.format(*self.dev_id), self.async_update_callback
|
||||
self.hass,
|
||||
CHILD_CALLBACK.format(*self.dev_id),
|
||||
self.async_update_callback,
|
||||
)
|
||||
)
|
||||
self.async_on_remove(
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
"""Support for tracking MySensors devices."""
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.device_tracker import DOMAIN
|
||||
from homeassistant.components.mysensors import DevId, on_unload
|
||||
from homeassistant.components.mysensors.const import GatewayId
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
from homeassistant.util import slugify
|
||||
|
||||
|
||||
async def async_setup_platform(hass: HomeAssistantType, config, async_add_entities, discovery_info=None) -> None:
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable) -> None:
|
||||
pass
|
||||
|
||||
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||
async def async_setup_scanner(
|
||||
hass: HomeAssistantType, config, async_see, discovery_info=None
|
||||
):
|
||||
"""Set up the MySensors device scanner."""
|
||||
new_devices = mysensors.setup_mysensors_platform(
|
||||
hass,
|
||||
|
@ -33,16 +25,24 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
|||
for device in new_devices:
|
||||
gateway_id: GatewayId = device.gateway.unique_id
|
||||
dev_id: DevId = (gateway_id, device.node_id, device.child_id, device.value_type)
|
||||
await on_unload(hass, gateway_id, async_dispatcher_connect(
|
||||
await on_unload(
|
||||
hass,
|
||||
mysensors.const.CHILD_CALLBACK.format(*dev_id),
|
||||
device.async_update_callback,
|
||||
))
|
||||
await on_unload(hass, gateway_id, async_dispatcher_connect(
|
||||
gateway_id,
|
||||
async_dispatcher_connect(
|
||||
hass,
|
||||
mysensors.const.CHILD_CALLBACK.format(*dev_id),
|
||||
device.async_update_callback,
|
||||
),
|
||||
)
|
||||
await on_unload(
|
||||
hass,
|
||||
mysensors.const.NODE_CALLBACK.format(gateway_id, device.node_id),
|
||||
device.async_update_callback,
|
||||
))
|
||||
gateway_id,
|
||||
async_dispatcher_connect(
|
||||
hass,
|
||||
mysensors.const.NODE_CALLBACK.format(gateway_id, device.node_id),
|
||||
device.async_update_callback,
|
||||
),
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -50,7 +50,7 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
|||
class MySensorsDeviceScanner(mysensors.device.MySensorsDevice):
|
||||
"""Represent a MySensors scanner."""
|
||||
|
||||
def __init__(self, hass, async_see, *args):
|
||||
def __init__(self, hass: HomeAssistantType, async_see, *args):
|
||||
"""Set up instance."""
|
||||
super().__init__(*args)
|
||||
self.async_see = async_see
|
||||
|
|
|
@ -4,22 +4,21 @@ from collections import defaultdict
|
|||
import logging
|
||||
import socket
|
||||
import sys
|
||||
from typing import Any, Callable, Coroutine, Dict, Optional, Union
|
||||
|
||||
import async_timeout
|
||||
from mysensors import mysensors, BaseAsyncGateway, Sensor, Message
|
||||
from typing import Optional, List, Any, Callable, Coroutine, Union, Dict
|
||||
from mysensors import BaseAsyncGateway, Message, Sensor, mysensors
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from ...config_entries import ConfigEntry
|
||||
from ...helpers.typing import HomeAssistantType
|
||||
from .const import (
|
||||
CONF_BAUD_RATE,
|
||||
CONF_DEVICE,
|
||||
CONF_GATEWAYS,
|
||||
CONF_NODES,
|
||||
CONF_PERSISTENCE,
|
||||
CONF_PERSISTENCE_FILE,
|
||||
CONF_RETAIN,
|
||||
|
@ -27,15 +26,12 @@ from .const import (
|
|||
CONF_TOPIC_IN_PREFIX,
|
||||
CONF_TOPIC_OUT_PREFIX,
|
||||
CONF_VERSION,
|
||||
DOMAIN,
|
||||
MYSENSORS_GATEWAY_READY,
|
||||
MYSENSORS_GATEWAYS,
|
||||
GatewayId,
|
||||
)
|
||||
from .const import GatewayId
|
||||
from .handler import HANDLERS
|
||||
from .helpers import discover_mysensors_platform, validate_child, validate_node
|
||||
from ...config_entries import ConfigEntry
|
||||
from ...helpers.typing import HomeAssistantType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -62,22 +58,30 @@ def is_socket_address(value):
|
|||
raise vol.Invalid("Device is not a valid domain name or ip address") from err
|
||||
|
||||
|
||||
def get_mysensors_gateway(hass: HomeAssistantType, gateway_id: GatewayId) -> Optional[BaseAsyncGateway]:
|
||||
"""Returns the Gateway for a given GatewayId."""
|
||||
def get_mysensors_gateway(
|
||||
hass: HomeAssistantType, gateway_id: GatewayId
|
||||
) -> Optional[BaseAsyncGateway]:
|
||||
"""Return the Gateway for a given GatewayId."""
|
||||
if MYSENSORS_GATEWAYS not in hass.data:
|
||||
hass.data[MYSENSORS_GATEWAYS] = {}
|
||||
gateways = hass.data.get(MYSENSORS_GATEWAYS)
|
||||
return gateways.get(gateway_id)
|
||||
|
||||
|
||||
async def setup_gateway(hass: HomeAssistantType, entry: ConfigEntry) -> Optional[BaseAsyncGateway]:
|
||||
async def setup_gateway(
|
||||
hass: HomeAssistantType, entry: ConfigEntry
|
||||
) -> Optional[BaseAsyncGateway]:
|
||||
"""Set up all gateways."""
|
||||
|
||||
ready_gateway = await _get_gateway(hass, entry)
|
||||
return ready_gateway
|
||||
|
||||
|
||||
async def _get_gateway(hass: HomeAssistantType, entry: Union[ConfigEntry,Dict[str, Any]], unique_id: Optional[str]=None) -> Optional[BaseAsyncGateway]:
|
||||
async def _get_gateway(
|
||||
hass: HomeAssistantType,
|
||||
entry: Union[ConfigEntry, Dict[str, Any]],
|
||||
unique_id: Optional[str] = None,
|
||||
) -> Optional[BaseAsyncGateway]:
|
||||
"""Return gateway after setup of the gateway."""
|
||||
|
||||
if isinstance(entry, ConfigEntry):
|
||||
|
@ -87,8 +91,12 @@ async def _get_gateway(hass: HomeAssistantType, entry: Union[ConfigEntry,Dict[st
|
|||
data: Dict[str, Any] = entry
|
||||
|
||||
if unique_id is None:
|
||||
raise ValueError("no unique id! either give configEntry for auto-extraction or explicitly give one")
|
||||
persistence_file = data.get(CONF_PERSISTENCE_FILE, hass.config.path(f"mysensors{unique_id}.pickle"))
|
||||
raise ValueError(
|
||||
"no unique id! either give configEntry for auto-extraction or explicitly give one"
|
||||
)
|
||||
persistence_file = data.get(
|
||||
CONF_PERSISTENCE_FILE, hass.config.path(f"mysensors{unique_id}.pickle")
|
||||
)
|
||||
persistence = data.get(CONF_PERSISTENCE)
|
||||
version = data.get(CONF_VERSION)
|
||||
device = data.get(CONF_DEVICE)
|
||||
|
@ -98,8 +106,8 @@ async def _get_gateway(hass: HomeAssistantType, entry: Union[ConfigEntry,Dict[st
|
|||
out_prefix = data.get(CONF_TOPIC_OUT_PREFIX, "")
|
||||
|
||||
if device == MQTT_COMPONENT:
|
||||
#what is the purpose of this?
|
||||
#if not await async_setup_component(hass, MQTT_COMPONENT, entry):
|
||||
# what is the purpose of this?
|
||||
# if not await async_setup_component(hass, MQTT_COMPONENT, entry):
|
||||
# return None
|
||||
mqtt = hass.components.mqtt
|
||||
retain = data.get(CONF_RETAIN)
|
||||
|
@ -170,7 +178,9 @@ async def _get_gateway(hass: HomeAssistantType, entry: Union[ConfigEntry,Dict[st
|
|||
return gateway
|
||||
|
||||
|
||||
async def finish_setup(hass: HomeAssistantType, hass_config: ConfigEntry, gateway: BaseAsyncGateway):
|
||||
async def finish_setup(
|
||||
hass: HomeAssistantType, hass_config: ConfigEntry, gateway: BaseAsyncGateway
|
||||
):
|
||||
"""Load any persistent devices and platforms and start gateway."""
|
||||
discover_tasks = []
|
||||
start_tasks = []
|
||||
|
@ -183,7 +193,9 @@ async def finish_setup(hass: HomeAssistantType, hass_config: ConfigEntry, gatewa
|
|||
await asyncio.wait(start_tasks)
|
||||
|
||||
|
||||
async def _discover_persistent_devices(hass: HomeAssistantType, hass_config: ConfigEntry, gateway: BaseAsyncGateway):
|
||||
async def _discover_persistent_devices(
|
||||
hass: HomeAssistantType, hass_config: ConfigEntry, gateway: BaseAsyncGateway
|
||||
):
|
||||
"""Discover platforms for devices loaded via persistence file."""
|
||||
tasks = []
|
||||
new_devices = defaultdict(list)
|
||||
|
@ -191,7 +203,7 @@ async def _discover_persistent_devices(hass: HomeAssistantType, hass_config: Con
|
|||
if not validate_node(gateway, node_id):
|
||||
continue
|
||||
node: Sensor = gateway.sensors[node_id]
|
||||
for child in node.children.values():#child is of type ChildSensor
|
||||
for child in node.children.values(): # child is of type ChildSensor
|
||||
validated = validate_child(gateway, node_id, child)
|
||||
for platform, dev_ids in validated.items():
|
||||
new_devices[platform].extend(dev_ids)
|
||||
|
@ -201,9 +213,15 @@ async def _discover_persistent_devices(hass: HomeAssistantType, hass_config: Con
|
|||
if tasks:
|
||||
await asyncio.wait(tasks)
|
||||
|
||||
|
||||
async def gw_stop(hass, gateway: BaseAsyncGateway):
|
||||
"""Stop the gateway."""
|
||||
_LOGGER.info("stopping gateway %s", gateway.unique_id)
|
||||
if hasattr(gateway, "connect_task") and gateway.connect_task is not None and not gateway.connect_task.done():
|
||||
if (
|
||||
hasattr(gateway, "connect_task")
|
||||
and gateway.connect_task is not None
|
||||
and not gateway.connect_task.done()
|
||||
):
|
||||
gateway.connect_task.cancel()
|
||||
await gateway.stop()
|
||||
|
||||
|
@ -213,7 +231,9 @@ async def _gw_start(hass: HomeAssistantType, gateway: BaseAsyncGateway):
|
|||
# Don't use hass.async_create_task to avoid holding up setup indefinitely.
|
||||
gateway.connect_task = hass.loop.create_task(gateway.start())
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: gw_stop(hass, gateway))
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP, lambda event: gw_stop(hass, gateway)
|
||||
)
|
||||
if gateway.device == "mqtt":
|
||||
# Gatways connected via mqtt doesn't send gateway ready message.
|
||||
return
|
||||
|
@ -234,18 +254,24 @@ async def _gw_start(hass: HomeAssistantType, gateway: BaseAsyncGateway):
|
|||
hass.data.pop(gateway_ready_key, None)
|
||||
|
||||
|
||||
def _gw_callback_factory(hass: HomeAssistantType, hass_config: ConfigEntry) -> Callable[[Message], None]:
|
||||
def _gw_callback_factory(
|
||||
hass: HomeAssistantType, hass_config: ConfigEntry
|
||||
) -> Callable[[Message], None]:
|
||||
"""Return a new callback for the gateway."""
|
||||
|
||||
@callback
|
||||
def mysensors_callback(msg: Message):
|
||||
"""Handle messages from a MySensors gateway."""
|
||||
"""Handle messages from a MySensors gateway.
|
||||
|
||||
All MySenors messages are received here.
|
||||
The messages are passed to handler functions depending on their type.
|
||||
"""
|
||||
_LOGGER.debug("Node update: node %s child %s", msg.node_id, msg.child_id)
|
||||
|
||||
msg_type = msg.gateway.const.MessageType(msg.type)
|
||||
msg_handler: Callable[[Any, ConfigEntry, Message], Coroutine[None]] = HANDLERS.get(
|
||||
msg_type.name
|
||||
)
|
||||
msg_handler: Callable[
|
||||
[Any, ConfigEntry, Message], Coroutine[None]
|
||||
] = HANDLERS.get(msg_type.name)
|
||||
|
||||
if msg_handler is None:
|
||||
return
|
||||
|
|
|
@ -7,11 +7,10 @@ from homeassistant.core import callback
|
|||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.util import decorator
|
||||
|
||||
from .const import CHILD_CALLBACK, MYSENSORS_GATEWAY_READY, NODE_CALLBACK
|
||||
from .const import DevId
|
||||
from ...config_entries import ConfigEntry
|
||||
from .const import CHILD_CALLBACK, MYSENSORS_GATEWAY_READY, NODE_CALLBACK, DevId
|
||||
from .device import get_mysensors_devices
|
||||
from .helpers import discover_mysensors_platform, validate_set_msg
|
||||
from ...config_entries import ConfigEntry
|
||||
|
||||
HANDLERS = decorator.Registry()
|
||||
|
||||
|
@ -70,7 +69,9 @@ async def handle_gateway_ready(hass, hass_config: ConfigEntry, msg: Message) ->
|
|||
|
||||
|
||||
@callback
|
||||
def _handle_child_update(hass, hass_config: ConfigEntry, validated: Dict[str, List[DevId]]):
|
||||
def _handle_child_update(
|
||||
hass, hass_config: ConfigEntry, validated: Dict[str, List[DevId]]
|
||||
):
|
||||
"""Handle a child update."""
|
||||
signals: List[str] = []
|
||||
|
||||
|
|
|
@ -1,80 +1,104 @@
|
|||
"""Helper functions for mysensors package."""
|
||||
from collections import defaultdict
|
||||
import logging
|
||||
from enum import IntEnum
|
||||
import logging
|
||||
from typing import DefaultDict, Dict, List, Optional, Set
|
||||
|
||||
import voluptuous as vol
|
||||
from mysensors import BaseAsyncGateway, Message
|
||||
from typing import Dict, Tuple, Optional, List, Set, Any, DefaultDict
|
||||
|
||||
from mysensors.sensor import ChildSensor
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util.decorator import Registry
|
||||
|
||||
from .const import ATTR_DEVICES, DOMAIN, FLAT_PLATFORM_TYPES, TYPE_TO_PLATFORMS, ValueType, SensorType, DevId, \
|
||||
MYSENSORS_DISCOVERY
|
||||
from ...config_entries import ConfigEntry
|
||||
from ...helpers.dispatcher import async_dispatcher_send
|
||||
from .const import (
|
||||
ATTR_DEVICES,
|
||||
DOMAIN,
|
||||
FLAT_PLATFORM_TYPES,
|
||||
MYSENSORS_DISCOVERY,
|
||||
TYPE_TO_PLATFORMS,
|
||||
DevId,
|
||||
SensorType,
|
||||
ValueType,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
SCHEMAS = Registry()
|
||||
|
||||
|
||||
@callback
|
||||
def discover_mysensors_platform(hass, hass_config: ConfigEntry, platform: str, new_devices: List[DevId]) -> None:
|
||||
def discover_mysensors_platform(
|
||||
hass, hass_config: ConfigEntry, platform: str, new_devices: List[DevId]
|
||||
) -> None:
|
||||
"""Discover a MySensors platform."""
|
||||
_LOGGER.debug("discovering platform %s with devIds: %s", platform, new_devices)
|
||||
async_dispatcher_send(
|
||||
hass, MYSENSORS_DISCOVERY.format(hass_config.unique_id, platform), {ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN}
|
||||
hass,
|
||||
MYSENSORS_DISCOVERY.format(hass_config.unique_id, platform),
|
||||
{ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN},
|
||||
)
|
||||
|
||||
|
||||
def default_schema(gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType) -> vol.Schema:
|
||||
def default_schema(
|
||||
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
|
||||
) -> vol.Schema:
|
||||
"""Return a default validation schema for value types."""
|
||||
schema = {value_type_name: cv.string}
|
||||
return get_child_schema(gateway, child, value_type_name, schema)
|
||||
|
||||
|
||||
@SCHEMAS.register(("light", "V_DIMMER"))
|
||||
def light_dimmer_schema(gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType) -> vol.Schema:
|
||||
def light_dimmer_schema(
|
||||
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
|
||||
) -> vol.Schema:
|
||||
"""Return a validation schema for V_DIMMER."""
|
||||
schema = {"V_DIMMER": cv.string, "V_LIGHT": cv.string}
|
||||
return get_child_schema(gateway, child, value_type_name, schema)
|
||||
|
||||
|
||||
@SCHEMAS.register(("light", "V_PERCENTAGE"))
|
||||
def light_percentage_schema(gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType) -> vol.Schema:
|
||||
def light_percentage_schema(
|
||||
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
|
||||
) -> vol.Schema:
|
||||
"""Return a validation schema for V_PERCENTAGE."""
|
||||
schema = {"V_PERCENTAGE": cv.string, "V_STATUS": cv.string}
|
||||
return get_child_schema(gateway, child, value_type_name, schema)
|
||||
|
||||
|
||||
@SCHEMAS.register(("light", "V_RGB"))
|
||||
def light_rgb_schema(gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType) -> vol.Schema:
|
||||
def light_rgb_schema(
|
||||
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
|
||||
) -> vol.Schema:
|
||||
"""Return a validation schema for V_RGB."""
|
||||
schema = {"V_RGB": cv.string, "V_STATUS": cv.string}
|
||||
return get_child_schema(gateway, child, value_type_name, schema)
|
||||
|
||||
|
||||
@SCHEMAS.register(("light", "V_RGBW"))
|
||||
def light_rgbw_schema(gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType) -> vol.Schema:
|
||||
def light_rgbw_schema(
|
||||
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
|
||||
) -> vol.Schema:
|
||||
"""Return a validation schema for V_RGBW."""
|
||||
schema = {"V_RGBW": cv.string, "V_STATUS": cv.string}
|
||||
return get_child_schema(gateway, child, value_type_name, schema)
|
||||
|
||||
|
||||
@SCHEMAS.register(("switch", "V_IR_SEND"))
|
||||
def switch_ir_send_schema(gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType) -> vol.Schema:
|
||||
def switch_ir_send_schema(
|
||||
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
|
||||
) -> vol.Schema:
|
||||
"""Return a validation schema for V_IR_SEND."""
|
||||
schema = {"V_IR_SEND": cv.string, "V_LIGHT": cv.string}
|
||||
return get_child_schema(gateway, child, value_type_name, schema)
|
||||
|
||||
|
||||
def get_child_schema(gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType, schema) -> vol.Schema:
|
||||
def get_child_schema(
|
||||
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType, schema
|
||||
) -> vol.Schema:
|
||||
"""Return a child schema."""
|
||||
set_req = gateway.const.SetReq
|
||||
child_schema = child.get_schema(gateway.protocol_version)
|
||||
|
@ -90,7 +114,9 @@ def get_child_schema(gateway: BaseAsyncGateway, child: ChildSensor, value_type_n
|
|||
return schema
|
||||
|
||||
|
||||
def invalid_msg(gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType):
|
||||
def invalid_msg(
|
||||
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
|
||||
):
|
||||
"""Return a message for an invalid child during schema validation."""
|
||||
pres = gateway.const.Presentation
|
||||
set_req = gateway.const.SetReq
|
||||
|
@ -115,8 +141,13 @@ def validate_node(gateway: BaseAsyncGateway, node_id: int) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def validate_child(gateway: BaseAsyncGateway, node_id: int, child: ChildSensor, value_type: Optional[int] = None) -> DefaultDict[str, List[DevId]]:
|
||||
"""Validate a child. Returns a dict mapping hass platform names to list of DevId"""
|
||||
def validate_child(
|
||||
gateway: BaseAsyncGateway,
|
||||
node_id: int,
|
||||
child: ChildSensor,
|
||||
value_type: Optional[int] = None,
|
||||
) -> DefaultDict[str, List[DevId]]:
|
||||
"""Validate a child. Returns a dict mapping hass platform names to list of DevId."""
|
||||
validated: defaultdict[str, List[DevId]] = defaultdict(list)
|
||||
pres: IntEnum = gateway.const.Presentation
|
||||
set_req: IntEnum = gateway.const.SetReq
|
||||
|
@ -133,7 +164,9 @@ def validate_child(gateway: BaseAsyncGateway, node_id: int, child: ChildSensor,
|
|||
return validated
|
||||
|
||||
for platform in platforms:
|
||||
platform_v_names: Set[ValueType] = FLAT_PLATFORM_TYPES[platform, child_type_name]
|
||||
platform_v_names: Set[ValueType] = FLAT_PLATFORM_TYPES[
|
||||
platform, child_type_name
|
||||
]
|
||||
v_names: Set[ValueType] = platform_v_names & value_type_names
|
||||
if not v_names:
|
||||
child_value_names: Set[ValueType] = {
|
||||
|
@ -155,7 +188,12 @@ def validate_child(gateway: BaseAsyncGateway, node_id: int, child: ChildSensor,
|
|||
exc,
|
||||
)
|
||||
continue
|
||||
dev_id: DevId = gateway.unique_id, node_id, child.id, set_req[v_name].value
|
||||
dev_id: DevId = (
|
||||
gateway.unique_id,
|
||||
node_id,
|
||||
child.id,
|
||||
set_req[v_name].value,
|
||||
)
|
||||
validated[platform].append(dev_id)
|
||||
|
||||
return validated
|
||||
|
|
|
@ -17,25 +17,31 @@ from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY
|
|||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.util.color import rgb_hex_to_rgb_list
|
||||
|
||||
SUPPORT_MYSENSORS_RGBW = SUPPORT_COLOR | SUPPORT_WHITE_VALUE
|
||||
|
||||
|
||||
async def async_setup_platform(hass: HomeAssistantType, config, async_add_entities, discovery_info=None):
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistantType, config, async_add_entities, discovery_info=None
|
||||
):
|
||||
"""Set up the mysensors platform for lights."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable
|
||||
):
|
||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||
device_class_map = {
|
||||
"S_DIMMER": MySensorsLightDimmer,
|
||||
"S_RGB_LIGHT": MySensorsLightRGB,
|
||||
"S_RGBW_LIGHT": MySensorsLightRGBW,
|
||||
}
|
||||
|
||||
async def async_discover(discovery_info):
|
||||
"""Discover and add a MySensors light."""
|
||||
mysensors.setup_mysensors_platform(
|
||||
|
@ -46,9 +52,16 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry,
|
|||
async_add_entities=async_add_entities,
|
||||
)
|
||||
|
||||
await on_unload(hass, config_entry, async_dispatcher_connect(
|
||||
hass, MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN), async_discover
|
||||
))
|
||||
await on_unload(
|
||||
hass,
|
||||
config_entry,
|
||||
async_dispatcher_connect(
|
||||
hass,
|
||||
MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN),
|
||||
async_discover,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity):
|
||||
"""Representation of a MySensors Light child node."""
|
||||
|
|
|
@ -61,12 +61,18 @@ SENSORS = {
|
|||
}
|
||||
|
||||
|
||||
async def async_setup_platform(hass: HomeAssistantType, config, async_add_entities, discovery_info=None):
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistantType, config, async_add_entities, discovery_info=None
|
||||
):
|
||||
"""Set up the MySensors platform for sensors."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable
|
||||
):
|
||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||
|
||||
async def async_discover(discovery_info):
|
||||
"""Discover and add a MySensors sensor."""
|
||||
mysensors.setup_mysensors_platform(
|
||||
|
@ -77,9 +83,15 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry,
|
|||
async_add_entities=async_add_entities,
|
||||
)
|
||||
|
||||
await on_unload(hass, config_entry, async_dispatcher_connect(
|
||||
hass, MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN), async_discover
|
||||
))
|
||||
await on_unload(
|
||||
hass,
|
||||
config_entry,
|
||||
async_dispatcher_connect(
|
||||
hass,
|
||||
MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN),
|
||||
async_discover,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class MySensorsSensor(mysensors.device.MySensorsEntity):
|
||||
|
|
|
@ -7,12 +7,12 @@ from homeassistant.components import mysensors
|
|||
from homeassistant.components.switch import DOMAIN, SwitchEntity
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from . import on_unload
|
||||
|
||||
from .const import DOMAIN as MYSENSORS_DOMAIN, SERVICE_SEND_IR_CODE, MYSENSORS_DISCOVERY
|
||||
from . import on_unload
|
||||
from ...config_entries import ConfigEntry
|
||||
from ...helpers.dispatcher import async_dispatcher_connect
|
||||
from ...helpers.typing import HomeAssistantType
|
||||
from .const import DOMAIN as MYSENSORS_DOMAIN, MYSENSORS_DISCOVERY, SERVICE_SEND_IR_CODE
|
||||
|
||||
ATTR_IR_CODE = "V_IR_SEND"
|
||||
|
||||
|
@ -21,12 +21,17 @@ SEND_IR_CODE_SERVICE_SCHEMA = vol.Schema(
|
|||
)
|
||||
|
||||
|
||||
async def async_setup_platform(hass: HomeAssistantType, config, async_add_entities, discovery_info=None):
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistantType, config, async_add_entities, discovery_info=None
|
||||
):
|
||||
"""Set up the mysensors platform for switches."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable):
|
||||
"""Set up the mysensors platform for switches."""
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable
|
||||
):
|
||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||
device_class_map = {
|
||||
"S_DOOR": MySensorsSwitch,
|
||||
"S_MOTION": MySensorsSwitch,
|
||||
|
@ -64,7 +69,7 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry,
|
|||
device
|
||||
for device in devices.values()
|
||||
if isinstance(device, MySensorsIRSwitch)
|
||||
and device.entity_id in entity_ids
|
||||
and device.entity_id in entity_ids
|
||||
]
|
||||
else:
|
||||
_devices = [
|
||||
|
@ -84,9 +89,15 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry,
|
|||
schema=SEND_IR_CODE_SERVICE_SCHEMA,
|
||||
)
|
||||
|
||||
await on_unload(hass, config_entry, async_dispatcher_connect(
|
||||
hass, MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN), async_discover
|
||||
))
|
||||
await on_unload(
|
||||
hass,
|
||||
config_entry,
|
||||
async_dispatcher_connect(
|
||||
hass,
|
||||
MYSENSORS_DISCOVERY.format(config_entry.unique_id, DOMAIN),
|
||||
async_discover,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class MySensorsSwitch(mysensors.device.MySensorsEntity, SwitchEntity):
|
||||
|
|
|
@ -1542,7 +1542,7 @@ pymusiccast==0.1.6
|
|||
pymyq==2.0.14
|
||||
|
||||
# homeassistant.components.mysensors
|
||||
pymysensors==0.18.0
|
||||
pymysensors==0.20.1
|
||||
|
||||
# homeassistant.components.nanoleaf
|
||||
pynanoleaf==0.0.5
|
||||
|
|
|
@ -788,7 +788,7 @@ pymonoprice==0.3
|
|||
pymyq==2.0.14
|
||||
|
||||
# homeassistant.components.mysensors
|
||||
pymysensors==0.18.0
|
||||
pymysensors==0.20.1
|
||||
|
||||
# homeassistant.components.nut
|
||||
pynut2==2.1.2
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue