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

160 lines
5.1 KiB
Python

import logging
import re
from typing import Dict, List
from meross_iot.controller.device import BaseDevice
from meross_iot.manager import TransportMode
from . import version
from .version import MEROSS_IOT_VERSION
_LOGGER = logging.getLogger(__name__)
# Constants
MEROSS_DEFAULT_CLOUD_API_URL = "https://iot.meross.com"
MEROSS_LOCAL_API_URL = "http://homeassistant.local:2003"
MEROSS_LOCAL_MQTT_BROKER_URI = "homeassistant.local:2001"
MEROSS_LOCAL_MDNS_API_SERVICE_TYPE = "_meross-api._tcp.local."
MEROSS_LOCAL_MDNS_MQTT_SERVICE_TYPE = "_meross-mqtt._tcp.local."
MEROSS_LOCAL_MDNS_SERVICE_TYPES = [MEROSS_LOCAL_MDNS_API_SERVICE_TYPE, MEROSS_LOCAL_MDNS_MQTT_SERVICE_TYPE]
DOMAIN = "meross_cloud"
ATTR_CONFIG = "config"
MANAGER = "manager"
DEVICE_LIST_COORDINATOR = "device_list_coordinator"
LIMITER = "limiter"
CLOUD_HANDLER = "cloud_handler"
MEROSS_MANAGER = "%s.%s" % (DOMAIN, MANAGER)
SENSORS = "sensors"
HA_SWITCH = "switch"
HA_LIGHT = "light"
HA_SENSOR = "sensor"
HA_COVER = "cover"
HA_CLIMATE = "climate"
HA_FAN = "fan"
HA_HUMIDIFIER = "humidifier"
MEROSS_PLATFORMS = (HA_SWITCH, HA_LIGHT, HA_COVER, HA_SENSOR, HA_CLIMATE, HA_HUMIDIFIER)
CONNECTION_TIMEOUT_THRESHOLD = 5
CONF_STORED_CREDS = "stored_credentials"
CONF_MQTT_SKIP_CERT_VALIDATION = "skip_mqtt_cert_validation"
CONF_OVERRIDE_MQTT_ENDPOINT = "override_mqtt_endpoint"
CONF_HTTP_ENDPOINT = "http_api_endpoint"
CONF_WORKING_MODE = "working_mode"
CONF_WORKING_MODE_CLOUD_MODE = "cloud_mode"
CONF_WORKING_MODE_LOCAL_MODE = "local_mode"
CONF_MFA_CODE = "mfa_code"
UNKNOWN_ERROR = "unknown_error"
MULTIPLE_BROKERS_FOUND = "multiple_brokers_found"
MULTIPLE_APIS_FOUND = "multiple_apis_found"
DIFFERENT_HOSTS_FOR_BROKER_AND_API = "different_hosts_for_broker_and_api"
CONF_OPT_CUSTOM_USER_AGENT = "custom_user_agent"
CONF_OPT_LAN = "lan_transport_mode"
CONF_OPT_LAN_MQTT_ONLY = "conf_opt_lan_mqtt_only"
CONF_OPT_LAN_HTTP_FIRST = "conf_opt_lan_http_first"
CONF_OPT_LAN_HTTP_FIRST_ONLY_GET = "conf_opt_lan_http_first_only_get"
HA_SENSOR_POLL_INTERVAL_SECONDS = 30 # HA sensor polling interval
HTTP_UPDATE_INTERVAL = 120 # Meross Cloud "discovery" interval
UNIT_PERCENTAGE = "%"
ATTR_API_CALLS_PER_SECOND = "api_calls_per_second"
ATTR_DELAYED_API_CALLS_PER_SECOND = "delayed_api_calls_per_second"
ATTR_DROPPED_API_CALLS_PER_SECOND = "dropped_api_calls_per_second"
HTTP_API_RE = re.compile("(http://|https://)?([^:]+)(:([0-9]+))?")
DEFAULT_USER_AGENT = f"MerossHA/{version.MEROSS_INTEGRATION_VERSION}"
TRANSPORT_MODES_TO_ENUM = {
CONF_OPT_LAN_MQTT_ONLY: TransportMode.MQTT_ONLY,
CONF_OPT_LAN_HTTP_FIRST: TransportMode.LAN_HTTP_FIRST,
CONF_OPT_LAN_HTTP_FIRST_ONLY_GET: TransportMode.LAN_HTTP_FIRST_ONLY_GET
}
def calculate_id(platform: str, uuid: str, channel: int, supplementary_classifiers: List[str] = None) -> str:
base = "%s:%s:%d" % (platform, uuid, channel)
if supplementary_classifiers is not None:
extrastr = ":".join(supplementary_classifiers)
if extrastr != "":
extrastr = ":" + extrastr
return base + extrastr
return base
def dismiss_notification(hass, notification_id):
hass.async_create_task(
hass.services.async_call(
domain="persistent_notification",
service="dismiss",
service_data={"notification_id": "%s.%s" % (DOMAIN, notification_id)},
)
)
def notify_error(hass, notification_id, title, message):
hass.async_create_task(
hass.services.async_call(
domain="persistent_notification",
service="create",
service_data={
"title": title,
"message": message,
"notification_id": "%s.%s" % (DOMAIN, notification_id),
},
)
)
def log_exception(
message: str = None, logger: logging = None, device: BaseDevice = None
):
if logger is None:
logger = logging.getLogger(__name__)
if message is None:
message = "An exception occurred"
device_info = "<Unavailable>"
if device is not None:
device_info = (
f"\tName: {device.name}\n"
f"\tUUID: {device.uuid}\n"
f"\tType: {device.type}\n\t"
f"HW Version: {device.hardware_version}\n"
f"\tFW Version: {device.firmware_version}"
)
formatted_message = (
f"Error occurred.\n"
f"-------------------------------------\n"
f"Component version: {MEROSS_IOT_VERSION}\n"
f"Device info: \n"
f"{device_info}\n"
f'Error Message: "{message}"'
)
logger.exception(formatted_message)
def invoke_method_or_property(obj, method_or_property):
# We only call the explicit method if the sampled value is older than 10 seconds.
attr = getattr(obj, method_or_property)
if callable(attr):
return attr()
else:
return attr
def extract_subdevice_notification_data(
data: dict, filter_accessor: str, subdevice_id: str
) -> Dict:
# Operate only on relative accessor
context = data.get(filter_accessor)
for notification in context:
if notification.get("id") != subdevice_id:
continue
return notification