Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions homeassistant/components/matter/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ def _update_from_device(self) -> None:
entity_class=MatterBinarySensor,
required_attributes=(clusters.OccupancySensing.Attributes.Occupancy,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="ThermostatOccupancySensor",
device_class=BinarySensorDeviceClass.OCCUPANCY,
# The first bit = if occupied
device_to_ha=lambda x: (x & 1 == 1) if x is not None else None,
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.Thermostat.Attributes.Occupancy,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
Expand Down
13 changes: 12 additions & 1 deletion homeassistant/components/sensibo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,26 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.device_registry import DeviceEntry
from homeassistant.helpers.typing import ConfigType

from .const import DOMAIN, LOGGER, PLATFORMS
from .coordinator import SensiboDataUpdateCoordinator
from .services import async_setup_services
from .util import NoDevicesError, NoUsernameError, async_validate_api

type SensiboConfigEntry = ConfigEntry[SensiboDataUpdateCoordinator]

CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Sensibo component."""
async_setup_services(hass)

return True


async def async_setup_entry(hass: HomeAssistant, entry: SensiboConfigEntry) -> bool:
"""Set up Sensibo from a config entry."""
Expand Down
100 changes: 2 additions & 98 deletions homeassistant/components/sensibo/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,14 @@
from bisect import bisect_left
from typing import TYPE_CHECKING, Any

import voluptuous as vol

from homeassistant.components.climate import (
ATTR_FAN_MODE,
ATTR_HVAC_MODE,
ATTR_SWING_MODE,
ClimateEntity,
ClimateEntityFeature,
HVACMode,
)
from homeassistant.const import (
ATTR_MODE,
ATTR_STATE,
ATTR_TEMPERATURE,
PRECISION_TENTHS,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, SupportsResponse
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.unit_conversion import TemperatureConverter

Expand All @@ -33,30 +21,6 @@
from .coordinator import SensiboDataUpdateCoordinator
from .entity import SensiboDeviceBaseEntity, async_handle_api_call

SERVICE_ASSUME_STATE = "assume_state"
SERVICE_ENABLE_TIMER = "enable_timer"
ATTR_MINUTES = "minutes"
SERVICE_ENABLE_PURE_BOOST = "enable_pure_boost"
SERVICE_DISABLE_PURE_BOOST = "disable_pure_boost"
SERVICE_FULL_STATE = "full_state"
SERVICE_ENABLE_CLIMATE_REACT = "enable_climate_react"
SERVICE_GET_DEVICE_CAPABILITIES = "get_device_capabilities"
ATTR_HIGH_TEMPERATURE_THRESHOLD = "high_temperature_threshold"
ATTR_HIGH_TEMPERATURE_STATE = "high_temperature_state"
ATTR_LOW_TEMPERATURE_THRESHOLD = "low_temperature_threshold"
ATTR_LOW_TEMPERATURE_STATE = "low_temperature_state"
ATTR_SMART_TYPE = "smart_type"

ATTR_AC_INTEGRATION = "ac_integration"
ATTR_GEO_INTEGRATION = "geo_integration"
ATTR_INDOOR_INTEGRATION = "indoor_integration"
ATTR_OUTDOOR_INTEGRATION = "outdoor_integration"
ATTR_SENSITIVITY = "sensitivity"
ATTR_TARGET_TEMPERATURE = "target_temperature"
ATTR_HORIZONTAL_SWING_MODE = "horizontal_swing_mode"
ATTR_LIGHT = "light"
BOOST_INCLUSIVE = "boost_inclusive"

AVAILABLE_FAN_MODES = {
"quiet",
"low",
Expand Down Expand Up @@ -162,66 +126,6 @@ def _add_remove_devices() -> None:
entry.async_on_unload(coordinator.async_add_listener(_add_remove_devices))
_add_remove_devices()

platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_ASSUME_STATE,
{
vol.Required(ATTR_STATE): vol.In(["on", "off"]),
},
"async_assume_state",
)
platform.async_register_entity_service(
SERVICE_ENABLE_TIMER,
{
vol.Required(ATTR_MINUTES): cv.positive_int,
},
"async_enable_timer",
)
platform.async_register_entity_service(
SERVICE_ENABLE_PURE_BOOST,
{
vol.Required(ATTR_AC_INTEGRATION): bool,
vol.Required(ATTR_GEO_INTEGRATION): bool,
vol.Required(ATTR_INDOOR_INTEGRATION): bool,
vol.Required(ATTR_OUTDOOR_INTEGRATION): bool,
vol.Required(ATTR_SENSITIVITY): vol.In(["normal", "sensitive"]),
},
"async_enable_pure_boost",
)
platform.async_register_entity_service(
SERVICE_FULL_STATE,
{
vol.Required(ATTR_MODE): vol.In(
["cool", "heat", "fan", "auto", "dry", "off"]
),
vol.Optional(ATTR_TARGET_TEMPERATURE): int,
vol.Optional(ATTR_FAN_MODE): str,
vol.Optional(ATTR_SWING_MODE): str,
vol.Optional(ATTR_HORIZONTAL_SWING_MODE): str,
vol.Optional(ATTR_LIGHT): vol.In(["on", "off", "dim"]),
},
"async_full_ac_state",
)
platform.async_register_entity_service(
SERVICE_ENABLE_CLIMATE_REACT,
{
vol.Required(ATTR_HIGH_TEMPERATURE_THRESHOLD): vol.Coerce(float),
vol.Required(ATTR_HIGH_TEMPERATURE_STATE): dict,
vol.Required(ATTR_LOW_TEMPERATURE_THRESHOLD): vol.Coerce(float),
vol.Required(ATTR_LOW_TEMPERATURE_STATE): dict,
vol.Required(ATTR_SMART_TYPE): vol.In(
["temperature", "feelslike", "humidity"]
),
},
"async_enable_climate_react",
)
platform.async_register_entity_service(
SERVICE_GET_DEVICE_CAPABILITIES,
{vol.Required(ATTR_HVAC_MODE): vol.Coerce(HVACMode)},
"async_get_device_capabilities",
supports_response=SupportsResponse.ONLY,
)


class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
"""Representation of a Sensibo climate device."""
Expand Down
124 changes: 124 additions & 0 deletions homeassistant/components/sensibo/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""Sensibo services."""

from __future__ import annotations

import voluptuous as vol

from homeassistant.components.climate import (
ATTR_FAN_MODE,
ATTR_HVAC_MODE,
ATTR_SWING_MODE,
DOMAIN as CLIMATE_DOMAIN,
HVACMode,
)
from homeassistant.const import ATTR_MODE, ATTR_STATE
from homeassistant.core import HomeAssistant, SupportsResponse, callback
from homeassistant.helpers import config_validation as cv, service

from .const import DOMAIN

SERVICE_ASSUME_STATE = "assume_state"
SERVICE_ENABLE_TIMER = "enable_timer"
ATTR_MINUTES = "minutes"
SERVICE_ENABLE_PURE_BOOST = "enable_pure_boost"
SERVICE_DISABLE_PURE_BOOST = "disable_pure_boost"
SERVICE_FULL_STATE = "full_state"
SERVICE_ENABLE_CLIMATE_REACT = "enable_climate_react"
SERVICE_GET_DEVICE_CAPABILITIES = "get_device_capabilities"
ATTR_HIGH_TEMPERATURE_THRESHOLD = "high_temperature_threshold"
ATTR_HIGH_TEMPERATURE_STATE = "high_temperature_state"
ATTR_LOW_TEMPERATURE_THRESHOLD = "low_temperature_threshold"
ATTR_LOW_TEMPERATURE_STATE = "low_temperature_state"
ATTR_SMART_TYPE = "smart_type"

ATTR_AC_INTEGRATION = "ac_integration"
ATTR_GEO_INTEGRATION = "geo_integration"
ATTR_INDOOR_INTEGRATION = "indoor_integration"
ATTR_OUTDOOR_INTEGRATION = "outdoor_integration"
ATTR_SENSITIVITY = "sensitivity"
ATTR_TARGET_TEMPERATURE = "target_temperature"
ATTR_HORIZONTAL_SWING_MODE = "horizontal_swing_mode"
ATTR_LIGHT = "light"
BOOST_INCLUSIVE = "boost_inclusive"


@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Register Sensibo services."""

service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_ASSUME_STATE,
entity_domain=CLIMATE_DOMAIN,
schema={
vol.Required(ATTR_STATE): vol.In(["on", "off"]),
},
func="async_assume_state",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_ENABLE_TIMER,
entity_domain=CLIMATE_DOMAIN,
schema={
vol.Required(ATTR_MINUTES): cv.positive_int,
},
func="async_enable_timer",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_ENABLE_PURE_BOOST,
entity_domain=CLIMATE_DOMAIN,
schema={
vol.Required(ATTR_AC_INTEGRATION): bool,
vol.Required(ATTR_GEO_INTEGRATION): bool,
vol.Required(ATTR_INDOOR_INTEGRATION): bool,
vol.Required(ATTR_OUTDOOR_INTEGRATION): bool,
vol.Required(ATTR_SENSITIVITY): vol.In(["normal", "sensitive"]),
},
func="async_enable_pure_boost",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_FULL_STATE,
entity_domain=CLIMATE_DOMAIN,
schema={
vol.Required(ATTR_MODE): vol.In(
["cool", "heat", "fan", "auto", "dry", "off"]
),
vol.Optional(ATTR_TARGET_TEMPERATURE): int,
vol.Optional(ATTR_FAN_MODE): str,
vol.Optional(ATTR_SWING_MODE): str,
vol.Optional(ATTR_HORIZONTAL_SWING_MODE): str,
vol.Optional(ATTR_LIGHT): vol.In(["on", "off", "dim"]),
},
func="async_full_ac_state",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_ENABLE_CLIMATE_REACT,
entity_domain=CLIMATE_DOMAIN,
schema={
vol.Required(ATTR_HIGH_TEMPERATURE_THRESHOLD): vol.Coerce(float),
vol.Required(ATTR_HIGH_TEMPERATURE_STATE): dict,
vol.Required(ATTR_LOW_TEMPERATURE_THRESHOLD): vol.Coerce(float),
vol.Required(ATTR_LOW_TEMPERATURE_STATE): dict,
vol.Required(ATTR_SMART_TYPE): vol.In(
["temperature", "feelslike", "humidity"]
),
},
func="async_enable_climate_react",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_GET_DEVICE_CAPABILITIES,
entity_domain=CLIMATE_DOMAIN,
schema={vol.Required(ATTR_HVAC_MODE): vol.Coerce(HVACMode)},
func="async_get_device_capabilities",
supports_response=SupportsResponse.ONLY,
)
10 changes: 10 additions & 0 deletions homeassistant/components/sql/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
)
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.template import Template
from homeassistant.helpers.trigger_template_entity import (
CONF_AVAILABILITY,
Expand Down Expand Up @@ -69,6 +70,15 @@ async def async_setup_platform(
) -> None:
"""Set up the SQL sensor from yaml."""
if (conf := discovery_info) is None:
async_create_issue(
hass,
DOMAIN,
"sensor_platform_yaml_not_supported",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="platform_yaml_not_supported",
learn_more_url="https://www.home-assistant.io/integrations/sql/",
)
return

name: Template = conf[CONF_NAME]
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/sql/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@
"entity_id_query_does_full_table_scan": {
"title": "SQL query does full table scan",
"description": "The query `{query}` contains the keyword `entity_id` but does not reference the `states_meta` table. This will cause a full table scan and database instability. Please check the documentation and use `states_meta.entity_id` instead."
},
"platform_yaml_not_supported": {
"title": "Platform YAML is not supported in SQL",
"description": "Platform YAML setup is not supported.\nChange from configuring it in the `sensor:` key to using the `sql:` key directly in configuration.yaml.\nTo see the detailed documentation, select Learn more."
}
}
}
1 change: 1 addition & 0 deletions tests/components/matter/fixtures/nodes/thermostat.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@
"1/64/65531": [0, 65528, 65529, 65531, 65532, 65533],
"1/513/0": 2830,
"1/513/1": 1250,
"1/513/2": 1,
"1/513/3": null,
"1/513/4": null,
"1/513/5": null,
Expand Down
49 changes: 49 additions & 0 deletions tests/components/matter/snapshots/test_binary_sensor.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,55 @@
'state': 'off',
})
# ---
# name: test_binary_sensors[thermostat][binary_sensor.longan_link_hvac_occupancy-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.longan_link_hvac_occupancy',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.OCCUPANCY: 'occupancy'>,
'original_icon': None,
'original_name': 'Occupancy',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-ThermostatOccupancySensor-513-2',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[thermostat][binary_sensor.longan_link_hvac_occupancy-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'occupancy',
'friendly_name': 'Longan link HVAC Occupancy',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.longan_link_hvac_occupancy',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_binary_sensors[valve][binary_sensor.valve_general_fault-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
Expand Down
Loading
Loading