Skip to content

Commit

Permalink
add consumption sensor(s) and init for Appliance.Control.ConsumptionH (
Browse files Browse the repository at this point in the history
  • Loading branch information
krahabb committed Oct 10, 2024
1 parent 71373b7 commit 85d84e4
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 76 deletions.
43 changes: 0 additions & 43 deletions custom_components/meross_lan/devices/mss.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,49 +218,6 @@ def namespace_init_electricityx(device: "MerossDevice"):
).register_entity_class(ElectricityXSensor)


class ConsumptionHSensor(MLNumericSensor):

manager: "MerossDevice"
ns = mn.Appliance_Control_ConsumptionH

_attr_suggested_display_precision = 0

__slots__ = ()

def __init__(self, manager: "MerossDevice", channel: object | None):
super().__init__(
manager,
channel,
mc.KEY_CONSUMPTIONH,
self.DeviceClass.ENERGY,
name="Consumption",
)
manager.register_parser_entity(self)

def _parse_consumptionH(self, payload: dict):
"""
{"channel": 1, "total": 958, "data": [{"timestamp": 1721548740, "value": 0}]}
"""
self.update_device_value(payload[mc.KEY_TOTAL])


class ConsumptionHNamespaceHandler(NamespaceHandler):
"""
This namespace carries hourly statistics (over last 24 ours?) of energy consumption
It appeared in a mts200 and an em06 (Refoss). We're actually not registering for parsing
though since it looks like just carrying energy consumption (just different sum period)
for which we also usually have ConsumptionX or ElectricityX (for em06).
Nevertheless, it looks tricky since for mts200, the query (payload GET) needs the channel
index while for em06 this isn't necessary (empty query replies full sensor set statistics).
Actual coding, according to what mts200 expects might work badly on em06 (since the query
code setup will use our knowledge of which channels are available and this is not enforced
on em06).
"""

def __init__(self, device: "MerossDevice"):
super().__init__(device, mn.Appliance_Control_ConsumptionH)
self.register_entity_class(ConsumptionHSensor, initially_disabled=False)


class ConsumptionXSensor(EntityNamespaceMixin, MLNumericSensor):
ATTR_OFFSET: typing.Final = "offset"
Expand Down
61 changes: 32 additions & 29 deletions custom_components/meross_lan/meross_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,10 @@ def namespace_init_empty(device: "MerossDevice"):
".devices.mss",
"namespace_init_electricityx",
),
mn.Appliance_Control_ConsumptionH.name: (
".sensor",
"namespace_init_consumptionh",
),
mn.Appliance_Control_ConsumptionX.name: (".devices.mss", "ConsumptionXSensor"),
mn.Appliance_Control_Fan.name: (".fan", "namespace_init_fan"),
mn.Appliance_Control_FilterMaintenance.name: (
Expand Down Expand Up @@ -530,6 +534,34 @@ async def async_init(self):
except:
pass

for namespace, ns_init_func in MerossDevice.NAMESPACE_INIT.items():
if namespace not in descriptor.ability:
continue
try:
try:
ns_init_func(self)
except TypeError:
try:
ns_init_func = getattr(
await async_import_module(ns_init_func[0]),
ns_init_func[1],
)
except Exception as exception:
self.log_exception(
self.WARNING,
exception,
"loading namespace initializer for %s",
namespace,
)
ns_init_func = MerossDevice.namespace_init_empty
MerossDevice.NAMESPACE_INIT[namespace] = ns_init_func
ns_init_func(self)

except Exception as exception:
self.log_exception(
self.WARNING, exception, "initializing namespace %s", namespace
)

for key_digest, _digest in (
descriptor.digest.items() or descriptor.control.items()
):
Expand Down Expand Up @@ -571,35 +603,6 @@ async def async_init(self):
)
self.digest_handlers[key_digest] = MerossDevice.digest_parse_empty

for namespace, ns_init_func in MerossDevice.NAMESPACE_INIT.items():
if namespace not in descriptor.ability:
continue
try:
try:
ns_init_func(self)
except TypeError:
try:
# _ns_init_descriptor = MerossDevice.NAMESPACE_INIT[namespace]
ns_init_func = getattr(
await async_import_module(ns_init_func[0]),
ns_init_func[1],
)
except Exception as exception:
self.log_exception(
self.WARNING,
exception,
"loading namespace initializer for %s",
namespace,
)
ns_init_func = MerossDevice.namespace_init_empty
MerossDevice.NAMESPACE_INIT[namespace] = ns_init_func
ns_init_func(self)

except Exception as exception:
self.log_exception(
self.WARNING, exception, "initializing namespace %s", namespace
)

def start(self):
# called by async_setup_entry after the entities have been registered
# here we'll register mqtt listening (in case) and start polling after
Expand Down
11 changes: 7 additions & 4 deletions custom_components/meross_lan/merossclient/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,11 +444,10 @@

TYPE_MSS310 = "mss310" # smart plug with energy meter
TYPE_NAME_MAP[TYPE_MSS310] = "Smart Plug"
TYPE_MSS560 = "mss560"
TYPE_NAME_MAP[TYPE_MSS560] = "Smart Dimmer Switch"
TYPE_MSS570 = "mss570"
TYPE_NAME_MAP[TYPE_MSS570] = TYPE_NAME_MAP[TYPE_MSS560]
TYPE_NAME_MAP["mss560"] = "Smart Dimmer Switch"
TYPE_NAME_MAP["mss570"] = TYPE_NAME_MAP["mss560"]
TYPE_NAME_MAP["mss"] = "Smart Switch"
TYPE_NAME_MAP["mop320"] = "Smart Outdoor Plug"

TYPE_MTS100 = "mts100" # Smart thermostat over hub
TYPE_MTS100V3 = "mts100v3" # Smart thermostat over hub
Expand Down Expand Up @@ -483,6 +482,10 @@
TYPE_MS600 = "ms600"
TYPE_NAME_MAP[TYPE_MS600] = "Smart Presence Sensor"

# REFOSS device types
TYPE_EM06 = "em06"
TYPE_NAME_MAP[TYPE_EM06] = "Smart Energy Monitor"

#
# HUB helpers symbols
#
Expand Down
70 changes: 70 additions & 0 deletions custom_components/meross_lan/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,73 @@ def __init__(self, device: "MerossDevice"):
mn.Appliance_Control_FilterMaintenance,
)
MLFilterMaintenanceSensor(device, 0)


class ConsumptionHSensor(MLNumericSensor):

manager: "MerossDevice"
ns = mn.Appliance_Control_ConsumptionH

_attr_suggested_display_precision = 0

__slots__ = ()

def __init__(self, manager: "MerossDevice", channel: object | None):
super().__init__(
manager,
channel,
mc.KEY_CONSUMPTIONH,
self.DeviceClass.ENERGY,
name="Consumption",
)
manager.register_parser_entity(self)

def _parse_consumptionH(self, payload: dict):
"""
{"channel": 1, "total": 958, "data": [{"timestamp": 1721548740, "value": 0}]}
"""
self.update_device_value(payload[mc.KEY_TOTAL])


def namespace_init_consumptionh(device: "MerossDevice"):
"""
This namespace carries hourly statistics (over last 24 ours?) of energy consumption
Appearing in: mts200 - em06 (Refoss) - mop320
This ns looks tricky since for mts200, the query (payload GET) needs the channel
index while for em06 this isn't necessary (empty query replies full sensor set statistics).
Actual coding, according to what mts200 expects might work badly on em06 (since the query
code setup will use our knowledge of which channels are available and this is not enforced
on em06).
Also, we need to come up with a reasonable euristic on which channels are available
mts200: 1 (channel 0)
mop320: 3 (channel 0 - 1 - 2) even tho it only has 2 metering channels (0 looks toggling both)
em06: 6 channels (but the query works without setting any)
"""
NamespaceHandler(
device,
mn.Appliance_Control_ConsumptionH,
).register_entity_class(ConsumptionHSensor, initially_disabled=False)
if device.descriptor.type.startswith(mc.TYPE_EM06):
# em06 doesn't provide any digest info
for channel in range(1, 7):
ConsumptionHSensor(device, channel)
else:
# current approach is to build a sensor for any
# appearing channel index in digest
channels = set()

def _scan_digest(digest: dict):
if mc.KEY_CHANNEL in digest:
channels.add(digest[mc.KEY_CHANNEL])
else:
for value in digest.values():
if type(value) is dict:
_scan_digest(value)
elif type(value) is list:
for value_item in value:
if type(value_item) is dict:
_scan_digest(value_item)

_scan_digest(device.descriptor.digest)
for channel in channels:
ConsumptionHSensor(device, channel)

0 comments on commit 85d84e4

Please sign in to comment.