From c3f95a4f7aa1485d73750fecf4475751e10b33c1 Mon Sep 17 00:00:00 2001 From: Laurence Presland <22112431+laurence-presland@users.noreply.github.com> Date: Mon, 13 May 2024 23:18:28 +1000 Subject: [PATCH] Implement support for SwitchBot Meter, MeterPlus, and Outdoor Meter (#115522) * Implement support for SwitchBot MeterPlus Add temperature, humidity, and battery sensor entities for the MeterPlus device * Rename GH username * Update homeassistant/components/switchbot_cloud/coordinator.py Co-authored-by: Joost Lekkerkerker * Refactor to use EntityDescriptions Concat entity ID in SwitchBotCloudSensor init * Remove __future__ import * Make scan interval user configurable * Add support for Meter and Outdoor Meter * Revert "Make scan interval user configurable" This reverts commit e256c35bb71e598cf879e05e1df21dff8456b09d. * Remove device-specific default scan intervals * Update homeassistant/components/switchbot_cloud/sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/switchbot_cloud/sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/switchbot_cloud/sensor.py Co-authored-by: Joost Lekkerkerker * Fix ruff errors * Reorder manifest keys * Update CODEOWNERS * Add sensor.py to coveragerc --------- Co-authored-by: Joost Lekkerkerker --- .coveragerc | 1 + CODEOWNERS | 4 +- .../components/switchbot_cloud/__init__.py | 13 ++- .../components/switchbot_cloud/const.py | 6 +- .../components/switchbot_cloud/coordinator.py | 5 +- .../components/switchbot_cloud/manifest.json | 3 +- .../components/switchbot_cloud/sensor.py | 83 +++++++++++++++++++ 7 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/switchbot_cloud/sensor.py diff --git a/.coveragerc b/.coveragerc index bb52648710f..1334a59df92 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1377,6 +1377,7 @@ omit = homeassistant/components/switchbot_cloud/climate.py homeassistant/components/switchbot_cloud/coordinator.py homeassistant/components/switchbot_cloud/entity.py + homeassistant/components/switchbot_cloud/sensor.py homeassistant/components/switchbot_cloud/switch.py homeassistant/components/switchmate/switch.py homeassistant/components/syncthing/__init__.py diff --git a/CODEOWNERS b/CODEOWNERS index a65ff6955f8..03d3d3569e7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1365,8 +1365,8 @@ build.json @home-assistant/supervisor /tests/components/switchbee/ @jafar-atili /homeassistant/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski /tests/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski -/homeassistant/components/switchbot_cloud/ @SeraphicRav -/tests/components/switchbot_cloud/ @SeraphicRav +/homeassistant/components/switchbot_cloud/ @SeraphicRav @laurence-presland +/tests/components/switchbot_cloud/ @SeraphicRav @laurence-presland /homeassistant/components/switcher_kis/ @thecode /tests/components/switcher_kis/ @thecode /homeassistant/components/switchmate/ @danielhiversen @qiz-li diff --git a/homeassistant/components/switchbot_cloud/__init__.py b/homeassistant/components/switchbot_cloud/__init__.py index 744d513f521..c79ba41018f 100644 --- a/homeassistant/components/switchbot_cloud/__init__.py +++ b/homeassistant/components/switchbot_cloud/__init__.py @@ -1,4 +1,4 @@ -"""The SwitchBot via API integration.""" +"""SwitchBot via API integration.""" from asyncio import gather from dataclasses import dataclass, field @@ -15,7 +15,7 @@ from .const import DOMAIN from .coordinator import SwitchBotCoordinator _LOGGER = getLogger(__name__) -PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.SWITCH] +PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH] @dataclass @@ -24,6 +24,7 @@ class SwitchbotDevices: climates: list[Remote] = field(default_factory=list) switches: list[Device | Remote] = field(default_factory=list) + sensors: list[Device] = field(default_factory=list) @dataclass @@ -72,6 +73,14 @@ def make_device_data( devices_data.switches.append( prepare_device(hass, api, device, coordinators_by_id) ) + if isinstance(device, Device) and device.device_type in [ + "Meter", + "MeterPlus", + "WoIOSensor", + ]: + devices_data.sensors.append( + prepare_device(hass, api, device, coordinators_by_id) + ) return devices_data diff --git a/homeassistant/components/switchbot_cloud/const.py b/homeassistant/components/switchbot_cloud/const.py index b90a2f3a2ec..66c84b63047 100644 --- a/homeassistant/components/switchbot_cloud/const.py +++ b/homeassistant/components/switchbot_cloud/const.py @@ -5,4 +5,8 @@ from typing import Final DOMAIN: Final = "switchbot_cloud" ENTRY_TITLE = "SwitchBot Cloud" -SCAN_INTERVAL = timedelta(seconds=600) +DEFAULT_SCAN_INTERVAL = timedelta(seconds=600) + +SENSOR_KIND_TEMPERATURE = "temperature" +SENSOR_KIND_HUMIDITY = "humidity" +SENSOR_KIND_BATTERY = "battery" diff --git a/homeassistant/components/switchbot_cloud/coordinator.py b/homeassistant/components/switchbot_cloud/coordinator.py index 4c12e03a6f2..7d3980bcff9 100644 --- a/homeassistant/components/switchbot_cloud/coordinator.py +++ b/homeassistant/components/switchbot_cloud/coordinator.py @@ -9,7 +9,7 @@ from switchbot_api import CannotConnect, Device, Remote, SwitchBotAPI from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN, SCAN_INTERVAL +from .const import DEFAULT_SCAN_INTERVAL, DOMAIN _LOGGER = getLogger(__name__) @@ -21,7 +21,6 @@ class SwitchBotCoordinator(DataUpdateCoordinator[Status]): _api: SwitchBotAPI _device_id: str - _should_poll = False def __init__( self, hass: HomeAssistant, api: SwitchBotAPI, device: Device | Remote @@ -31,7 +30,7 @@ class SwitchBotCoordinator(DataUpdateCoordinator[Status]): hass, _LOGGER, name=DOMAIN, - update_interval=SCAN_INTERVAL, + update_interval=DEFAULT_SCAN_INTERVAL, ) self._api = api self._device_id = device.device_id diff --git a/homeassistant/components/switchbot_cloud/manifest.json b/homeassistant/components/switchbot_cloud/manifest.json index 2b50f39925f..e7a220bc42c 100644 --- a/homeassistant/components/switchbot_cloud/manifest.json +++ b/homeassistant/components/switchbot_cloud/manifest.json @@ -1,9 +1,10 @@ { "domain": "switchbot_cloud", "name": "SwitchBot Cloud", - "codeowners": ["@SeraphicRav"], + "codeowners": ["@SeraphicRav", "@laurence-presland"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/switchbot_cloud", + "integration_type": "hub", "iot_class": "cloud_polling", "loggers": ["switchbot-api"], "requirements": ["switchbot-api==2.1.0"] diff --git a/homeassistant/components/switchbot_cloud/sensor.py b/homeassistant/components/switchbot_cloud/sensor.py new file mode 100644 index 00000000000..ac612aea119 --- /dev/null +++ b/homeassistant/components/switchbot_cloud/sensor.py @@ -0,0 +1,83 @@ +"""Platform for sensor integration.""" + +from switchbot_api import Device, SwitchBotAPI + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE, UnitOfTemperature +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import SwitchbotCloudData +from .const import DOMAIN +from .coordinator import SwitchBotCoordinator +from .entity import SwitchBotCloudEntity + +SENSOR_TYPE_TEMPERATURE = "temperature" +SENSOR_TYPE_HUMIDITY = "humidity" +SENSOR_TYPE_BATTERY = "battery" + +METER_PLUS_SENSOR_DESCRIPTIONS = ( + SensorEntityDescription( + key=SENSOR_TYPE_TEMPERATURE, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + ), + SensorEntityDescription( + key=SENSOR_TYPE_HUMIDITY, + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + ), + SensorEntityDescription( + key=SENSOR_TYPE_BATTERY, + device_class=SensorDeviceClass.BATTERY, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up SwitchBot Cloud entry.""" + data: SwitchbotCloudData = hass.data[DOMAIN][config.entry_id] + + async_add_entities( + SwitchBotCloudSensor(data.api, device, coordinator, description) + for device, coordinator in data.devices.sensors + for description in METER_PLUS_SENSOR_DESCRIPTIONS + ) + + +class SwitchBotCloudSensor(SwitchBotCloudEntity, SensorEntity): + """Representation of a SwitchBot Cloud sensor entity.""" + + def __init__( + self, + api: SwitchBotAPI, + device: Device, + coordinator: SwitchBotCoordinator, + description: SensorEntityDescription, + ) -> None: + """Initialize SwitchBot Cloud sensor entity.""" + super().__init__(api, device, coordinator) + self.entity_description = description + self._attr_unique_id = f"{device.device_id}_{description.key}" + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + if not self.coordinator.data: + return + self._attr_native_value = self.coordinator.data.get(self.entity_description.key) + self.async_write_ha_state()