Files
2025-09-11 10:47:34 +03:00

182 lines
6.6 KiB
Python

import logging
from datetime import datetime
from typing import Optional, Dict
from homeassistant.core import HomeAssistant
from meross_iot.controller.device import BaseDevice
from meross_iot.controller.mixins.consumption import ConsumptionXMixin
from meross_iot.controller.mixins.electricity import ElectricityMixin
from meross_iot.controller.mixins.garage import GarageOpenerMixin
from meross_iot.controller.mixins.light import LightMixin
from meross_iot.controller.mixins.dnd import SystemDndMixin
from meross_iot.controller.mixins.toggle import ToggleXMixin, ToggleMixin
from meross_iot.manager import MerossManager
from meross_iot.model.http.device import HttpDeviceInfo
from meross_iot.model.enums import DNDMode
# Conditional import for switch device
from homeassistant.components.switch import SwitchEntity
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import MerossDevice
from .common import (DOMAIN, MANAGER, DEVICE_LIST_COORDINATOR, HA_SWITCH)
_LOGGER = logging.getLogger(__name__)
class MerossSwitchDevice(ToggleXMixin, BaseDevice):
"""
Type hints helper
"""
pass
class MerossDndDevice(SystemDndMixin, BaseDevice):
"""
Type hints helper
"""
pass
class SwitchEntityWrapper(MerossDevice, SwitchEntity):
"""Wrapper class to adapt the Meross switches into the Homeassistant platform"""
_device: MerossSwitchDevice
def __init__(self,
channel: int,
device: MerossSwitchDevice,
device_list_coordinator: DataUpdateCoordinator[Dict[str, HttpDeviceInfo]]):
super().__init__(
device=device,
channel=channel,
device_list_coordinator=device_list_coordinator,
platform=HA_SWITCH)
# Device properties
self._last_power_sample = None
self._daily_consumption = None
async def async_update(self):
if self.online:
await super().async_update()
# If the device supports power reading, update it
if isinstance(self._device, ElectricityMixin):
self._last_power_sample = await self._device.async_get_instant_metrics(channel=self._channel_id)
if isinstance(self._device, ConsumptionXMixin):
self._daily_consumption = await self._device.async_get_daily_power_consumption(channel=self._channel_id)
@property
def is_on(self) -> bool:
dev = self._device
return dev.is_on(channel=self._channel_id)
async def async_turn_off(self, **kwargs) -> None:
dev = self._device
await dev.async_turn_off(channel=self._channel_id, skip_rate_limits=True)
async def async_turn_on(self, **kwargs) -> None:
dev = self._device
await dev.async_turn_on(channel=self._channel_id, skip_rate_limits=True)
@property
def current_power_w(self) -> Optional[float]:
if self._last_power_sample is not None:
return self._last_power_sample.power
@property
def today_energy_kwh(self) -> Optional[float]:
if self._daily_consumption is not None:
today = datetime.today()
total = 0
daystart = datetime(year=today.year, month=today.month, day=today.day, hour=0, second=0)
for x in self._daily_consumption:
if x['date'] == daystart:
total = x['total_consumption_kwh']
return total
class DndEntityWrapper(MerossDevice, SwitchEntity):
"""Wrapper class to adapt the Meross switches into the Homeassistant platform"""
_device: MerossDndDevice
# The DNDMode change does not trigger any push notification, so we cannot we
_attr_should_poll = True
_dnd_mode: Optional[DNDMode] = None
def __init__(self,
device: MerossDndDevice,
device_list_coordinator: DataUpdateCoordinator[Dict[str, HttpDeviceInfo]]):
super().__init__(
device=device,
channel=-1, # DND devices do not relate to channels
device_list_coordinator=device_list_coordinator,
platform=HA_SWITCH,
override_channel_name="Do Not Disturb")
async def async_update(self):
if self.online:
await super().async_update()
self._dnd_mode = await self._device.async_get_dnd_mode()
@property
def is_on(self) -> bool | None:
if self._dnd_mode is None:
return None
return self._dnd_mode == DNDMode.DND_DISABLED
async def async_turn_off(self, **kwargs) -> None:
dev = self._device
await dev.set_dnd_mode(mode=DNDMode.DND_ENABLED, skip_rate_limits=True)
self._dnd_mode = DNDMode.DND_ENABLED
async def async_turn_on(self, **kwargs) -> None:
dev = self._device
await dev.set_dnd_mode(mode=DNDMode.DND_DISABLED, skip_rate_limits=True)
self._dnd_mode = DNDMode.DND_DISABLED
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
def entity_adder_callback():
"""Discover and adds new Meross entities"""
manager: MerossManager = hass.data[DOMAIN][MANAGER] # type
coordinator = hass.data[DOMAIN][DEVICE_LIST_COORDINATOR]
devices = manager.find_devices()
new_entities = []
# Identify all the devices that expose the Toggle or ToggleX capabilities
devs = filter(lambda d: isinstance(d, ToggleXMixin) or isinstance(d, ToggleMixin), devices)
# Exclude garage openers, lights.
devs = filter(lambda d: not (isinstance(d, GarageOpenerMixin) or isinstance(d, LightMixin)), devs)
for d in devs:
channels = [c.index for c in d.channels] if len(d.channels) > 0 else [0]
for channel_index in channels:
w = SwitchEntityWrapper(device=d, channel=channel_index,
device_list_coordinator=coordinator)
if w.unique_id not in hass.data[DOMAIN]["ADDED_ENTITIES_IDS"]:
new_entities.append(w)
dnd_switches = filter(lambda d: isinstance(d, SystemDndMixin), devices)
for d in dnd_switches:
w = DndEntityWrapper(device=d, device_list_coordinator=coordinator)
if w.unique_id not in hass.data[DOMAIN]["ADDED_ENTITIES_IDS"]:
new_entities.append(w)
async_add_entities(new_entities, True)
coordinator = hass.data[DOMAIN][DEVICE_LIST_COORDINATOR]
coordinator.async_add_listener(entity_adder_callback)
# Run the entity adder a first time during setup
entity_adder_callback()
# TODO: Implement entry unload
# TODO: Unload entry
# TODO: Remove entry
def setup_platform(hass, config, async_add_entities, discovery_info=None):
pass