mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 00:07:10 +00:00
Type mysensors strictly (#51535)
This commit is contained in:
parent
564042ec67
commit
7560a77e0e
@ -46,6 +46,7 @@ homeassistant.components.local_ip.*
|
|||||||
homeassistant.components.lock.*
|
homeassistant.components.lock.*
|
||||||
homeassistant.components.mailbox.*
|
homeassistant.components.mailbox.*
|
||||||
homeassistant.components.media_player.*
|
homeassistant.components.media_player.*
|
||||||
|
homeassistant.components.mysensors.*
|
||||||
homeassistant.components.nam.*
|
homeassistant.components.nam.*
|
||||||
homeassistant.components.network.*
|
homeassistant.components.network.*
|
||||||
homeassistant.components.notify.*
|
homeassistant.components.notify.*
|
||||||
|
@ -40,6 +40,7 @@ from .const import (
|
|||||||
MYSENSORS_ON_UNLOAD,
|
MYSENSORS_ON_UNLOAD,
|
||||||
PLATFORMS_WITH_ENTRY_SUPPORT,
|
PLATFORMS_WITH_ENTRY_SUPPORT,
|
||||||
DevId,
|
DevId,
|
||||||
|
DiscoveryInfo,
|
||||||
SensorType,
|
SensorType,
|
||||||
)
|
)
|
||||||
from .device import MySensorsDevice, get_mysensors_devices
|
from .device import MySensorsDevice, get_mysensors_devices
|
||||||
@ -70,7 +71,7 @@ def set_default_persistence_file(value: dict) -> dict:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def has_all_unique_files(value):
|
def has_all_unique_files(value: list[dict]) -> list[dict]:
|
||||||
"""Validate that all persistence files are unique and set if any is set."""
|
"""Validate that all persistence files are unique and set if any is set."""
|
||||||
persistence_files = [gateway[CONF_PERSISTENCE_FILE] for gateway in value]
|
persistence_files = [gateway[CONF_PERSISTENCE_FILE] for gateway in value]
|
||||||
schema = vol.Schema(vol.Unique())
|
schema = vol.Schema(vol.Unique())
|
||||||
@ -78,17 +79,17 @@ def has_all_unique_files(value):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def is_persistence_file(value):
|
def is_persistence_file(value: str) -> str:
|
||||||
"""Validate that persistence file path ends in either .pickle or .json."""
|
"""Validate that persistence file path ends in either .pickle or .json."""
|
||||||
if value.endswith((".json", ".pickle")):
|
if value.endswith((".json", ".pickle")):
|
||||||
return value
|
return value
|
||||||
raise vol.Invalid(f"{value} does not end in either `.json` or `.pickle`")
|
raise vol.Invalid(f"{value} does not end in either `.json` or `.pickle`")
|
||||||
|
|
||||||
|
|
||||||
def deprecated(key):
|
def deprecated(key: str) -> Callable[[dict], dict]:
|
||||||
"""Mark key as deprecated in configuration."""
|
"""Mark key as deprecated in configuration."""
|
||||||
|
|
||||||
def validator(config):
|
def validator(config: dict) -> dict:
|
||||||
"""Check if key is in config, log warning and remove key."""
|
"""Check if key is in config, log warning and remove key."""
|
||||||
if key not in config:
|
if key not in config:
|
||||||
return config
|
return config
|
||||||
@ -270,7 +271,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
def setup_mysensors_platform(
|
def setup_mysensors_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
domain: str, # hass platform name
|
domain: str, # hass platform name
|
||||||
discovery_info: dict[str, list[DevId]],
|
discovery_info: DiscoveryInfo,
|
||||||
device_class: type[MySensorsDevice] | dict[SensorType, type[MySensorsDevice]],
|
device_class: type[MySensorsDevice] | dict[SensorType, type[MySensorsDevice]],
|
||||||
device_args: (
|
device_args: (
|
||||||
None | tuple
|
None | tuple
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""Support for MySensors binary sensors."""
|
"""Support for MySensors binary sensors."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.components import mysensors
|
from homeassistant.components import mysensors
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
DEVICE_CLASS_MOISTURE,
|
DEVICE_CLASS_MOISTURE,
|
||||||
@ -17,6 +19,7 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .const import DiscoveryInfo
|
||||||
from .helpers import on_unload
|
from .helpers import on_unload
|
||||||
|
|
||||||
SENSORS = {
|
SENSORS = {
|
||||||
@ -35,11 +38,11 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
):
|
) -> None:
|
||||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_discover(discovery_info):
|
def async_discover(discovery_info: DiscoveryInfo) -> None:
|
||||||
"""Discover and add a MySensors binary_sensor."""
|
"""Discover and add a MySensors binary_sensor."""
|
||||||
mysensors.setup_mysensors_platform(
|
mysensors.setup_mysensors_platform(
|
||||||
hass,
|
hass,
|
||||||
@ -64,12 +67,12 @@ class MySensorsBinarySensor(mysensors.device.MySensorsEntity, BinarySensorEntity
|
|||||||
"""Representation of a MySensors Binary Sensor child node."""
|
"""Representation of a MySensors Binary Sensor child node."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self) -> bool:
|
||||||
"""Return True if the binary sensor is on."""
|
"""Return True if the binary sensor is on."""
|
||||||
return self._values.get(self.value_type) == STATE_ON
|
return self._values.get(self.value_type) == STATE_ON
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self) -> str | None:
|
||||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||||
pres = self.gateway.const.Presentation
|
pres = self.gateway.const.Presentation
|
||||||
device_class = SENSORS.get(pres(self.child_type).name)
|
device_class = SENSORS.get(pres(self.child_type).name)
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
"""MySensors platform that offers a Climate (MySensors-HVAC) component."""
|
"""MySensors platform that offers a Climate (MySensors-HVAC) component."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components import mysensors
|
from homeassistant.components import mysensors
|
||||||
from homeassistant.components.climate import ClimateEntity
|
from homeassistant.components.climate import ClimateEntity
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
@ -13,7 +17,7 @@ from homeassistant.components.climate.const import (
|
|||||||
SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||||
)
|
)
|
||||||
from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY
|
from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY, DiscoveryInfo
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -43,10 +47,10 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
):
|
) -> None:
|
||||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||||
|
|
||||||
async def async_discover(discovery_info):
|
async def async_discover(discovery_info: DiscoveryInfo) -> None:
|
||||||
"""Discover and add a MySensors climate."""
|
"""Discover and add a MySensors climate."""
|
||||||
mysensors.setup_mysensors_platform(
|
mysensors.setup_mysensors_platform(
|
||||||
hass,
|
hass,
|
||||||
@ -71,7 +75,7 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity):
|
|||||||
"""Representation of a MySensors HVAC."""
|
"""Representation of a MySensors HVAC."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self) -> int:
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
features = 0
|
features = 0
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
@ -87,22 +91,23 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity):
|
|||||||
return features
|
return features
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self) -> str:
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
return TEMP_CELSIUS if self.hass.config.units.is_metric else TEMP_FAHRENHEIT
|
return TEMP_CELSIUS if self.hass.config.units.is_metric else TEMP_FAHRENHEIT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self) -> float | None:
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
value = self._values.get(self.gateway.const.SetReq.V_TEMP)
|
value: str | None = self._values.get(self.gateway.const.SetReq.V_TEMP)
|
||||||
|
float_value: float | None = None
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
value = float(value)
|
float_value = float(value)
|
||||||
|
|
||||||
return value
|
return float_value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self) -> float | None:
|
||||||
"""Return the temperature we try to reach."""
|
"""Return the temperature we try to reach."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
if (
|
if (
|
||||||
@ -116,42 +121,46 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity):
|
|||||||
return float(temp) if temp is not None else None
|
return float(temp) if temp is not None else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_high(self):
|
def target_temperature_high(self) -> float | None:
|
||||||
"""Return the highbound target temperature we try to reach."""
|
"""Return the highbound target temperature we try to reach."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
if set_req.V_HVAC_SETPOINT_HEAT in self._values:
|
if set_req.V_HVAC_SETPOINT_HEAT in self._values:
|
||||||
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
|
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
|
||||||
return float(temp) if temp is not None else None
|
return float(temp) if temp is not None else None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature_low(self):
|
def target_temperature_low(self) -> float | None:
|
||||||
"""Return the lowbound target temperature we try to reach."""
|
"""Return the lowbound target temperature we try to reach."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
if set_req.V_HVAC_SETPOINT_COOL in self._values:
|
if set_req.V_HVAC_SETPOINT_COOL in self._values:
|
||||||
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
|
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
|
||||||
return float(temp) if temp is not None else None
|
return float(temp) if temp is not None else None
|
||||||
|
|
||||||
@property
|
return None
|
||||||
def hvac_mode(self):
|
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
|
||||||
return self._values.get(self.value_type)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_modes(self):
|
def hvac_mode(self) -> str:
|
||||||
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
|
return self._values.get(self.value_type, HVAC_MODE_HEAT)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_modes(self) -> list[str]:
|
||||||
"""List of available operation modes."""
|
"""List of available operation modes."""
|
||||||
return OPERATION_LIST
|
return OPERATION_LIST
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_mode(self):
|
def fan_mode(self) -> str | None:
|
||||||
"""Return the fan setting."""
|
"""Return the fan setting."""
|
||||||
return self._values.get(self.gateway.const.SetReq.V_HVAC_SPEED)
|
return self._values.get(self.gateway.const.SetReq.V_HVAC_SPEED)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_modes(self):
|
def fan_modes(self) -> list[str]:
|
||||||
"""List of available fan modes."""
|
"""List of available fan modes."""
|
||||||
return FAN_LIST
|
return FAN_LIST
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs):
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||||
@ -183,7 +192,7 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity):
|
|||||||
self._values[value_type] = value
|
self._values[value_type] = value
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_set_fan_mode(self, fan_mode):
|
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
self.gateway.set_child_value(
|
self.gateway.set_child_value(
|
||||||
@ -194,7 +203,7 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity):
|
|||||||
self._values[set_req.V_HVAC_SPEED] = fan_mode
|
self._values[set_req.V_HVAC_SPEED] = fan_mode
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode):
|
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
self.gateway.set_child_value(
|
self.gateway.set_child_value(
|
||||||
self.node_id,
|
self.node_id,
|
||||||
@ -208,7 +217,7 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity):
|
|||||||
self._values[self.value_type] = hvac_mode
|
self._values[self.value_type] = hvac_mode
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self) -> None:
|
||||||
"""Update the controller with the latest value from a sensor."""
|
"""Update the controller with the latest value from a sensor."""
|
||||||
await super().async_update()
|
await super().async_update()
|
||||||
self._values[self.value_type] = DICT_MYS_TO_HA[self._values[self.value_type]]
|
self._values[self.value_type] = DICT_MYS_TO_HA[self._values[self.value_type]]
|
||||||
|
@ -82,8 +82,8 @@ def _validate_version(version: str) -> dict[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
def _is_same_device(
|
def _is_same_device(
|
||||||
gw_type: ConfGatewayType, user_input: dict[str, str], entry: ConfigEntry
|
gw_type: ConfGatewayType, user_input: dict[str, Any], entry: ConfigEntry
|
||||||
):
|
) -> bool:
|
||||||
"""Check if another ConfigDevice is actually the same as user_input.
|
"""Check if another ConfigDevice is actually the same as user_input.
|
||||||
|
|
||||||
This function only compares addresses and tcp ports, so it is possible to fool it with tricks like port forwarding.
|
This function only compares addresses and tcp ports, so it is possible to fool it with tricks like port forwarding.
|
||||||
@ -91,7 +91,9 @@ def _is_same_device(
|
|||||||
if entry.data[CONF_DEVICE] != user_input[CONF_DEVICE]:
|
if entry.data[CONF_DEVICE] != user_input[CONF_DEVICE]:
|
||||||
return False
|
return False
|
||||||
if gw_type == CONF_GATEWAY_TYPE_TCP:
|
if gw_type == CONF_GATEWAY_TYPE_TCP:
|
||||||
return entry.data[CONF_TCP_PORT] == user_input[CONF_TCP_PORT]
|
entry_tcp_port: int = entry.data[CONF_TCP_PORT]
|
||||||
|
input_tcp_port: int = user_input[CONF_TCP_PORT]
|
||||||
|
return entry_tcp_port == input_tcp_port
|
||||||
if gw_type == CONF_GATEWAY_TYPE_MQTT:
|
if gw_type == CONF_GATEWAY_TYPE_MQTT:
|
||||||
entry_topics = {
|
entry_topics = {
|
||||||
entry.data[CONF_TOPIC_IN_PREFIX],
|
entry.data[CONF_TOPIC_IN_PREFIX],
|
||||||
|
@ -2,23 +2,23 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Literal, Tuple
|
from typing import Final, Literal, Tuple, TypedDict
|
||||||
|
|
||||||
ATTR_DEVICES: str = "devices"
|
ATTR_DEVICES: Final = "devices"
|
||||||
ATTR_GATEWAY_ID: str = "gateway_id"
|
ATTR_GATEWAY_ID: Final = "gateway_id"
|
||||||
|
|
||||||
CONF_BAUD_RATE: str = "baud_rate"
|
CONF_BAUD_RATE: Final = "baud_rate"
|
||||||
CONF_DEVICE: str = "device"
|
CONF_DEVICE: Final = "device"
|
||||||
CONF_GATEWAYS: str = "gateways"
|
CONF_GATEWAYS: Final = "gateways"
|
||||||
CONF_NODES: str = "nodes"
|
CONF_NODES: Final = "nodes"
|
||||||
CONF_PERSISTENCE: str = "persistence"
|
CONF_PERSISTENCE: Final = "persistence"
|
||||||
CONF_PERSISTENCE_FILE: str = "persistence_file"
|
CONF_PERSISTENCE_FILE: Final = "persistence_file"
|
||||||
CONF_RETAIN: str = "retain"
|
CONF_RETAIN: Final = "retain"
|
||||||
CONF_TCP_PORT: str = "tcp_port"
|
CONF_TCP_PORT: Final = "tcp_port"
|
||||||
CONF_TOPIC_IN_PREFIX: str = "topic_in_prefix"
|
CONF_TOPIC_IN_PREFIX: Final = "topic_in_prefix"
|
||||||
CONF_TOPIC_OUT_PREFIX: str = "topic_out_prefix"
|
CONF_TOPIC_OUT_PREFIX: Final = "topic_out_prefix"
|
||||||
CONF_VERSION: str = "version"
|
CONF_VERSION: Final = "version"
|
||||||
CONF_GATEWAY_TYPE: str = "gateway_type"
|
CONF_GATEWAY_TYPE: Final = "gateway_type"
|
||||||
ConfGatewayType = Literal["Serial", "TCP", "MQTT"]
|
ConfGatewayType = Literal["Serial", "TCP", "MQTT"]
|
||||||
CONF_GATEWAY_TYPE_SERIAL: ConfGatewayType = "Serial"
|
CONF_GATEWAY_TYPE_SERIAL: ConfGatewayType = "Serial"
|
||||||
CONF_GATEWAY_TYPE_TCP: ConfGatewayType = "TCP"
|
CONF_GATEWAY_TYPE_TCP: ConfGatewayType = "TCP"
|
||||||
@ -29,19 +29,28 @@ CONF_GATEWAY_TYPE_ALL: list[str] = [
|
|||||||
CONF_GATEWAY_TYPE_TCP,
|
CONF_GATEWAY_TYPE_TCP,
|
||||||
]
|
]
|
||||||
|
|
||||||
DOMAIN: str = "mysensors"
|
DOMAIN: Final = "mysensors"
|
||||||
MYSENSORS_GATEWAY_START_TASK: str = "mysensors_gateway_start_task_{}"
|
MYSENSORS_GATEWAY_START_TASK: str = "mysensors_gateway_start_task_{}"
|
||||||
MYSENSORS_GATEWAYS: str = "mysensors_gateways"
|
MYSENSORS_GATEWAYS: Final = "mysensors_gateways"
|
||||||
PLATFORM: str = "platform"
|
PLATFORM: Final = "platform"
|
||||||
SCHEMA: str = "schema"
|
SCHEMA: Final = "schema"
|
||||||
CHILD_CALLBACK: str = "mysensors_child_callback_{}_{}_{}_{}"
|
CHILD_CALLBACK: str = "mysensors_child_callback_{}_{}_{}_{}"
|
||||||
NODE_CALLBACK: str = "mysensors_node_callback_{}_{}"
|
NODE_CALLBACK: str = "mysensors_node_callback_{}_{}"
|
||||||
MYSENSORS_DISCOVERY = "mysensors_discovery_{}_{}"
|
MYSENSORS_DISCOVERY: str = "mysensors_discovery_{}_{}"
|
||||||
MYSENSORS_ON_UNLOAD = "mysensors_on_unload_{}"
|
MYSENSORS_ON_UNLOAD: str = "mysensors_on_unload_{}"
|
||||||
TYPE: str = "type"
|
TYPE: Final = "type"
|
||||||
UPDATE_DELAY: float = 0.1
|
UPDATE_DELAY: float = 0.1
|
||||||
|
|
||||||
SERVICE_SEND_IR_CODE: str = "send_ir_code"
|
|
||||||
|
class DiscoveryInfo(TypedDict):
|
||||||
|
"""Represent the discovery info type for mysensors platforms."""
|
||||||
|
|
||||||
|
devices: list[DevId]
|
||||||
|
name: str # CONF_NAME is used in the notify base integration.
|
||||||
|
gateway_id: GatewayId
|
||||||
|
|
||||||
|
|
||||||
|
SERVICE_SEND_IR_CODE: Final = "send_ir_code"
|
||||||
|
|
||||||
SensorType = str
|
SensorType = str
|
||||||
# S_DOOR, S_MOTION, S_SMOKE, ...
|
# S_DOOR, S_MOTION, S_SMOKE, ...
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
"""Support for MySensors covers."""
|
"""Support for MySensors covers."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum, unique
|
from enum import Enum, unique
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components import mysensors
|
from homeassistant.components import mysensors
|
||||||
from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverEntity
|
from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverEntity
|
||||||
from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY
|
from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY, DiscoveryInfo
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON
|
from homeassistant.const import STATE_OFF, STATE_ON
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -30,10 +33,10 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
):
|
) -> None:
|
||||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||||
|
|
||||||
async def async_discover(discovery_info):
|
async def async_discover(discovery_info: DiscoveryInfo) -> None:
|
||||||
"""Discover and add a MySensors cover."""
|
"""Discover and add a MySensors cover."""
|
||||||
mysensors.setup_mysensors_platform(
|
mysensors.setup_mysensors_platform(
|
||||||
hass,
|
hass,
|
||||||
@ -57,7 +60,7 @@ async def async_setup_entry(
|
|||||||
class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity):
|
class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity):
|
||||||
"""Representation of the value of a MySensors Cover child node."""
|
"""Representation of the value of a MySensors Cover child node."""
|
||||||
|
|
||||||
def get_cover_state(self):
|
def get_cover_state(self) -> CoverState:
|
||||||
"""Return a CoverState enum representing the state of the cover."""
|
"""Return a CoverState enum representing the state of the cover."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
v_up = self._values.get(set_req.V_UP) == STATE_ON
|
v_up = self._values.get(set_req.V_UP) == STATE_ON
|
||||||
@ -69,7 +72,7 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity):
|
|||||||
# or V_STATUS.
|
# or V_STATUS.
|
||||||
amount = 100
|
amount = 100
|
||||||
if set_req.V_DIMMER in self._values:
|
if set_req.V_DIMMER in self._values:
|
||||||
amount = self._values.get(set_req.V_DIMMER)
|
amount = self._values[set_req.V_DIMMER]
|
||||||
else:
|
else:
|
||||||
amount = 100 if self._values.get(set_req.V_LIGHT) == STATE_ON else 0
|
amount = 100 if self._values.get(set_req.V_LIGHT) == STATE_ON else 0
|
||||||
|
|
||||||
@ -82,22 +85,22 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity):
|
|||||||
return CoverState.OPEN
|
return CoverState.OPEN
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closed(self):
|
def is_closed(self) -> bool:
|
||||||
"""Return True if the cover is closed."""
|
"""Return True if the cover is closed."""
|
||||||
return self.get_cover_state() == CoverState.CLOSED
|
return self.get_cover_state() == CoverState.CLOSED
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_closing(self):
|
def is_closing(self) -> bool:
|
||||||
"""Return True if the cover is closing."""
|
"""Return True if the cover is closing."""
|
||||||
return self.get_cover_state() == CoverState.CLOSING
|
return self.get_cover_state() == CoverState.CLOSING
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_opening(self):
|
def is_opening(self) -> bool:
|
||||||
"""Return True if the cover is opening."""
|
"""Return True if the cover is opening."""
|
||||||
return self.get_cover_state() == CoverState.OPENING
|
return self.get_cover_state() == CoverState.OPENING
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_cover_position(self):
|
def current_cover_position(self) -> int | None:
|
||||||
"""Return current position of cover.
|
"""Return current position of cover.
|
||||||
|
|
||||||
None is unknown, 0 is closed, 100 is fully open.
|
None is unknown, 0 is closed, 100 is fully open.
|
||||||
@ -105,7 +108,7 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity):
|
|||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
return self._values.get(set_req.V_DIMMER)
|
return self._values.get(set_req.V_DIMMER)
|
||||||
|
|
||||||
async def async_open_cover(self, **kwargs):
|
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||||
"""Move the cover up."""
|
"""Move the cover up."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
self.gateway.set_child_value(
|
self.gateway.set_child_value(
|
||||||
@ -119,7 +122,7 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity):
|
|||||||
self._values[set_req.V_LIGHT] = STATE_ON
|
self._values[set_req.V_LIGHT] = STATE_ON
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_close_cover(self, **kwargs):
|
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||||
"""Move the cover down."""
|
"""Move the cover down."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
self.gateway.set_child_value(
|
self.gateway.set_child_value(
|
||||||
@ -133,7 +136,7 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity):
|
|||||||
self._values[set_req.V_LIGHT] = STATE_OFF
|
self._values[set_req.V_LIGHT] = STATE_OFF
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_set_cover_position(self, **kwargs):
|
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
||||||
"""Move the cover to a specific position."""
|
"""Move the cover to a specific position."""
|
||||||
position = kwargs.get(ATTR_POSITION)
|
position = kwargs.get(ATTR_POSITION)
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
@ -145,7 +148,7 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity):
|
|||||||
self._values[set_req.V_DIMMER] = position
|
self._values[set_req.V_DIMMER] = position
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_stop_cover(self, **kwargs):
|
async def async_stop_cover(self, **kwargs: Any) -> None:
|
||||||
"""Stop the device."""
|
"""Stop the device."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
self.gateway.set_child_value(
|
self.gateway.set_child_value(
|
||||||
|
@ -66,10 +66,10 @@ class MySensorsDevice:
|
|||||||
return self.gateway_id, self.node_id, self.child_id, self.value_type
|
return self.gateway_id, self.node_id, self.child_id, self.value_type
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _logger(self):
|
def _logger(self) -> logging.Logger:
|
||||||
return logging.getLogger(f"{__name__}.{self.name}")
|
return logging.getLogger(f"{__name__}.{self.name}")
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self):
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
"""Remove this entity from home assistant."""
|
"""Remove this entity from home assistant."""
|
||||||
for platform in PLATFORM_TYPES:
|
for platform in PLATFORM_TYPES:
|
||||||
platform_str = MYSENSORS_PLATFORM_DEVICES.format(platform)
|
platform_str = MYSENSORS_PLATFORM_DEVICES.format(platform)
|
||||||
@ -91,17 +91,26 @@ class MySensorsDevice:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def sketch_name(self) -> str:
|
def sketch_name(self) -> str:
|
||||||
"""Return the name of the sketch running on the whole node (will be the same for several entities!)."""
|
"""Return the name of the sketch running on the whole node.
|
||||||
return self._node.sketch_name
|
|
||||||
|
The name will be the same for several entities.
|
||||||
|
"""
|
||||||
|
return self._node.sketch_name # type: ignore[no-any-return]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sketch_version(self) -> str:
|
def sketch_version(self) -> str:
|
||||||
"""Return the version of the sketch running on the whole node (will be the same for several entities!)."""
|
"""Return the version of the sketch running on the whole node.
|
||||||
return self._node.sketch_version
|
|
||||||
|
The name will be the same for several entities.
|
||||||
|
"""
|
||||||
|
return self._node.sketch_version # type: ignore[no-any-return]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def node_name(self) -> str:
|
def node_name(self) -> str:
|
||||||
"""Name of the whole node (will be the same for several entities!)."""
|
"""Name of the whole node.
|
||||||
|
|
||||||
|
The name will be the same for several entities.
|
||||||
|
"""
|
||||||
return f"{self.sketch_name} {self.node_id}"
|
return f"{self.sketch_name} {self.node_id}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -111,7 +120,7 @@ class MySensorsDevice:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return a dict that allows home assistant to puzzle all entities belonging to a node together."""
|
"""Return the device info."""
|
||||||
return {
|
return {
|
||||||
"identifiers": {(DOMAIN, f"{self.gateway_id}-{self.node_id}")},
|
"identifiers": {(DOMAIN, f"{self.gateway_id}-{self.node_id}")},
|
||||||
"name": self.node_name,
|
"name": self.node_name,
|
||||||
@ -120,13 +129,13 @@ class MySensorsDevice:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
"""Return the name of this entity."""
|
"""Return the name of this entity."""
|
||||||
return f"{self.node_name} {self.child_id}"
|
return f"{self.node_name} {self.child_id}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def _extra_attributes(self) -> dict[str, Any]:
|
||||||
"""Return device specific state attributes."""
|
"""Return device specific attributes."""
|
||||||
node = self.gateway.sensors[self.node_id]
|
node = self.gateway.sensors[self.node_id]
|
||||||
child = node.children[self.child_id]
|
child = node.children[self.child_id]
|
||||||
attr = {
|
attr = {
|
||||||
@ -136,10 +145,6 @@ class MySensorsDevice:
|
|||||||
ATTR_DESCRIPTION: child.description,
|
ATTR_DESCRIPTION: child.description,
|
||||||
ATTR_NODE_ID: self.node_id,
|
ATTR_NODE_ID: self.node_id,
|
||||||
}
|
}
|
||||||
# This works when we are actually an Entity (i.e. all platforms except device_tracker)
|
|
||||||
if hasattr(self, "platform"):
|
|
||||||
# pylint: disable=no-member
|
|
||||||
attr[ATTR_DEVICE] = self.platform.config_entry.data[CONF_DEVICE]
|
|
||||||
|
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
|
|
||||||
@ -148,7 +153,7 @@ class MySensorsDevice:
|
|||||||
|
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self) -> None:
|
||||||
"""Update the controller with the latest value from a sensor."""
|
"""Update the controller with the latest value from a sensor."""
|
||||||
node = self.gateway.sensors[self.node_id]
|
node = self.gateway.sensors[self.node_id]
|
||||||
child = node.children[self.child_id]
|
child = node.children[self.child_id]
|
||||||
@ -175,17 +180,17 @@ class MySensorsDevice:
|
|||||||
else:
|
else:
|
||||||
self._values[value_type] = value
|
self._values[value_type] = value
|
||||||
|
|
||||||
async def _async_update_callback(self):
|
async def _async_update_callback(self) -> None:
|
||||||
"""Update the device."""
|
"""Update the device."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_callback(self):
|
def async_update_callback(self) -> None:
|
||||||
"""Update the device after delay."""
|
"""Update the device after delay."""
|
||||||
if self._update_scheduled:
|
if self._update_scheduled:
|
||||||
return
|
return
|
||||||
|
|
||||||
async def update():
|
async def update() -> None:
|
||||||
"""Perform update."""
|
"""Perform update."""
|
||||||
try:
|
try:
|
||||||
await self._async_update_callback()
|
await self._async_update_callback()
|
||||||
@ -199,31 +204,47 @@ class MySensorsDevice:
|
|||||||
self.hass.loop.call_later(UPDATE_DELAY, delayed_update)
|
self.hass.loop.call_later(UPDATE_DELAY, delayed_update)
|
||||||
|
|
||||||
|
|
||||||
def get_mysensors_devices(hass, domain: str) -> dict[DevId, MySensorsDevice]:
|
def get_mysensors_devices(
|
||||||
|
hass: HomeAssistant, 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[DOMAIN]:
|
if MYSENSORS_PLATFORM_DEVICES.format(domain) not in hass.data[DOMAIN]:
|
||||||
hass.data[DOMAIN][MYSENSORS_PLATFORM_DEVICES.format(domain)] = {}
|
hass.data[DOMAIN][MYSENSORS_PLATFORM_DEVICES.format(domain)] = {}
|
||||||
return hass.data[DOMAIN][MYSENSORS_PLATFORM_DEVICES.format(domain)]
|
devices: dict[DevId, MySensorsDevice] = hass.data[DOMAIN][
|
||||||
|
MYSENSORS_PLATFORM_DEVICES.format(domain)
|
||||||
|
]
|
||||||
|
return devices
|
||||||
|
|
||||||
|
|
||||||
class MySensorsEntity(MySensorsDevice, Entity):
|
class MySensorsEntity(MySensorsDevice, Entity):
|
||||||
"""Representation of a MySensors entity."""
|
"""Representation of a MySensors entity."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self) -> bool:
|
||||||
"""Return the polling state. The gateway pushes its states."""
|
"""Return the polling state. The gateway pushes its states."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self) -> bool:
|
||||||
"""Return true if entity is available."""
|
"""Return true if entity is available."""
|
||||||
return self.value_type in self._values
|
return self.value_type in self._values
|
||||||
|
|
||||||
async def _async_update_callback(self):
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Return entity specific state attributes."""
|
||||||
|
attr = self._extra_attributes
|
||||||
|
|
||||||
|
assert self.platform
|
||||||
|
assert self.platform.config_entry
|
||||||
|
attr[ATTR_DEVICE] = self.platform.config_entry.data[CONF_DEVICE]
|
||||||
|
|
||||||
|
return attr
|
||||||
|
|
||||||
|
async def _async_update_callback(self) -> None:
|
||||||
"""Update the entity."""
|
"""Update the entity."""
|
||||||
await self.async_update_ha_state(True)
|
await self.async_update_ha_state(True)
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Register update callback."""
|
"""Register update callback."""
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
"""Support for tracking MySensors devices."""
|
"""Support for tracking MySensors devices."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
from homeassistant.components import mysensors
|
from homeassistant.components import mysensors
|
||||||
from homeassistant.components.device_tracker import DOMAIN
|
from homeassistant.components.device_tracker import DOMAIN
|
||||||
from homeassistant.components.mysensors import DevId
|
from homeassistant.components.mysensors import DevId
|
||||||
from homeassistant.components.mysensors.const import ATTR_GATEWAY_ID, GatewayId
|
from homeassistant.components.mysensors.const import (
|
||||||
|
ATTR_GATEWAY_ID,
|
||||||
|
DiscoveryInfo,
|
||||||
|
GatewayId,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
@ -11,8 +19,11 @@ from .helpers import on_unload
|
|||||||
|
|
||||||
|
|
||||||
async def async_setup_scanner(
|
async def async_setup_scanner(
|
||||||
hass: HomeAssistant, config, async_see, discovery_info=None
|
hass: HomeAssistant,
|
||||||
):
|
config: dict[str, Any],
|
||||||
|
async_see: Callable,
|
||||||
|
discovery_info: DiscoveryInfo | None = None,
|
||||||
|
) -> bool:
|
||||||
"""Set up the MySensors device scanner."""
|
"""Set up the MySensors device scanner."""
|
||||||
if not discovery_info:
|
if not discovery_info:
|
||||||
return False
|
return False
|
||||||
@ -55,13 +66,13 @@ async def async_setup_scanner(
|
|||||||
class MySensorsDeviceScanner(mysensors.device.MySensorsDevice):
|
class MySensorsDeviceScanner(mysensors.device.MySensorsDevice):
|
||||||
"""Represent a MySensors scanner."""
|
"""Represent a MySensors scanner."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, async_see, *args):
|
def __init__(self, hass: HomeAssistant, async_see: Callable, *args: Any) -> None:
|
||||||
"""Set up instance."""
|
"""Set up instance."""
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
self.async_see = async_see
|
self.async_see = async_see
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
|
|
||||||
async def _async_update_callback(self):
|
async def _async_update_callback(self) -> None:
|
||||||
"""Update the device."""
|
"""Update the device."""
|
||||||
await self.async_update()
|
await self.async_update()
|
||||||
node = self.gateway.sensors[self.node_id]
|
node = self.gateway.sensors[self.node_id]
|
||||||
@ -74,5 +85,5 @@ class MySensorsDeviceScanner(mysensors.device.MySensorsDevice):
|
|||||||
host_name=self.name,
|
host_name=self.name,
|
||||||
gps=(latitude, longitude),
|
gps=(latitude, longitude),
|
||||||
battery=node.battery_level,
|
battery=node.battery_level,
|
||||||
attributes=self.extra_state_attributes,
|
attributes=self._extra_attributes,
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,10 @@ from mysensors import BaseAsyncGateway, Message, Sensor, mysensors
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
|
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
|
||||||
|
from homeassistant.components.mqtt.models import (
|
||||||
|
Message as MQTTMessage,
|
||||||
|
PublishPayloadType,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import Event, HomeAssistant, callback
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
@ -51,7 +55,7 @@ GATEWAY_READY_TIMEOUT = 20.0
|
|||||||
MQTT_COMPONENT = "mqtt"
|
MQTT_COMPONENT = "mqtt"
|
||||||
|
|
||||||
|
|
||||||
def is_serial_port(value):
|
def is_serial_port(value: str) -> str:
|
||||||
"""Validate that value is a windows serial port or a unix device."""
|
"""Validate that value is a windows serial port or a unix device."""
|
||||||
if sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
ports = (f"COM{idx + 1}" for idx in range(256))
|
ports = (f"COM{idx + 1}" for idx in range(256))
|
||||||
@ -61,7 +65,7 @@ def is_serial_port(value):
|
|||||||
return cv.isdevice(value)
|
return cv.isdevice(value)
|
||||||
|
|
||||||
|
|
||||||
def is_socket_address(value):
|
def is_socket_address(value: str) -> str:
|
||||||
"""Validate that value is a valid address."""
|
"""Validate that value is a valid address."""
|
||||||
try:
|
try:
|
||||||
socket.getaddrinfo(value, None)
|
socket.getaddrinfo(value, None)
|
||||||
@ -179,15 +183,17 @@ async def _get_gateway(
|
|||||||
return None
|
return None
|
||||||
mqtt = hass.components.mqtt
|
mqtt = hass.components.mqtt
|
||||||
|
|
||||||
def pub_callback(topic, payload, qos, retain):
|
def pub_callback(topic: str, payload: str, qos: int, retain: bool) -> None:
|
||||||
"""Call MQTT publish function."""
|
"""Call MQTT publish function."""
|
||||||
mqtt.async_publish(topic, payload, qos, retain)
|
mqtt.async_publish(topic, payload, qos, retain)
|
||||||
|
|
||||||
def sub_callback(topic, sub_cb, qos):
|
def sub_callback(
|
||||||
|
topic: str, sub_cb: Callable[[str, PublishPayloadType, int], None], qos: int
|
||||||
|
) -> None:
|
||||||
"""Call MQTT subscribe function."""
|
"""Call MQTT subscribe function."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def internal_callback(msg):
|
def internal_callback(msg: MQTTMessage) -> None:
|
||||||
"""Call callback."""
|
"""Call callback."""
|
||||||
sub_cb(msg.topic, msg.payload, msg.qos)
|
sub_cb(msg.topic, msg.payload, msg.qos)
|
||||||
|
|
||||||
@ -234,7 +240,7 @@ async def _get_gateway(
|
|||||||
|
|
||||||
async def finish_setup(
|
async def finish_setup(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway
|
hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway
|
||||||
):
|
) -> None:
|
||||||
"""Load any persistent devices and platforms and start gateway."""
|
"""Load any persistent devices and platforms and start gateway."""
|
||||||
discover_tasks = []
|
discover_tasks = []
|
||||||
start_tasks = []
|
start_tasks = []
|
||||||
@ -249,7 +255,7 @@ async def finish_setup(
|
|||||||
|
|
||||||
async def _discover_persistent_devices(
|
async def _discover_persistent_devices(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway
|
hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway
|
||||||
):
|
) -> None:
|
||||||
"""Discover platforms for devices loaded via persistence file."""
|
"""Discover platforms for devices loaded via persistence file."""
|
||||||
new_devices = defaultdict(list)
|
new_devices = defaultdict(list)
|
||||||
for node_id in gateway.sensors:
|
for node_id in gateway.sensors:
|
||||||
@ -265,7 +271,9 @@ async def _discover_persistent_devices(
|
|||||||
discover_mysensors_platform(hass, entry.entry_id, platform, dev_ids)
|
discover_mysensors_platform(hass, entry.entry_id, platform, dev_ids)
|
||||||
|
|
||||||
|
|
||||||
async def gw_stop(hass, entry: ConfigEntry, gateway: BaseAsyncGateway):
|
async def gw_stop(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway
|
||||||
|
) -> None:
|
||||||
"""Stop the gateway."""
|
"""Stop the gateway."""
|
||||||
connect_task = hass.data[DOMAIN].pop(
|
connect_task = hass.data[DOMAIN].pop(
|
||||||
MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id), None
|
MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id), None
|
||||||
@ -275,11 +283,14 @@ async def gw_stop(hass, entry: ConfigEntry, gateway: BaseAsyncGateway):
|
|||||||
await gateway.stop()
|
await gateway.stop()
|
||||||
|
|
||||||
|
|
||||||
async def _gw_start(hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway):
|
async def _gw_start(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncGateway
|
||||||
|
) -> None:
|
||||||
"""Start the gateway."""
|
"""Start the gateway."""
|
||||||
gateway_ready = asyncio.Event()
|
gateway_ready = asyncio.Event()
|
||||||
|
|
||||||
def gateway_connected(_: BaseAsyncGateway):
|
def gateway_connected(_: BaseAsyncGateway) -> None:
|
||||||
|
"""Handle gateway connected."""
|
||||||
gateway_ready.set()
|
gateway_ready.set()
|
||||||
|
|
||||||
gateway.on_conn_made = gateway_connected
|
gateway.on_conn_made = gateway_connected
|
||||||
@ -290,7 +301,8 @@ async def _gw_start(hass: HomeAssistant, entry: ConfigEntry, gateway: BaseAsyncG
|
|||||||
gateway.start()
|
gateway.start()
|
||||||
) # store the connect task so it can be cancelled in gw_stop
|
) # store the connect task so it can be cancelled in gw_stop
|
||||||
|
|
||||||
async def stop_this_gw(_: Event):
|
async def stop_this_gw(_: Event) -> None:
|
||||||
|
"""Stop the gateway."""
|
||||||
await gw_stop(hass, entry, gateway)
|
await gw_stop(hass, entry, gateway)
|
||||||
|
|
||||||
on_unload(
|
on_unload(
|
||||||
@ -319,7 +331,7 @@ def _gw_callback_factory(
|
|||||||
"""Return a new callback for the gateway."""
|
"""Return a new callback for the gateway."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def mysensors_callback(msg: Message):
|
def mysensors_callback(msg: Message) -> None:
|
||||||
"""Handle messages from a MySensors gateway.
|
"""Handle messages from a MySensors gateway.
|
||||||
|
|
||||||
All MySenors messages are received here.
|
All MySenors messages are received here.
|
||||||
|
@ -68,7 +68,7 @@ async def handle_sketch_version(
|
|||||||
@callback
|
@callback
|
||||||
def _handle_child_update(
|
def _handle_child_update(
|
||||||
hass: HomeAssistant, gateway_id: GatewayId, validated: dict[str, list[DevId]]
|
hass: HomeAssistant, gateway_id: GatewayId, validated: dict[str, list[DevId]]
|
||||||
):
|
) -> None:
|
||||||
"""Handle a child update."""
|
"""Handle a child update."""
|
||||||
signals: list[str] = []
|
signals: list[str] = []
|
||||||
|
|
||||||
@ -91,7 +91,9 @@ def _handle_child_update(
|
|||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_node_update(hass: HomeAssistant, gateway_id: GatewayId, msg: Message):
|
def _handle_node_update(
|
||||||
|
hass: HomeAssistant, gateway_id: GatewayId, msg: Message
|
||||||
|
) -> None:
|
||||||
"""Handle a node update."""
|
"""Handle a node update."""
|
||||||
signal = NODE_CALLBACK.format(gateway_id, msg.node_id)
|
signal = NODE_CALLBACK.format(gateway_id, msg.node_id)
|
||||||
async_dispatcher_send(hass, signal)
|
async_dispatcher_send(hass, signal)
|
||||||
|
@ -117,7 +117,10 @@ def switch_ir_send_schema(
|
|||||||
|
|
||||||
|
|
||||||
def get_child_schema(
|
def get_child_schema(
|
||||||
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType, schema
|
gateway: BaseAsyncGateway,
|
||||||
|
child: ChildSensor,
|
||||||
|
value_type_name: ValueType,
|
||||||
|
schema: dict,
|
||||||
) -> vol.Schema:
|
) -> vol.Schema:
|
||||||
"""Return a child schema."""
|
"""Return a child schema."""
|
||||||
set_req = gateway.const.SetReq
|
set_req = gateway.const.SetReq
|
||||||
@ -136,7 +139,7 @@ def get_child_schema(
|
|||||||
|
|
||||||
def invalid_msg(
|
def invalid_msg(
|
||||||
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
|
gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType
|
||||||
):
|
) -> str:
|
||||||
"""Return a message for an invalid child during schema validation."""
|
"""Return a message for an invalid child during schema validation."""
|
||||||
pres = gateway.const.Presentation
|
pres = gateway.const.Presentation
|
||||||
set_req = gateway.const.SetReq
|
set_req = gateway.const.SetReq
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
"""Support for MySensors lights."""
|
"""Support for MySensors lights."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components import mysensors
|
from homeassistant.components import mysensors
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
@ -10,7 +14,6 @@ from homeassistant.components.light import (
|
|||||||
SUPPORT_WHITE_VALUE,
|
SUPPORT_WHITE_VALUE,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON
|
from homeassistant.const import STATE_OFF, STATE_ON
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
@ -19,6 +22,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
from homeassistant.util.color import rgb_hex_to_rgb_list
|
from homeassistant.util.color import rgb_hex_to_rgb_list
|
||||||
|
|
||||||
|
from .const import MYSENSORS_DISCOVERY, DiscoveryInfo, SensorType
|
||||||
|
from .device import MySensorsDevice
|
||||||
from .helpers import on_unload
|
from .helpers import on_unload
|
||||||
|
|
||||||
SUPPORT_MYSENSORS_RGBW = SUPPORT_COLOR | SUPPORT_WHITE_VALUE
|
SUPPORT_MYSENSORS_RGBW = SUPPORT_COLOR | SUPPORT_WHITE_VALUE
|
||||||
@ -28,15 +33,15 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
):
|
) -> None:
|
||||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||||
device_class_map = {
|
device_class_map: dict[SensorType, type[MySensorsDevice]] = {
|
||||||
"S_DIMMER": MySensorsLightDimmer,
|
"S_DIMMER": MySensorsLightDimmer,
|
||||||
"S_RGB_LIGHT": MySensorsLightRGB,
|
"S_RGB_LIGHT": MySensorsLightRGB,
|
||||||
"S_RGBW_LIGHT": MySensorsLightRGBW,
|
"S_RGBW_LIGHT": MySensorsLightRGBW,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def async_discover(discovery_info):
|
async def async_discover(discovery_info: DiscoveryInfo) -> None:
|
||||||
"""Discover and add a MySensors light."""
|
"""Discover and add a MySensors light."""
|
||||||
mysensors.setup_mysensors_platform(
|
mysensors.setup_mysensors_platform(
|
||||||
hass,
|
hass,
|
||||||
@ -60,35 +65,35 @@ async def async_setup_entry(
|
|||||||
class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity):
|
class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity):
|
||||||
"""Representation of a MySensors Light child node."""
|
"""Representation of a MySensors Light child node."""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args: Any) -> None:
|
||||||
"""Initialize a MySensors Light."""
|
"""Initialize a MySensors Light."""
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
self._state = None
|
self._state: bool | None = None
|
||||||
self._brightness = None
|
self._brightness: int | None = None
|
||||||
self._hs = None
|
self._hs: tuple[int, int] | None = None
|
||||||
self._white = None
|
self._white: int | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self) -> int | None:
|
||||||
"""Return the brightness of this light between 0..255."""
|
"""Return the brightness of this light between 0..255."""
|
||||||
return self._brightness
|
return self._brightness
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hs_color(self):
|
def hs_color(self) -> tuple[int, int] | None:
|
||||||
"""Return the hs color value [int, int]."""
|
"""Return the hs color value [int, int]."""
|
||||||
return self._hs
|
return self._hs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def white_value(self):
|
def white_value(self) -> int | None:
|
||||||
"""Return the white value of this light between 0..255."""
|
"""Return the white value of this light between 0..255."""
|
||||||
return self._white
|
return self._white
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self) -> bool:
|
||||||
"""Return true if device is on."""
|
"""Return true if device is on."""
|
||||||
return self._state
|
return bool(self._state)
|
||||||
|
|
||||||
def _turn_on_light(self):
|
def _turn_on_light(self) -> None:
|
||||||
"""Turn on light child device."""
|
"""Turn on light child device."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
|
|
||||||
@ -103,10 +108,9 @@ class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity):
|
|||||||
self._state = True
|
self._state = True
|
||||||
self._values[set_req.V_LIGHT] = STATE_ON
|
self._values[set_req.V_LIGHT] = STATE_ON
|
||||||
|
|
||||||
def _turn_on_dimmer(self, **kwargs):
|
def _turn_on_dimmer(self, **kwargs: Any) -> None:
|
||||||
"""Turn on dimmer child device."""
|
"""Turn on dimmer child device."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
brightness = self._brightness
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ATTR_BRIGHTNESS not in kwargs
|
ATTR_BRIGHTNESS not in kwargs
|
||||||
@ -114,7 +118,7 @@ class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity):
|
|||||||
or set_req.V_DIMMER not in self._values
|
or set_req.V_DIMMER not in self._values
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
brightness = kwargs[ATTR_BRIGHTNESS]
|
brightness: int = kwargs[ATTR_BRIGHTNESS]
|
||||||
percent = round(100 * brightness / 255)
|
percent = round(100 * brightness / 255)
|
||||||
self.gateway.set_child_value(
|
self.gateway.set_child_value(
|
||||||
self.node_id, self.child_id, set_req.V_DIMMER, percent, ack=1
|
self.node_id, self.child_id, set_req.V_DIMMER, percent, ack=1
|
||||||
@ -125,17 +129,20 @@ class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity):
|
|||||||
self._brightness = brightness
|
self._brightness = brightness
|
||||||
self._values[set_req.V_DIMMER] = percent
|
self._values[set_req.V_DIMMER] = percent
|
||||||
|
|
||||||
def _turn_on_rgb_and_w(self, hex_template, **kwargs):
|
def _turn_on_rgb_and_w(self, hex_template: str, **kwargs: Any) -> None:
|
||||||
"""Turn on RGB or RGBW child device."""
|
"""Turn on RGB or RGBW child device."""
|
||||||
|
assert self._hs
|
||||||
|
assert self._white is not None
|
||||||
rgb = list(color_util.color_hs_to_RGB(*self._hs))
|
rgb = list(color_util.color_hs_to_RGB(*self._hs))
|
||||||
white = self._white
|
white = self._white
|
||||||
hex_color = self._values.get(self.value_type)
|
hex_color = self._values.get(self.value_type)
|
||||||
hs_color = kwargs.get(ATTR_HS_COLOR)
|
hs_color: tuple[float, float] | None = kwargs.get(ATTR_HS_COLOR)
|
||||||
|
new_rgb: tuple[int, int, int] | None
|
||||||
if hs_color is not None:
|
if hs_color is not None:
|
||||||
new_rgb = color_util.color_hs_to_RGB(*hs_color)
|
new_rgb = color_util.color_hs_to_RGB(*hs_color)
|
||||||
else:
|
else:
|
||||||
new_rgb = None
|
new_rgb = None
|
||||||
new_white = kwargs.get(ATTR_WHITE_VALUE)
|
new_white: int | None = kwargs.get(ATTR_WHITE_VALUE)
|
||||||
|
|
||||||
if new_rgb is None and new_white is None:
|
if new_rgb is None and new_white is None:
|
||||||
return
|
return
|
||||||
@ -155,11 +162,11 @@ class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity):
|
|||||||
|
|
||||||
if self.assumed_state:
|
if self.assumed_state:
|
||||||
# optimistically assume that light has changed state
|
# optimistically assume that light has changed state
|
||||||
self._hs = color_util.color_RGB_to_hs(*rgb)
|
self._hs = color_util.color_RGB_to_hs(*rgb) # type: ignore[assignment]
|
||||||
self._white = white
|
self._white = white
|
||||||
self._values[self.value_type] = hex_color
|
self._values[self.value_type] = hex_color
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs):
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
value_type = self.gateway.const.SetReq.V_LIGHT
|
value_type = self.gateway.const.SetReq.V_LIGHT
|
||||||
self.gateway.set_child_value(self.node_id, self.child_id, value_type, 0, ack=1)
|
self.gateway.set_child_value(self.node_id, self.child_id, value_type, 0, ack=1)
|
||||||
@ -170,13 +177,13 @@ class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity):
|
|||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_update_light(self):
|
def _async_update_light(self) -> None:
|
||||||
"""Update the controller with values from light child."""
|
"""Update the controller with values from light child."""
|
||||||
value_type = self.gateway.const.SetReq.V_LIGHT
|
value_type = self.gateway.const.SetReq.V_LIGHT
|
||||||
self._state = self._values[value_type] == STATE_ON
|
self._state = self._values[value_type] == STATE_ON
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_update_dimmer(self):
|
def _async_update_dimmer(self) -> None:
|
||||||
"""Update the controller with values from dimmer child."""
|
"""Update the controller with values from dimmer child."""
|
||||||
value_type = self.gateway.const.SetReq.V_DIMMER
|
value_type = self.gateway.const.SetReq.V_DIMMER
|
||||||
if value_type in self._values:
|
if value_type in self._values:
|
||||||
@ -185,31 +192,31 @@ class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity):
|
|||||||
self._state = False
|
self._state = False
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_update_rgb_or_w(self):
|
def _async_update_rgb_or_w(self) -> None:
|
||||||
"""Update the controller with values from RGB or RGBW child."""
|
"""Update the controller with values from RGB or RGBW child."""
|
||||||
value = self._values[self.value_type]
|
value = self._values[self.value_type]
|
||||||
color_list = rgb_hex_to_rgb_list(value)
|
color_list = rgb_hex_to_rgb_list(value)
|
||||||
if len(color_list) > 3:
|
if len(color_list) > 3:
|
||||||
self._white = color_list.pop()
|
self._white = color_list.pop()
|
||||||
self._hs = color_util.color_RGB_to_hs(*color_list)
|
self._hs = color_util.color_RGB_to_hs(*color_list) # type: ignore[assignment]
|
||||||
|
|
||||||
|
|
||||||
class MySensorsLightDimmer(MySensorsLight):
|
class MySensorsLightDimmer(MySensorsLight):
|
||||||
"""Dimmer child class to MySensorsLight."""
|
"""Dimmer child class to MySensorsLight."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return SUPPORT_BRIGHTNESS
|
return SUPPORT_BRIGHTNESS
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device on."""
|
"""Turn the device on."""
|
||||||
self._turn_on_light()
|
self._turn_on_light()
|
||||||
self._turn_on_dimmer(**kwargs)
|
self._turn_on_dimmer(**kwargs)
|
||||||
if self.assumed_state:
|
if self.assumed_state:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self) -> None:
|
||||||
"""Update the controller with the latest value from a sensor."""
|
"""Update the controller with the latest value from a sensor."""
|
||||||
await super().async_update()
|
await super().async_update()
|
||||||
self._async_update_light()
|
self._async_update_light()
|
||||||
@ -220,14 +227,14 @@ class MySensorsLightRGB(MySensorsLight):
|
|||||||
"""RGB child class to MySensorsLight."""
|
"""RGB child class to MySensorsLight."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
if set_req.V_DIMMER in self._values:
|
if set_req.V_DIMMER in self._values:
|
||||||
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR
|
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR
|
||||||
return SUPPORT_COLOR
|
return SUPPORT_COLOR
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device on."""
|
"""Turn the device on."""
|
||||||
self._turn_on_light()
|
self._turn_on_light()
|
||||||
self._turn_on_dimmer(**kwargs)
|
self._turn_on_dimmer(**kwargs)
|
||||||
@ -235,7 +242,7 @@ class MySensorsLightRGB(MySensorsLight):
|
|||||||
if self.assumed_state:
|
if self.assumed_state:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self) -> None:
|
||||||
"""Update the controller with the latest value from a sensor."""
|
"""Update the controller with the latest value from a sensor."""
|
||||||
await super().async_update()
|
await super().async_update()
|
||||||
self._async_update_light()
|
self._async_update_light()
|
||||||
@ -247,14 +254,14 @@ class MySensorsLightRGBW(MySensorsLightRGB):
|
|||||||
"""RGBW child class to MySensorsLightRGB."""
|
"""RGBW child class to MySensorsLightRGB."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
if set_req.V_DIMMER in self._values:
|
if set_req.V_DIMMER in self._values:
|
||||||
return SUPPORT_BRIGHTNESS | SUPPORT_MYSENSORS_RGBW
|
return SUPPORT_BRIGHTNESS | SUPPORT_MYSENSORS_RGBW
|
||||||
return SUPPORT_MYSENSORS_RGBW
|
return SUPPORT_MYSENSORS_RGBW
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device on."""
|
"""Turn the device on."""
|
||||||
self._turn_on_light()
|
self._turn_on_light()
|
||||||
self._turn_on_dimmer(**kwargs)
|
self._turn_on_dimmer(**kwargs)
|
||||||
|
@ -1,9 +1,20 @@
|
|||||||
"""MySensors notification service."""
|
"""MySensors notification service."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components import mysensors
|
from homeassistant.components import mysensors
|
||||||
from homeassistant.components.notify import ATTR_TARGET, DOMAIN, BaseNotificationService
|
from homeassistant.components.notify import ATTR_TARGET, DOMAIN, BaseNotificationService
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import DevId, DiscoveryInfo
|
||||||
|
|
||||||
|
|
||||||
async def async_get_service(hass, config, discovery_info=None):
|
async def async_get_service(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: dict[str, Any],
|
||||||
|
discovery_info: DiscoveryInfo | None = None,
|
||||||
|
) -> BaseNotificationService | None:
|
||||||
"""Get the MySensors notification service."""
|
"""Get the MySensors notification service."""
|
||||||
if not discovery_info:
|
if not discovery_info:
|
||||||
return None
|
return None
|
||||||
@ -19,7 +30,7 @@ async def async_get_service(hass, config, discovery_info=None):
|
|||||||
class MySensorsNotificationDevice(mysensors.device.MySensorsDevice):
|
class MySensorsNotificationDevice(mysensors.device.MySensorsDevice):
|
||||||
"""Represent a MySensors Notification device."""
|
"""Represent a MySensors Notification device."""
|
||||||
|
|
||||||
def send_msg(self, msg):
|
def send_msg(self, msg: str) -> None:
|
||||||
"""Send a message."""
|
"""Send a message."""
|
||||||
for sub_msg in [msg[i : i + 25] for i in range(0, len(msg), 25)]:
|
for sub_msg in [msg[i : i + 25] for i in range(0, len(msg), 25)]:
|
||||||
# Max mysensors payload is 25 bytes.
|
# Max mysensors payload is 25 bytes.
|
||||||
@ -27,7 +38,7 @@ class MySensorsNotificationDevice(mysensors.device.MySensorsDevice):
|
|||||||
self.node_id, self.child_id, self.value_type, sub_msg
|
self.node_id, self.child_id, self.value_type, sub_msg
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
"""Return the representation."""
|
"""Return the representation."""
|
||||||
return f"<MySensorsNotificationDevice {self.name}>"
|
return f"<MySensorsNotificationDevice {self.name}>"
|
||||||
|
|
||||||
@ -35,11 +46,15 @@ class MySensorsNotificationDevice(mysensors.device.MySensorsDevice):
|
|||||||
class MySensorsNotificationService(BaseNotificationService):
|
class MySensorsNotificationService(BaseNotificationService):
|
||||||
"""Implement a MySensors notification service."""
|
"""Implement a MySensors notification service."""
|
||||||
|
|
||||||
def __init__(self, hass):
|
def __init__(self, hass: HomeAssistant) -> None:
|
||||||
"""Initialize the service."""
|
"""Initialize the service."""
|
||||||
self.devices = mysensors.get_mysensors_devices(hass, DOMAIN)
|
self.devices: dict[
|
||||||
|
DevId, MySensorsNotificationDevice
|
||||||
|
] = mysensors.get_mysensors_devices(
|
||||||
|
hass, DOMAIN
|
||||||
|
) # type: ignore[assignment]
|
||||||
|
|
||||||
async def async_send_message(self, message="", **kwargs):
|
async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
|
||||||
"""Send a message to a user."""
|
"""Send a message to a user."""
|
||||||
target_devices = kwargs.get(ATTR_TARGET)
|
target_devices = kwargs.get(ATTR_TARGET)
|
||||||
devices = [
|
devices = [
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
"""Support for MySensors sensors."""
|
"""Support for MySensors sensors."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
|
|
||||||
from homeassistant.components import mysensors
|
from homeassistant.components import mysensors
|
||||||
from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY
|
|
||||||
from homeassistant.components.sensor import DOMAIN, SensorEntity
|
from homeassistant.components.sensor import DOMAIN, SensorEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -26,9 +27,10 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .const import MYSENSORS_DISCOVERY, DiscoveryInfo
|
||||||
from .helpers import on_unload
|
from .helpers import on_unload
|
||||||
|
|
||||||
SENSORS = {
|
SENSORS: dict[str, list[str | None] | dict[str, list[str | None]]] = {
|
||||||
"V_TEMP": [None, "mdi:thermometer"],
|
"V_TEMP": [None, "mdi:thermometer"],
|
||||||
"V_HUM": [PERCENTAGE, "mdi:water-percent"],
|
"V_HUM": [PERCENTAGE, "mdi:water-percent"],
|
||||||
"V_DIMMER": [PERCENTAGE, "mdi:percent"],
|
"V_DIMMER": [PERCENTAGE, "mdi:percent"],
|
||||||
@ -67,10 +69,10 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
):
|
) -> None:
|
||||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||||
|
|
||||||
async def async_discover(discovery_info):
|
async def async_discover(discovery_info: DiscoveryInfo) -> None:
|
||||||
"""Discover and add a MySensors sensor."""
|
"""Discover and add a MySensors sensor."""
|
||||||
mysensors.setup_mysensors_platform(
|
mysensors.setup_mysensors_platform(
|
||||||
hass,
|
hass,
|
||||||
@ -95,7 +97,7 @@ class MySensorsSensor(mysensors.device.MySensorsEntity, SensorEntity):
|
|||||||
"""Representation of a MySensors Sensor child node."""
|
"""Representation of a MySensors Sensor child node."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def force_update(self):
|
def force_update(self) -> bool:
|
||||||
"""Return True if state updates should be forced.
|
"""Return True if state updates should be forced.
|
||||||
|
|
||||||
If True, a state change will be triggered anytime the state property is
|
If True, a state change will be triggered anytime the state property is
|
||||||
@ -104,36 +106,43 @@ class MySensorsSensor(mysensors.device.MySensorsEntity, SensorEntity):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self) -> str | None:
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
return self._values.get(self.value_type)
|
return self._values.get(self.value_type)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self) -> str | None:
|
||||||
"""Return the icon to use in the frontend, if any."""
|
"""Return the icon to use in the frontend, if any."""
|
||||||
icon = self._get_sensor_type()[1]
|
icon = self._get_sensor_type()[1]
|
||||||
return icon
|
return icon
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self) -> str | None:
|
||||||
"""Return the unit of measurement of this entity."""
|
"""Return the unit of measurement of this entity."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
if (
|
if (
|
||||||
AwesomeVersion(self.gateway.protocol_version) >= AwesomeVersion("1.5")
|
AwesomeVersion(self.gateway.protocol_version) >= AwesomeVersion("1.5")
|
||||||
and set_req.V_UNIT_PREFIX in self._values
|
and set_req.V_UNIT_PREFIX in self._values
|
||||||
):
|
):
|
||||||
return self._values[set_req.V_UNIT_PREFIX]
|
custom_unit: str = self._values[set_req.V_UNIT_PREFIX]
|
||||||
|
return custom_unit
|
||||||
|
|
||||||
|
if set_req(self.value_type) == set_req.V_TEMP:
|
||||||
|
if self.hass.config.units.is_metric:
|
||||||
|
return TEMP_CELSIUS
|
||||||
|
return TEMP_FAHRENHEIT
|
||||||
|
|
||||||
unit = self._get_sensor_type()[0]
|
unit = self._get_sensor_type()[0]
|
||||||
return unit
|
return unit
|
||||||
|
|
||||||
def _get_sensor_type(self):
|
def _get_sensor_type(self) -> list[str | None]:
|
||||||
"""Return list with unit and icon of sensor type."""
|
"""Return list with unit and icon of sensor type."""
|
||||||
pres = self.gateway.const.Presentation
|
pres = self.gateway.const.Presentation
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
SENSORS[set_req.V_TEMP.name][0] = (
|
|
||||||
TEMP_CELSIUS if self.hass.config.units.is_metric else TEMP_FAHRENHEIT
|
_sensor_type = SENSORS.get(set_req(self.value_type).name, [None, None])
|
||||||
)
|
if isinstance(_sensor_type, dict):
|
||||||
sensor_type = SENSORS.get(set_req(self.value_type).name, [None, None])
|
sensor_type = _sensor_type.get(pres(self.child_type).name, [None, None])
|
||||||
if isinstance(sensor_type, dict):
|
else:
|
||||||
sensor_type = sensor_type.get(pres(self.child_type).name, [None, None])
|
sensor_type = _sensor_type
|
||||||
return sensor_type
|
return sensor_type
|
||||||
|
@ -1,16 +1,28 @@
|
|||||||
"""Support for MySensors switches."""
|
"""Support for MySensors switches."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import mysensors
|
from homeassistant.components import mysensors
|
||||||
from homeassistant.components.switch import DOMAIN, SwitchEntity
|
from homeassistant.components.switch import DOMAIN, SwitchEntity
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, ServiceCall
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from ...config_entries import ConfigEntry
|
from ...config_entries import ConfigEntry
|
||||||
from ...helpers.dispatcher import async_dispatcher_connect
|
from ...helpers.dispatcher import async_dispatcher_connect
|
||||||
from .const import DOMAIN as MYSENSORS_DOMAIN, MYSENSORS_DISCOVERY, SERVICE_SEND_IR_CODE
|
from .const import (
|
||||||
|
DOMAIN as MYSENSORS_DOMAIN,
|
||||||
|
MYSENSORS_DISCOVERY,
|
||||||
|
SERVICE_SEND_IR_CODE,
|
||||||
|
DiscoveryInfo,
|
||||||
|
SensorType,
|
||||||
|
)
|
||||||
|
from .device import MySensorsDevice
|
||||||
from .helpers import on_unload
|
from .helpers import on_unload
|
||||||
|
|
||||||
ATTR_IR_CODE = "V_IR_SEND"
|
ATTR_IR_CODE = "V_IR_SEND"
|
||||||
@ -24,9 +36,9 @@ async def async_setup_entry(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
):
|
) -> None:
|
||||||
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
"""Set up this platform for a specific ConfigEntry(==Gateway)."""
|
||||||
device_class_map = {
|
device_class_map: dict[SensorType, type[MySensorsDevice]] = {
|
||||||
"S_DOOR": MySensorsSwitch,
|
"S_DOOR": MySensorsSwitch,
|
||||||
"S_MOTION": MySensorsSwitch,
|
"S_MOTION": MySensorsSwitch,
|
||||||
"S_SMOKE": MySensorsSwitch,
|
"S_SMOKE": MySensorsSwitch,
|
||||||
@ -42,7 +54,7 @@ async def async_setup_entry(
|
|||||||
"S_WATER_QUALITY": MySensorsSwitch,
|
"S_WATER_QUALITY": MySensorsSwitch,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def async_discover(discovery_info):
|
async def async_discover(discovery_info: DiscoveryInfo) -> None:
|
||||||
"""Discover and add a MySensors switch."""
|
"""Discover and add a MySensors switch."""
|
||||||
mysensors.setup_mysensors_platform(
|
mysensors.setup_mysensors_platform(
|
||||||
hass,
|
hass,
|
||||||
@ -52,7 +64,7 @@ async def async_setup_entry(
|
|||||||
async_add_entities=async_add_entities,
|
async_add_entities=async_add_entities,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_send_ir_code_service(service):
|
async def async_send_ir_code_service(service: ServiceCall) -> None:
|
||||||
"""Set IR code as device state attribute."""
|
"""Set IR code as device state attribute."""
|
||||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||||
ir_code = service.data.get(ATTR_IR_CODE)
|
ir_code = service.data.get(ATTR_IR_CODE)
|
||||||
@ -98,17 +110,23 @@ class MySensorsSwitch(mysensors.device.MySensorsEntity, SwitchEntity):
|
|||||||
"""Representation of the value of a MySensors Switch child node."""
|
"""Representation of the value of a MySensors Switch child node."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_power_w(self):
|
def current_power_w(self) -> float | None:
|
||||||
"""Return the current power usage in W."""
|
"""Return the current power usage in W."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
return self._values.get(set_req.V_WATT)
|
value = self._values.get(set_req.V_WATT)
|
||||||
|
float_value: float | None = None
|
||||||
|
if value is not None:
|
||||||
|
with suppress(ValueError):
|
||||||
|
float_value = float(value)
|
||||||
|
|
||||||
|
return float_value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self) -> bool:
|
||||||
"""Return True if switch is on."""
|
"""Return True if switch is on."""
|
||||||
return self._values.get(self.value_type) == STATE_ON
|
return self._values.get(self.value_type) == STATE_ON
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the switch on."""
|
"""Turn the switch on."""
|
||||||
self.gateway.set_child_value(
|
self.gateway.set_child_value(
|
||||||
self.node_id, self.child_id, self.value_type, 1, ack=1
|
self.node_id, self.child_id, self.value_type, 1, ack=1
|
||||||
@ -118,7 +136,7 @@ class MySensorsSwitch(mysensors.device.MySensorsEntity, SwitchEntity):
|
|||||||
self._values[self.value_type] = STATE_ON
|
self._values[self.value_type] = STATE_ON
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs):
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the switch off."""
|
"""Turn the switch off."""
|
||||||
self.gateway.set_child_value(
|
self.gateway.set_child_value(
|
||||||
self.node_id, self.child_id, self.value_type, 0, ack=1
|
self.node_id, self.child_id, self.value_type, 0, ack=1
|
||||||
@ -132,18 +150,18 @@ class MySensorsSwitch(mysensors.device.MySensorsEntity, SwitchEntity):
|
|||||||
class MySensorsIRSwitch(MySensorsSwitch):
|
class MySensorsIRSwitch(MySensorsSwitch):
|
||||||
"""IR switch child class to MySensorsSwitch."""
|
"""IR switch child class to MySensorsSwitch."""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args: Any) -> None:
|
||||||
"""Set up instance attributes."""
|
"""Set up instance attributes."""
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
self._ir_code = None
|
self._ir_code: str | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self) -> bool:
|
||||||
"""Return True if switch is on."""
|
"""Return True if switch is on."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
return self._values.get(set_req.V_LIGHT) == STATE_ON
|
return self._values.get(set_req.V_LIGHT) == STATE_ON
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the IR switch on."""
|
"""Turn the IR switch on."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
if ATTR_IR_CODE in kwargs:
|
if ATTR_IR_CODE in kwargs:
|
||||||
@ -162,7 +180,7 @@ class MySensorsIRSwitch(MySensorsSwitch):
|
|||||||
# Turn off switch after switch was turned on
|
# Turn off switch after switch was turned on
|
||||||
await self.async_turn_off()
|
await self.async_turn_off()
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs):
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the IR switch off."""
|
"""Turn the IR switch off."""
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
self.gateway.set_child_value(
|
self.gateway.set_child_value(
|
||||||
@ -173,7 +191,7 @@ class MySensorsIRSwitch(MySensorsSwitch):
|
|||||||
self._values[set_req.V_LIGHT] = STATE_OFF
|
self._values[set_req.V_LIGHT] = STATE_OFF
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self) -> None:
|
||||||
"""Update the controller with the latest value from a sensor."""
|
"""Update the controller with the latest value from a sensor."""
|
||||||
await super().async_update()
|
await super().async_update()
|
||||||
self._ir_code = self._values.get(self.value_type)
|
self._ir_code = self._values.get(self.value_type)
|
||||||
|
11
mypy.ini
11
mypy.ini
@ -517,6 +517,17 @@ no_implicit_optional = true
|
|||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unreachable = true
|
warn_unreachable = true
|
||||||
|
|
||||||
|
[mypy-homeassistant.components.mysensors.*]
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unreachable = true
|
||||||
|
|
||||||
[mypy-homeassistant.components.nam.*]
|
[mypy-homeassistant.components.nam.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user