Skip to content

Commit

Permalink
Merge pull request #452 from krahabb/dev
Browse files Browse the repository at this point in the history
Moonlight.2.1
  • Loading branch information
krahabb authored Jun 7, 2024
2 parents ed19dd6 + 4f85695 commit 9538564
Show file tree
Hide file tree
Showing 12 changed files with 939 additions and 47 deletions.
10 changes: 7 additions & 3 deletions custom_components/meross_lan/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ class MerossFlowHandlerMixin(
"host": "",
}

profile_placeholders = {}
profile_placeholders = {
"email": "",
"placeholder": "",
}

_is_keyerror: bool = False
_httpclient: MerossHttpClient | None = None
Expand Down Expand Up @@ -319,6 +322,7 @@ async def async_step_profile(self, user_input=None):
domain=mlc.DOMAIN,
title=profile_config[mc.KEY_EMAIL],
data=profile_config,
options={}, # required since 2024.6
source=ce.SOURCE_USER,
unique_id=unique_id,
)
Expand Down Expand Up @@ -1304,7 +1308,7 @@ async def async_step_bind(self, user_input=None):
)

async def async_step_bind_finalize(self, user_input=None):
self.hass.config_entries.async_schedule_reload(self.config_entry_id)
ConfigEntriesHelper(self.hass).schedule_reload(self.config_entry_id)
return self.async_create_entry(data=None) # type: ignore

async def async_step_unbind(self, user_input=None):
Expand Down Expand Up @@ -1359,5 +1363,5 @@ def finish_options_flow(
"""Used in OptionsFlow to terminate and exit (with save)."""
self.hass.config_entries.async_update_entry(self.config_entry, data=config)
if reload:
self.hass.config_entries.async_schedule_reload(self.config_entry_id)
ConfigEntriesHelper(self.hass).schedule_reload(self.config_entry_id)
return self.async_create_entry(data=None) # type: ignore
24 changes: 23 additions & 1 deletion custom_components/meross_lan/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from enum import StrEnum
import importlib
import logging
import sys
from time import gmtime, time
import typing
import zoneinfo
Expand Down Expand Up @@ -165,6 +164,12 @@ def get_type_and_id(unique_id: str | None):


class ConfigEntriesHelper:
"""
Helpers and compatibility layer (among HA cores) for Hass ConfigEntries
"""

# TODO: move to a static class model

__slots__ = (
"config_entries",
"_entries",
Expand Down Expand Up @@ -200,6 +205,23 @@ def get_config_flow(self, unique_id: str):
return progress
return None

def schedule_reload(self, entry_id: str):
"""Pre HA core 2024.2 compatibility layer"""
_async_schedule_reload = getattr(
self.config_entries, "async_schedule_reload", None
)
if _async_schedule_reload:
_async_schedule_reload(entry_id)
else:
"""Schedule a config entry to be reloaded."""
if entry := self.config_entries.async_get_entry(entry_id):
entry.async_cancel_retry_setup()
Loggable.hass.async_create_task(
self.config_entries.async_reload(entry_id),
f"config entry reload {entry.title} {entry.domain} {entry.entry_id}",
)



def getLogger(name):
"""
Expand Down
4 changes: 2 additions & 2 deletions custom_components/meross_lan/helpers/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from . import LOGGER, Loggable, getLogger, schedule_callback
from . import LOGGER, ConfigEntriesHelper, Loggable, getLogger, schedule_callback
from ..const import (
CONF_ALLOW_MQTT_PUBLISH,
CONF_CREATE_DIAGNOSTIC_ENTITIES,
Expand Down Expand Up @@ -307,7 +307,7 @@ def schedule_entry_reload(self, delay: float = 0):
self._unsub_entry_reload.cancel()
self._unsub_entry_reload = self.schedule_callback(
delay,
self.hass.config_entries.async_schedule_reload,
ConfigEntriesHelper(self.hass).schedule_reload,
self.config_entry_id,
)

Expand Down
11 changes: 8 additions & 3 deletions custom_components/meross_lan/helpers/namespaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ def handle_exception(self, exception: Exception, function_name: str, payload):
self.__class__.__name__,
self.namespace,
function_name,
device.loggable_any(payload),
str(device.loggable_any(payload)),
timeout=604800
)

def _handle_list(self, header, payload):
Expand Down Expand Up @@ -387,8 +388,12 @@ async def async_poll_all(self, device: "MerossDevice", epoch: float):
# query specific namespaces instead of NS_ALL since we hope this is
# better (less overhead/http sessions) together with ns_multiple packing
for digest_poller in device.digest_pollers:
digest_poller.lastrequest = epoch
await device.async_request_poll(digest_poller)
if digest_poller.entities:
# don't query if digest key/namespace hasn't any entity registered
# this also prevents querying a somewhat 'malformed' ToggleX reply
# appearing in an mrs100 (#447)
digest_poller.lastrequest = epoch
await device.async_request_poll(digest_poller)

async def async_poll_default(self, device: "MerossDevice", epoch: float):
"""
Expand Down
2 changes: 1 addition & 1 deletion custom_components/meross_lan/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@
"/appliance/+/publish"
],
"requirements": [],
"version": "5.2.0"
"version": "5.2.1"
}
5 changes: 3 additions & 2 deletions custom_components/meross_lan/merossclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

from . import const as mc, namespaces as mn

MerossNamespaceType = str
MerossMethodType = str
MerossHeaderType = typing.TypedDict(
"MerossHeaderType",
{
Expand All @@ -32,9 +34,8 @@
MerossMessageType = typing.TypedDict(
"MerossMessageType", {"header": MerossHeaderType, "payload": MerossPayloadType}
)
MerossRequestType = tuple[str, str, MerossPayloadType]
MerossRequestType = tuple[MerossNamespaceType, MerossMethodType, MerossPayloadType]
KeyType = typing.Union[MerossHeaderType, str, None]
ResponseCallbackType = typing.Callable[[bool, dict, dict], None]


try:
Expand Down
64 changes: 36 additions & 28 deletions emulator/mixins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import typing
from zoneinfo import ZoneInfo

from custom_components.meross_lan import const as mlc
from custom_components.meross_lan.helpers.manager import ConfigEntryManager
from custom_components.meross_lan.merossclient import (
HostAddress,
MerossDeviceDescriptor,
MerossHeaderType,
MerossMessage,
MerossMessageType,
MerossNamespaceType,
MerossPayloadType,
MerossRequest,
build_message,
Expand All @@ -31,7 +33,11 @@


class MerossEmulatorDescriptor(MerossDeviceDescriptor):
namespaces: dict
namespaces: dict[MerossNamespaceType, MerossPayloadType]

__slots__ = (
"namespaces",
)

def __init__(
self,
Expand Down Expand Up @@ -65,7 +71,6 @@ def __init__(
firmware[mc.KEY_PORT] = broker_address.port
firmware.pop(mc.KEY_SECONDSERVER, None)
firmware.pop(mc.KEY_SECONDPORT, None)

if userId:
self.firmware[mc.KEY_USERID] = userId

Expand Down Expand Up @@ -100,26 +105,26 @@ def _import_json(self, f):
return

def _import_tracerow(self, values: list):
# rxtx = values[1]
protocol = values[-4]
method = values[-3]
namespace = values[-2]
data = values[-1]
if method == mc.METHOD_GETACK:
if not isinstance(data, dict):
data = json_loads(data)
if protocol == "auto":
data = {mn.NAMESPACES[namespace].key: data}
self.namespaces[namespace] = data
elif (
method == mc.METHOD_SETACK and namespace == mc.NS_APPLIANCE_CONTROL_MULTIPLE
):
if not isinstance(data, dict):
data = json_loads(data)
for message in data[mc.KEY_MULTIPLE]:
header = message[mc.KEY_HEADER]
if header[mc.KEY_METHOD] == mc.METHOD_GETACK:
self.namespaces[header[mc.KEY_NAMESPACE]] = message[mc.KEY_PAYLOAD]

def _get_data_dict(_data):
return _data if type(_data) is dict else json_loads(_data)

match method:
case mc.METHOD_GETACK:
self.namespaces[namespace] = {mn.NAMESPACES[namespace].key: _get_data_dict(data)} if protocol == mlc.CONF_PROTOCOL_AUTO else _get_data_dict(data)
case mc.METHOD_SETACK:
if namespace == mc.NS_APPLIANCE_CONTROL_MULTIPLE:
for message in _get_data_dict(data)[mc.KEY_MULTIPLE]:
header = message[mc.KEY_HEADER]
if header[mc.KEY_METHOD] == mc.METHOD_GETACK:
self.namespaces[header[mc.KEY_NAMESPACE]] = message[
mc.KEY_PAYLOAD
]



class MerossEmulator:
Expand Down Expand Up @@ -265,11 +270,22 @@ def _handle_message(self, header: MerossHeaderType, payload: MerossPayloadType):
if namespace not in self.descriptor.ability:
raise Exception(f"{namespace} not supported in ability")

if namespace == mc.NS_APPLIANCE_CONTROL_MULTIPLE:
if method != mc.METHOD_SET:
raise Exception(f"{method} not supported for {namespace}")
multiple = []
for message in payload[mc.KEY_MULTIPLE]:
multiple.append(
self._handle_message(
message[mc.KEY_HEADER], message[mc.KEY_PAYLOAD]
)
)
response_method = mc.METHOD_SETACK
response_payload = {mc.KEY_MULTIPLE: multiple}
elif handler := getattr(
self, f"_{method}_{namespace.replace('.', '_')}", None
):
response_method, response_payload = handler(header, payload)

else:
response_method, response_payload = self._handler_default(
method, namespace, payload
Expand Down Expand Up @@ -418,14 +434,6 @@ def _SETACK_Appliance_Control_Bind(self, header, payload):
)
return None, None

def _SET_Appliance_Control_Multiple(self, header, payload):
multiple = []
for message in payload[mc.KEY_MULTIPLE]:
multiple.append(
self._handle_message(message[mc.KEY_HEADER], message[mc.KEY_PAYLOAD])
)
return mc.METHOD_SETACK, {mc.KEY_MULTIPLE: multiple}

def _GET_Appliance_Control_Toggle(self, header, payload):
# only actual example of this usage comes from legacy firmwares
# carrying state in all->control
Expand Down
3 changes: 3 additions & 0 deletions emulator/mixins/rollershutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ def shutdown(self):
transition.shutdown()
super().shutdown()

def _GET_Appliance_Control_ToggleX(self, header, payload):
return mc.METHOD_GETACK, { "channel": 0} # 'strange' format response in #447

def _SET_Appliance_RollerShutter_Position(self, header, payload):
"""payload = { "postion": {"channel": 0, "position": 100}}"""
for p_request in extract_dict_payloads(payload[mc.KEY_POSITION]):
Expand Down
Loading

0 comments on commit 9538564

Please sign in to comment.