初始化config文件
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,188 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
Common utilities.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
from os import path
|
||||
import random
|
||||
from typing import Any, Optional
|
||||
import hashlib
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import Request, urlopen
|
||||
from paho.mqtt.matcher import MQTTMatcher
|
||||
import yaml
|
||||
from slugify import slugify
|
||||
|
||||
MIOT_ROOT_PATH: str = path.dirname(path.abspath(__file__))
|
||||
|
||||
|
||||
def gen_absolute_path(relative_path: str) -> str:
|
||||
"""Generate an absolute path."""
|
||||
return path.join(MIOT_ROOT_PATH, relative_path)
|
||||
|
||||
|
||||
def calc_group_id(uid: str, home_id: str) -> str:
|
||||
"""Calculate the group ID based on a user ID and a home ID."""
|
||||
return hashlib.sha1(
|
||||
f'{uid}central_service{home_id}'.encode('utf-8')).hexdigest()[:16]
|
||||
|
||||
|
||||
def load_json_file(json_file: str) -> dict:
|
||||
"""Load a JSON file."""
|
||||
with open(json_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def load_yaml_file(yaml_file: str) -> dict:
|
||||
"""Load a YAML file."""
|
||||
with open(yaml_file, 'r', encoding='utf-8') as f:
|
||||
return yaml.load(f, Loader=yaml.FullLoader)
|
||||
|
||||
|
||||
def randomize_int(value: int, ratio: float) -> int:
|
||||
"""Randomize an integer value."""
|
||||
return int(value * (1 - ratio + random.random()*2*ratio))
|
||||
|
||||
|
||||
def randomize_float(value: float, ratio: float) -> float:
|
||||
"""Randomize a float value."""
|
||||
return value * (1 - ratio + random.random()*2*ratio)
|
||||
|
||||
|
||||
def slugify_name(name: str, separator: str = '_') -> str:
|
||||
"""Slugify a name."""
|
||||
return slugify(name, separator=separator)
|
||||
|
||||
|
||||
def slugify_did(cloud_server: str, did: str) -> str:
|
||||
"""Slugify a device id."""
|
||||
return slugify(f'{cloud_server}_{did}', separator='_')
|
||||
|
||||
|
||||
class MIoTMatcher(MQTTMatcher):
|
||||
"""MIoT Pub/Sub topic matcher."""
|
||||
|
||||
def iter_all_nodes(self) -> Any:
|
||||
"""Return an iterator on all nodes with their paths and contents."""
|
||||
def rec(node, path_):
|
||||
# pylint: disable=protected-access
|
||||
if node._content:
|
||||
yield ('/'.join(path_), node._content)
|
||||
for part, child in node._children.items():
|
||||
yield from rec(child, path_ + [part])
|
||||
return rec(self._root, [])
|
||||
|
||||
def get(self, topic: str) -> Optional[Any]:
|
||||
try:
|
||||
return self[topic]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
class MIoTHttp:
|
||||
"""MIoT Common HTTP API."""
|
||||
@staticmethod
|
||||
def get(
|
||||
url: str, params: Optional[dict] = None, headers: Optional[dict] = None
|
||||
) -> Optional[str]:
|
||||
full_url = url
|
||||
if params:
|
||||
encoded_params = urlencode(params)
|
||||
full_url = f'{url}?{encoded_params}'
|
||||
request = Request(full_url, method='GET', headers=headers or {})
|
||||
content: Optional[bytes] = None
|
||||
with urlopen(request) as response:
|
||||
content = response.read()
|
||||
return str(content, 'utf-8') if content else None
|
||||
|
||||
@staticmethod
|
||||
def get_json(
|
||||
url: str, params: Optional[dict] = None, headers: Optional[dict] = None
|
||||
) -> Optional[dict]:
|
||||
response = MIoTHttp.get(url, params, headers)
|
||||
return json.loads(response) if response else None
|
||||
|
||||
@staticmethod
|
||||
def post(
|
||||
url: str, data: Optional[dict] = None, headers: Optional[dict] = None
|
||||
) -> Optional[str]:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def post_json(
|
||||
url: str, data: Optional[dict] = None, headers: Optional[dict] = None
|
||||
) -> Optional[dict]:
|
||||
response = MIoTHttp.post(url, data, headers)
|
||||
return json.loads(response) if response else None
|
||||
|
||||
@staticmethod
|
||||
async def get_async(
|
||||
url: str, params: Optional[dict] = None, headers: Optional[dict] = None,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> Optional[str]:
|
||||
# TODO: Use aiohttp
|
||||
ev_loop = loop or asyncio.get_running_loop()
|
||||
return await ev_loop.run_in_executor(
|
||||
None, MIoTHttp.get, url, params, headers)
|
||||
|
||||
@staticmethod
|
||||
async def get_json_async(
|
||||
url: str, params: Optional[dict] = None, headers: Optional[dict] = None,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> Optional[dict]:
|
||||
ev_loop = loop or asyncio.get_running_loop()
|
||||
return await ev_loop.run_in_executor(
|
||||
None, MIoTHttp.get_json, url, params, headers)
|
||||
|
||||
@ staticmethod
|
||||
async def post_async(
|
||||
url: str, data: Optional[dict] = None, headers: Optional[dict] = None,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> Optional[str]:
|
||||
ev_loop = loop or asyncio.get_running_loop()
|
||||
return await ev_loop.run_in_executor(
|
||||
None, MIoTHttp.post, url, data, headers)
|
||||
@@ -0,0 +1,160 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
Constants.
|
||||
"""
|
||||
DOMAIN: str = 'xiaomi_home'
|
||||
DEFAULT_NAME: str = 'Xiaomi Home'
|
||||
|
||||
DEFAULT_NICK_NAME: str = 'Xiaomi'
|
||||
|
||||
MIHOME_HTTP_API_TIMEOUT: int = 30
|
||||
MIHOME_MQTT_KEEPALIVE: int = 60
|
||||
# seconds, 3 days
|
||||
MIHOME_CERT_EXPIRE_MARGIN: int = 3600*24*3
|
||||
|
||||
NETWORK_REFRESH_INTERVAL: int = 30
|
||||
|
||||
OAUTH2_CLIENT_ID: str = '2882303761520251711'
|
||||
OAUTH2_AUTH_URL: str = 'https://account.xiaomi.com/oauth2/authorize'
|
||||
DEFAULT_OAUTH2_API_HOST: str = 'ha.api.io.mi.com'
|
||||
DEFAULT_CLOUD_BROKER_HOST: str = 'ha.mqtt.io.mi.com'
|
||||
|
||||
# seconds, 14 days
|
||||
SPEC_STD_LIB_EFFECTIVE_TIME = 3600*24*14
|
||||
# seconds, 14 days
|
||||
MANUFACTURER_EFFECTIVE_TIME = 3600*24*14
|
||||
|
||||
SUPPORTED_PLATFORMS: list = [
|
||||
'binary_sensor',
|
||||
'button',
|
||||
'climate',
|
||||
'cover',
|
||||
'device_tracker',
|
||||
'event',
|
||||
'fan',
|
||||
'humidifier',
|
||||
'light',
|
||||
'media_player',
|
||||
'notify',
|
||||
'number',
|
||||
'select',
|
||||
'sensor',
|
||||
'switch',
|
||||
'text',
|
||||
'vacuum',
|
||||
'water_heater',
|
||||
]
|
||||
|
||||
UNSUPPORTED_MODELS: list = [
|
||||
'chuangmi.ir.v2',
|
||||
'era.airp.cwb03',
|
||||
'hmpace.motion.v6nfc',
|
||||
'k0918.toothbrush.t700'
|
||||
]
|
||||
|
||||
DEFAULT_CLOUD_SERVER: str = 'cn'
|
||||
CLOUD_SERVERS: dict = {
|
||||
'cn': '中国大陆',
|
||||
'de': 'Europe',
|
||||
'i2': 'India',
|
||||
'ru': 'Russia',
|
||||
'sg': 'Singapore',
|
||||
'us': 'United States'
|
||||
}
|
||||
|
||||
SUPPORT_CENTRAL_GATEWAY_CTRL: list = ['cn']
|
||||
|
||||
DEFAULT_INTEGRATION_LANGUAGE: str = 'en'
|
||||
INTEGRATION_LANGUAGES = {
|
||||
'de': 'Deutsch',
|
||||
'en': 'English',
|
||||
'es': 'Español',
|
||||
'fr': 'Français',
|
||||
'it': 'Italiano',
|
||||
'ja': '日本語',
|
||||
'nl': 'Nederlands',
|
||||
'pt': 'Português',
|
||||
'pt-BR': 'Português (Brasil)',
|
||||
'ru': 'Русский',
|
||||
'tr': 'Türkçe',
|
||||
'zh-Hans': '简体中文',
|
||||
'zh-Hant': '繁體中文'
|
||||
}
|
||||
|
||||
DEFAULT_COVER_DEAD_ZONE_WIDTH: int = 0
|
||||
MIN_COVER_DEAD_ZONE_WIDTH: int = 0
|
||||
MAX_COVER_DEAD_ZONE_WIDTH: int = 5
|
||||
|
||||
DEFAULT_CTRL_MODE: str = 'auto'
|
||||
|
||||
# Registered in Xiaomi OAuth 2.0 Service
|
||||
# DO NOT CHANGE UNLESS YOU HAVE AN ADMINISTRATOR PERMISSION
|
||||
OAUTH_REDIRECT_URL: str = 'http://homeassistant.local:8123'
|
||||
|
||||
MIHOME_CA_CERT_STR: str = '-----BEGIN CERTIFICATE-----\n' \
|
||||
'MIIBazCCAQ+gAwIBAgIEA/UKYDAMBggqhkjOPQQDAgUAMCIxEzARBgNVBAoTCk1p\n' \
|
||||
'amlhIFJvb3QxCzAJBgNVBAYTAkNOMCAXDTE2MTEyMzAxMzk0NVoYDzIwNjYxMTEx\n' \
|
||||
'MDEzOTQ1WjAiMRMwEQYDVQQKEwpNaWppYSBSb290MQswCQYDVQQGEwJDTjBZMBMG\n' \
|
||||
'ByqGSM49AgEGCCqGSM49AwEHA0IABL71iwLa4//4VBqgRI+6xE23xpovqPCxtv96\n' \
|
||||
'2VHbZij61/Ag6jmi7oZ/3Xg/3C+whglcwoUEE6KALGJ9vccV9PmjLzAtMAwGA1Ud\n' \
|
||||
'EwQFMAMBAf8wHQYDVR0OBBYEFJa3onw5sblmM6n40QmyAGDI5sURMAwGCCqGSM49\n' \
|
||||
'BAMCBQADSAAwRQIgchciK9h6tZmfrP8Ka6KziQ4Lv3hKfrHtAZXMHPda4IYCIQCG\n' \
|
||||
'az93ggFcbrG9u2wixjx1HKW4DUA5NXZG0wWQTpJTbQ==\n' \
|
||||
'-----END CERTIFICATE-----\n' \
|
||||
'-----BEGIN CERTIFICATE-----\n' \
|
||||
'MIIBjzCCATWgAwIBAgIBATAKBggqhkjOPQQDAjAiMRMwEQYDVQQKEwpNaWppYSBS\n' \
|
||||
'b290MQswCQYDVQQGEwJDTjAgFw0yMjA2MDkxNDE0MThaGA8yMDcyMDUyNzE0MTQx\n' \
|
||||
'OFowLDELMAkGA1UEBhMCQ04xHTAbBgNVBAoMFE1JT1QgQ0VOVFJBTCBHQVRFV0FZ\n' \
|
||||
'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdYrzbnp/0x/cZLZnuEDXTFf8mhj4\n' \
|
||||
'CVpZPwgj9e9Ve5r3K7zvu8Jjj7JF1JjQYvEC6yhp1SzBgglnK4L8xQzdiqNQME4w\n' \
|
||||
'HQYDVR0OBBYEFCf9+YBU7pXDs6K6CAQPRhlGJ+cuMB8GA1UdIwQYMBaAFJa3onw5\n' \
|
||||
'sblmM6n40QmyAGDI5sURMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIh\n' \
|
||||
'AKUv+c8v98vypkGMTzMwckGjjVqTef8xodsy6PhcSCq+AiA/n9mDs62hAo5zXyJy\n' \
|
||||
'Bs1s7mqXPf1XgieoxIvs1MqyiA==\n' \
|
||||
'-----END CERTIFICATE-----\n'
|
||||
|
||||
MIHOME_CA_CERT_SHA256: str = \
|
||||
'8b7bf306be3632e08b0ead308249e5f2b2520dc921ad143872d5fcc7c68d6759'
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "Geräte",
|
||||
"found_central_gateway": ", lokales zentrales Gateway gefunden",
|
||||
"without_room": "Kein Raum zugewiesen",
|
||||
"no_display": "nicht anzeigen"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "automatisch",
|
||||
"cloud": "Cloud"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "ODER-Logik",
|
||||
"and": "UND-Logik"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "ausschließen",
|
||||
"include": "einschließen"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "yunyi-Gerät",
|
||||
"2": "Cloud-Gerät",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "virtuelles Gerät",
|
||||
"6": "BLE",
|
||||
"7": "lokaler AP",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "andere",
|
||||
"10": "Funktions-Plug-in",
|
||||
"11": "Zellnetz",
|
||||
"12": "Kabel",
|
||||
"13": "NB-IoT",
|
||||
"14": "Drittanbieter-Cloud-Zugriff",
|
||||
"15": "Infrarot-Fernbedienungsgerät",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "virtuelle Gerätegruppe",
|
||||
"18": "Gateway-Untergerät",
|
||||
"19": "Sicherheitsstufe Gateway-Untergerät",
|
||||
"22": "PLC",
|
||||
"23": "nur Kabel",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+Zellnetz"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "nicht synchronisieren",
|
||||
"home_room": "Hausname und Raumname (Xiaomi Home Schlafzimmer)",
|
||||
"room": "Raumname (Schlafzimmer)",
|
||||
"home": "Hausname (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "aktivieren",
|
||||
"disable": "deaktivieren"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Textsensor-Entität",
|
||||
"bool": "Binärsensor-Entität"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "hinzufügen",
|
||||
"del": "nicht verfügbar",
|
||||
"offline": "offline"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[Hinweis]** Es wurden mehrere Netzwerkkarten erkannt, die möglicherweise mit demselben Netzwerk verbunden sind. Bitte achten Sie auf die Auswahl.",
|
||||
"net_unavailable": "Schnittstelle nicht verfügbar"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "Authentifizierung erfolgreich",
|
||||
"content": "Bitte schließen Sie diese Seite und kehren Sie zur Kontoauthentifizierungsseite zurück, um auf „Weiter“ zu klicken.",
|
||||
"button": "Schließen"
|
||||
},
|
||||
"fail": {
|
||||
"title": "Authentifizierung fehlgeschlagen",
|
||||
"content": "{error_msg}, bitte schließen Sie diese Seite und kehren Sie zur Kontoauthentifizierungsseite zurück, um den Authentifizierungslink erneut zu klicken.",
|
||||
"button": "Schließen"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "Ungültige Antwortparameter ('code' oder 'state' Feld ist leer)",
|
||||
"-10101": "Übergebenes 'state' Feld stimmt nicht überein"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "Ungültige Authentifizierungsinformationen, Cloud-Verbindung nicht verfügbar, bitte betreten Sie die Xiaomi Home-Integrationsseite und klicken Sie auf 'Optionen', um die Authentifizierung erneut durchzuführen",
|
||||
"invalid_device_cache": "Ungültige Gerätecache-Informationen, bitte betreten Sie die Xiaomi Home-Integrationsseite und klicken Sie auf 'Optionen->Geräteliste aktualisieren', um den lokalen Gerätecache zu aktualisieren",
|
||||
"invalid_cert_info": "Ungültiges Benutzerzertifikat, lokale zentrale Verbindung nicht verfügbar, bitte betreten Sie die Xiaomi Home-Integrationsseite und klicken Sie auf 'Optionen', um die Authentifizierung erneut durchzuführen",
|
||||
"device_cloud_error": "Fehler beim Abrufen von Geräteinformationen aus der Cloud, bitte überprüfen Sie die lokale Netzwerkverbindung",
|
||||
"xiaomi_home_error_title": "Xiaomi Home-Integrationsfehler",
|
||||
"xiaomi_home_error": "Fehler **{nick_name}({uid}, {cloud_server})** festgestellt, bitte betreten Sie die Optionen-Seite, um die Konfiguration erneut durchzuführen.\n\n**Fehlermeldung**: \n{message}",
|
||||
"device_list_changed_title": "Xiaomi Home-Geräteliste geändert",
|
||||
"device_list_changed": "Änderung der Geräteinformationen **{nick_name}({uid}, {cloud_server})** festgestellt, bitte betreten Sie die Integrations-Optionen-Seite, klicken Sie auf 'Optionen->Geräteliste aktualisieren', um den lokalen Gerätecache zu aktualisieren.\n\nAktueller Netzwerkstatus: {network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} neue Geräte:** \n{message}",
|
||||
"device_list_del": "\n**{count} Geräte nicht verfügbar:** \n{message}",
|
||||
"device_list_offline": "\n**{count} Geräte offline:** \n{message}",
|
||||
"network_status_online": "Online",
|
||||
"network_status_offline": "Offline",
|
||||
"central_state_changed_title": "Verbindungsstatus des Zentral-Gateways",
|
||||
"central_state_changed": "**{nick_name}({uid}, {cloud_server})** Lokale Verbindungsstrecke des Zentral-Gateways: {conn_status}",
|
||||
"central_state_connected": "verbunden",
|
||||
"central_state_disconnected": "getrennt",
|
||||
"device_exec_error": "Fehler bei der Ausführung"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "Unbekannter Fehler",
|
||||
"-10001": "Dienst nicht verfügbar",
|
||||
"-10002": "Ungültiger Parameter",
|
||||
"-10003": "Unzureichende Ressourcen",
|
||||
"-10004": "Interner Fehler",
|
||||
"-10005": "Unzureichende Berechtigungen",
|
||||
"-10006": "Ausführungszeitüberschreitung",
|
||||
"-10007": "Gerät offline oder nicht vorhanden",
|
||||
"-10020": "Nicht autorisiert (OAuth2)",
|
||||
"-10030": "Ungültiges Token (HTTP)",
|
||||
"-10040": "Ungültiges Nachrichtenformat",
|
||||
"-10050": "Ungültiges Zertifikat",
|
||||
"-704000000": "Unbekannter Fehler",
|
||||
"-704010000": "Nicht autorisiert (Gerät wurde möglicherweise gelöscht)",
|
||||
"-704014006": "Gerätebeschreibung nicht gefunden",
|
||||
"-704030013": "Eigenschaft nicht lesbar",
|
||||
"-704030023": "Eigenschaft nicht beschreibbar",
|
||||
"-704030033": "Eigenschaft nicht abonnierbar",
|
||||
"-704040002": "Dienst existiert nicht",
|
||||
"-704040003": "Eigenschaft existiert nicht",
|
||||
"-704040004": "Ereignis existiert nicht",
|
||||
"-704040005": "Aktion existiert nicht",
|
||||
"-704040999": "Funktion nicht online",
|
||||
"-704042001": "Gerät existiert nicht",
|
||||
"-704042011": "Gerät offline",
|
||||
"-704053036": "Gerätebetrieb zeitüberschreitung",
|
||||
"-704053100": "Gerät kann diese Operation im aktuellen Zustand nicht ausführen",
|
||||
"-704083036": "Gerätebetrieb zeitüberschreitung",
|
||||
"-704090001": "Gerät existiert nicht",
|
||||
"-704220008": "Ungültige ID",
|
||||
"-704220025": "Aktionsparameteranzahl stimmt nicht überein",
|
||||
"-704220035": "Aktionsparameterfehler",
|
||||
"-704220043": "Eigenschaftswertfehler",
|
||||
"-704222034": "Aktionsrückgabewertfehler",
|
||||
"-705004000": "Unbekannter Fehler",
|
||||
"-705004501": "Unbekannter Fehler",
|
||||
"-705201013": "Eigenschaft nicht lesbar",
|
||||
"-705201015": "Aktionsausführungsfehler",
|
||||
"-705201023": "Eigenschaft nicht beschreibbar",
|
||||
"-705201033": "Eigenschaft nicht abonnierbar",
|
||||
"-706012000": "Unbekannter Fehler",
|
||||
"-706012013": "Eigenschaft nicht lesbar",
|
||||
"-706012015": "Aktionsausführungsfehler",
|
||||
"-706012023": "Eigenschaft nicht beschreibbar",
|
||||
"-706012033": "Eigenschaft nicht abonnierbar",
|
||||
"-706012043": "Eigenschaftswertfehler",
|
||||
"-706014006": "Gerätebeschreibung nicht gefunden"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "Devices",
|
||||
"found_central_gateway": ", Found Local Central Hub Gateway",
|
||||
"without_room": "No room assigned",
|
||||
"no_display": "Do not display"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "Auto",
|
||||
"cloud": "Cloud"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "OR logic",
|
||||
"and": "AND logic"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "Exclude",
|
||||
"include": "Include"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "yunyi device",
|
||||
"2": "Cloud device",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "Virtual device",
|
||||
"6": "BLE",
|
||||
"7": "Local AP",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "Other",
|
||||
"10": "Function plug-in",
|
||||
"11": "Cellular network",
|
||||
"12": "Cable",
|
||||
"13": "NB-IoT",
|
||||
"14": "Third-party cloud access",
|
||||
"15": "Infrared remote control device",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "Virtual device group",
|
||||
"18": "Gateway sub-device",
|
||||
"19": "Security level gateway sub-device",
|
||||
"22": "PLC",
|
||||
"23": "Cable only",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+Cellular network"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "Do not synchronize",
|
||||
"home_room": "Home Name and Room Name (Xiaomi Home Bedroom)",
|
||||
"room": "Room Name (Bedroom)",
|
||||
"home": "Home Name (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "Enable",
|
||||
"disable": "Disable"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Text Sensor Entity",
|
||||
"bool": "Binary Sensor Entity"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Add",
|
||||
"del": "Unavailable",
|
||||
"offline": "Offline"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[Notice]** Multiple network cards detected that may be connected to the same network. Please pay attention to the selection.",
|
||||
"net_unavailable": "Interface unavailable"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "Authentication Successful",
|
||||
"content": "Please close this page and return to the account authentication page to click 'Next'.",
|
||||
"button": "Close"
|
||||
},
|
||||
"fail": {
|
||||
"title": "Authentication Failed",
|
||||
"content": "{error_msg}, please close this page and return to the account authentication page to click the authentication link again.",
|
||||
"button": "Close"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "Invalid response parameters ('code' or 'state' field is empty)",
|
||||
"-10101": "Passed-in 'state' field mismatch"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "Authentication information is invalid, cloud link will be unavailable, please enter the Xiaomi Home integration page, click 'Options' to re-authenticate",
|
||||
"invalid_device_cache": "Cache device information is abnormal, please enter the Xiaomi Home integration page, click 'Options->Update device list', update the local cache",
|
||||
"invalid_cert_info": "Invalid user certificate, local central link will be unavailable, please enter the Xiaomi Home integration page, click 'Options' to re-authenticate",
|
||||
"device_cloud_error": "An exception occurred when obtaining device information from the cloud, please check the local network connection",
|
||||
"xiaomi_home_error_title": "Xiaomi Home Integration Error",
|
||||
"xiaomi_home_error": "Detected **{nick_name}({uid}, {cloud_server})** error, please enter the options page to reconfigure.\n\n**Error message**: \n{message}",
|
||||
"device_list_changed_title": "Xiaomi Home device list changes",
|
||||
"device_list_changed": "Detected **{nick_name}({uid}, {cloud_server})** device information has changed, please enter the integration options page, click `Options->Update device list`, update local device information.\n\nCurrent network status: {network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} new devices:** \n{message}",
|
||||
"device_list_del": "\n**{count} devices unavailable:** \n{message}",
|
||||
"device_list_offline": "\n**{count} devices offline:** \n{message}",
|
||||
"network_status_online": "Online",
|
||||
"network_status_offline": "Offline",
|
||||
"central_state_changed_title": "Central Hub Gateway Connection Status",
|
||||
"central_state_changed":"**{nick_name}({uid}, {cloud_server})** local connection to Xiaomi central hub gateway: {conn_status}",
|
||||
"central_state_connected": "Connected",
|
||||
"central_state_disconnected": "Disconnected",
|
||||
"device_exec_error": "Execution error"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "Unknown error",
|
||||
"-10001": "Service unavailable",
|
||||
"-10002": "Invalid parameter",
|
||||
"-10003": "Insufficient resources",
|
||||
"-10004": "Internal error",
|
||||
"-10005": "Insufficient permissions",
|
||||
"-10006": "Execution timeout",
|
||||
"-10007": "Device offline or does not exist",
|
||||
"-10020": "Unauthorized (OAuth2)",
|
||||
"-10030": "Invalid token (HTTP)",
|
||||
"-10040": "Invalid message format",
|
||||
"-10050": "Invalid certificate",
|
||||
"-704000000": "Unknown error",
|
||||
"-704010000": "Unauthorized (device may have been deleted)",
|
||||
"-704014006": "Device description not found",
|
||||
"-704030013": "Property not readable",
|
||||
"-704030023": "Property not writable",
|
||||
"-704030033": "Property not subscribable",
|
||||
"-704040002": "Service does not exist",
|
||||
"-704040003": "Property does not exist",
|
||||
"-704040004": "Event does not exist",
|
||||
"-704040005": "Action does not exist",
|
||||
"-704040999": "Feature not online",
|
||||
"-704042001": "Device does not exist",
|
||||
"-704042011": "Device offline",
|
||||
"-704053036": "Device operation timeout",
|
||||
"-704053100": "Device cannot perform this operation in the current state",
|
||||
"-704083036": "Device operation timeout",
|
||||
"-704090001": "Device does not exist",
|
||||
"-704220008": "Invalid ID",
|
||||
"-704220025": "Action parameter count mismatch",
|
||||
"-704220035": "Action parameter error",
|
||||
"-704220043": "Property value error",
|
||||
"-704222034": "Action return value error",
|
||||
"-705004000": "Unknown error",
|
||||
"-705004501": "Unknown error",
|
||||
"-705201013": "Property not readable",
|
||||
"-705201015": "Action execution error",
|
||||
"-705201023": "Property not writable",
|
||||
"-705201033": "Property not subscribable",
|
||||
"-706012000": "Unknown error",
|
||||
"-706012013": "Property not readable",
|
||||
"-706012015": "Action execution error",
|
||||
"-706012023": "Property not writable",
|
||||
"-706012033": "Property not subscribable",
|
||||
"-706012043": "Property value error",
|
||||
"-706014006": "Device description not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "dispositivos",
|
||||
"found_central_gateway": ", se encontró la puerta de enlace central local",
|
||||
"without_room": "Sin habitación asignada",
|
||||
"no_display": "no mostrar"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "automático",
|
||||
"cloud": "nube"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "lógica OR",
|
||||
"and": "lógica AND"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "excluir",
|
||||
"include": "incluir"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "dispositivo yunyi",
|
||||
"2": "dispositivo de nube",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "dispositivo virtual",
|
||||
"6": "BLE",
|
||||
"7": "AP local",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "otro",
|
||||
"10": "complemento de función",
|
||||
"11": "red celular",
|
||||
"12": "cable",
|
||||
"13": "NB-IoT",
|
||||
"14": "acceso a la nube de terceros",
|
||||
"15": "dispositivo de control remoto infrarrojo",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "grupo de dispositivos virtuales",
|
||||
"18": "subdispositivo de puerta de enlace",
|
||||
"19": "subdispositivo de puerta de enlace de nivel de seguridad",
|
||||
"22": "PLC",
|
||||
"23": "solo cable",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+red celular"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "no sincronizar",
|
||||
"home_room": "nombre de la casa y nombre de la habitación (Xiaomi Home Dormitorio)",
|
||||
"room": "nombre de la habitación (Dormitorio)",
|
||||
"home": "nombre de la casa (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "habilitar",
|
||||
"disable": "deshabilitar"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Entidad del sensor de texto",
|
||||
"bool": "Entidad del sensor binario"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "agregar",
|
||||
"del": "no disponible",
|
||||
"offline": "fuera de línea"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[Aviso]** Se detectaron varias tarjetas de red que pueden estar conectadas a la misma red. Por favor, preste atención a la selección.",
|
||||
"net_unavailable": "Interfaz no disponible"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "Autenticación exitosa",
|
||||
"content": "Por favor, cierre esta página y regrese a la página de autenticación de la cuenta para hacer clic en 'Siguiente'.",
|
||||
"button": "Cerrar"
|
||||
},
|
||||
"fail": {
|
||||
"title": "Autenticación fallida",
|
||||
"content": "{error_msg}, por favor, cierre esta página y regrese a la página de autenticación de la cuenta para hacer clic en el enlace de autenticación nuevamente.",
|
||||
"button": "Cerrar"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "Parámetros de respuesta inválidos ('code' o 'state' está vacío)",
|
||||
"-10101": "El campo 'state' proporcionado no coincide"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "La información de autenticación es inválida, la conexión en la nube no estará disponible, por favor, vaya a la página de integración de Xiaomi Home, haga clic en 'Opciones' para volver a autenticar",
|
||||
"invalid_device_cache": "La información de caché del dispositivo es anormal, por favor, vaya a la página de integración de Xiaomi Home, haga clic en 'Opciones -> Actualizar lista de dispositivos' para actualizar la información del dispositivo local",
|
||||
"invalid_cert_info": "Certificado de usuario inválido, la conexión del centro local no estará disponible, por favor, vaya a la página de integración de Xiaomi Home, haga clic en 'Opciones' para volver a autenticar",
|
||||
"device_cloud_error": "Error al obtener la información del dispositivo desde la nube, por favor, compruebe la conexión de red local",
|
||||
"xiaomi_home_error_title": "Error de integración de Xiaomi Home",
|
||||
"xiaomi_home_error": "Se detectó un error en **{nick_name}({uid}, {cloud_server})**, por favor, vaya a la página de opciones para reconfigurar.\n\n**Mensaje de error**: \n{message}",
|
||||
"device_list_changed_title": "Cambio en la lista de dispositivos de Xiaomi Home",
|
||||
"device_list_changed": "Se detectó un cambio en la información del dispositivo **{nick_name}({uid}, {cloud_server})**, por favor, vaya a la página de integración, haga clic en 'Opciones -> Actualizar lista de dispositivos' para actualizar la información del dispositivo local.\n\nEstado actual de la red: {network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} nuevos dispositivos:** \n{message}",
|
||||
"device_list_del": "\n**{count} dispositivos no disponibles:** \n{message}",
|
||||
"device_list_offline": "\n**{count} dispositivos sin conexión:** \n{message}",
|
||||
"network_status_online": "En línea",
|
||||
"network_status_offline": "Desconectado",
|
||||
"central_state_changed_title": "Estado de conexión de la puerta de enlace central",
|
||||
"central_state_changed": "**{nick_name}({uid}, {cloud_server})** enlace de conexión local de la puerta de enlace central: {conn_status}",
|
||||
"central_state_connected": "conectado",
|
||||
"central_state_disconnected": "desconectado",
|
||||
"device_exec_error": "Error de ejecución"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "Error desconocido",
|
||||
"-10001": "Servicio no disponible",
|
||||
"-10002": "Parámetro inválido",
|
||||
"-10003": "Recursos insuficientes",
|
||||
"-10004": "Error interno",
|
||||
"-10005": "Permisos insuficientes",
|
||||
"-10006": "Tiempo de ejecución agotado",
|
||||
"-10007": "Dispositivo fuera de línea o no existe",
|
||||
"-10020": "No autorizado (OAuth2)",
|
||||
"-10030": "Token inválido (HTTP)",
|
||||
"-10040": "Formato de mensaje inválido",
|
||||
"-10050": "Certificado inválido",
|
||||
"-704000000": "Error desconocido",
|
||||
"-704010000": "No autorizado (el dispositivo puede haber sido eliminado)",
|
||||
"-704014006": "Descripción del dispositivo no encontrada",
|
||||
"-704030013": "Propiedad no legible",
|
||||
"-704030023": "Propiedad no escribible",
|
||||
"-704030033": "Propiedad no suscribible",
|
||||
"-704040002": "Servicio no existe",
|
||||
"-704040003": "Propiedad no existe",
|
||||
"-704040004": "Evento no existe",
|
||||
"-704040005": "Acción no existe",
|
||||
"-704040999": "Función no en línea",
|
||||
"-704042001": "Dispositivo no existe",
|
||||
"-704042011": "Dispositivo fuera de línea",
|
||||
"-704053036": "Tiempo de operación del dispositivo agotado",
|
||||
"-704053100": "El dispositivo no puede realizar esta operación en el estado actual",
|
||||
"-704083036": "Tiempo de operación del dispositivo agotado",
|
||||
"-704090001": "Dispositivo no existe",
|
||||
"-704220008": "ID inválido",
|
||||
"-704220025": "Número de parámetros de acción no coincide",
|
||||
"-704220035": "Error de parámetro de acción",
|
||||
"-704220043": "Error de valor de propiedad",
|
||||
"-704222034": "Error de valor de retorno de acción",
|
||||
"-705004000": "Error desconocido",
|
||||
"-705004501": "Error desconocido",
|
||||
"-705201013": "Propiedad no legible",
|
||||
"-705201015": "Error de ejecución de acción",
|
||||
"-705201023": "Propiedad no escribible",
|
||||
"-705201033": "Propiedad no suscribible",
|
||||
"-706012000": "Error desconocido",
|
||||
"-706012013": "Propiedad no legible",
|
||||
"-706012015": "Error de ejecución de acción",
|
||||
"-706012023": "Propiedad no escribible",
|
||||
"-706012033": "Propiedad no suscribible",
|
||||
"-706012043": "Error de valor de propiedad",
|
||||
"-706014006": "Descripción del dispositivo no encontrada"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "appareils",
|
||||
"found_central_gateway": ", passerelle centrale locale trouvée",
|
||||
"without_room": "Aucune pièce attribuée",
|
||||
"no_display": "ne pas afficher"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "automatique",
|
||||
"cloud": "cloud"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "ou logique",
|
||||
"and": "et logique"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "exclure",
|
||||
"include": "inclure"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "appareil yunyi",
|
||||
"2": "appareil cloud",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "appareil virtuel",
|
||||
"6": "BLE",
|
||||
"7": "AP local",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "autre",
|
||||
"10": "plug-in de fonction",
|
||||
"11": "réseau cellulaire",
|
||||
"12": "câble",
|
||||
"13": "NB-IoT",
|
||||
"14": "accès cloud tiers",
|
||||
"15": "appareil de télécommande infrarouge",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "groupe d'appareils virtuels",
|
||||
"18": "sous-appareil de passerelle",
|
||||
"19": "niveau de sécurité sous-appareil de passerelle",
|
||||
"22": "PLC",
|
||||
"23": "câble uniquement",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+réseau cellulaire"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "ne pas synchroniser",
|
||||
"home_room": "nom de la maison et nom de la pièce (Xiaomi Home Chambre)",
|
||||
"room": "nom de la pièce (Chambre)",
|
||||
"home": "nom de la maison (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "activer",
|
||||
"disable": "désactiver"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Entité du capteur de texte",
|
||||
"bool": "Entité du capteur binaire"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Ajouter",
|
||||
"del": "Supprimer",
|
||||
"offline": "hors ligne"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[Remarque]** Plusieurs cartes réseau détectées qui peuvent être connectées au même réseau. Veuillez faire attention à la sélection.",
|
||||
"net_unavailable": "Interface non disponible"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "Authentification réussie",
|
||||
"content": "Veuillez fermer cette page et revenir à la page d'authentification du compte pour cliquer sur 'Suivant'.",
|
||||
"button": "Fermer"
|
||||
},
|
||||
"fail": {
|
||||
"title": "Échec de l'authentification",
|
||||
"content": "{error_msg}, veuillez fermer cette page et revenir à la page d'authentification du compte pour cliquer à nouveau sur le lien d'authentification.",
|
||||
"button": "Fermer"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "Paramètres de réponse invalides ('code' ou 'state' est vide)",
|
||||
"-10101": "Le champ 'state' transmis ne correspond pas"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "Informations d'authentification non valides, le lien cloud ne sera pas disponible, veuillez accéder à la page d'intégration Xiaomi Home, cliquez sur \"Options\" pour vous réauthentifier",
|
||||
"invalid_device_cache": "Informations de cache de périphérique non valides, veuillez accéder à la page d'intégration Xiaomi Home, cliquez sur `Options-> Mettre à jour la liste des appareils`, pour mettre à jour les informations locales des appareils",
|
||||
"invalid_cert_info": "Certificat utilisateur non valide, le lien central local ne sera pas disponible, veuillez accéder à la page d'intégration Xiaomi Home, cliquez sur \"Options\" pour vous réauthentifier",
|
||||
"device_cloud_error": "Erreur lors de la récupération des informations de l'appareil à partir du cloud, veuillez vérifier la connexion réseau locale",
|
||||
"xiaomi_home_error_title": "Erreur d'intégration Xiaomi Home",
|
||||
"xiaomi_home_error": "Erreur détectée sur **{nick_name}({uid}, {cloud_server})**, veuillez accéder à la page d'options pour reconfigurer.\n\n**Message d'erreur**: \n{message}",
|
||||
"device_list_changed_title": "Changements dans la liste des appareils Xiaomi Home",
|
||||
"device_list_changed": "Changements détectés sur **{nick_name}({uid}, {cloud_server})**, veuillez accéder à la page d'intégration, cliquez sur `Options-> Mettre à jour la liste des appareils`, pour mettre à jour les informations locales des appareils.\n\nÉtat actuel du réseau : {network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} nouveaux appareils :** \n{message}",
|
||||
"device_list_del": "\n**{count} appareils non disponibles :** \n{message}",
|
||||
"device_list_offline": "\n**{count} appareils hors ligne :** \n{message}",
|
||||
"network_status_online": "En ligne",
|
||||
"network_status_offline": "Hors ligne",
|
||||
"central_state_changed_title": "État de connexion de la passerelle centrale",
|
||||
"central_state_changed": "**{nick_name}({uid}, {cloud_server})** liaison de connexion locale de la passerelle centrale : {conn_status}",
|
||||
"central_state_connected": "connecté",
|
||||
"central_state_disconnected": "déconnecté",
|
||||
"device_exec_error": "Erreur d'exécution"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "Erreur inconnue",
|
||||
"-10001": "Service indisponible",
|
||||
"-10002": "Paramètre invalide",
|
||||
"-10003": "Ressources insuffisantes",
|
||||
"-10004": "Erreur interne",
|
||||
"-10005": "Permissions insuffisantes",
|
||||
"-10006": "Délai d'exécution dépassé",
|
||||
"-10007": "Appareil hors ligne ou n'existe pas",
|
||||
"-10020": "Non autorisé (OAuth2)",
|
||||
"-10030": "Jeton invalide (HTTP)",
|
||||
"-10040": "Format de message invalide",
|
||||
"-10050": "Certificat invalide",
|
||||
"-704000000": "Erreur inconnue",
|
||||
"-704010000": "Non autorisé (l'appareil peut avoir été supprimé)",
|
||||
"-704014006": "Description de l'appareil introuvable",
|
||||
"-704030013": "Propriété non lisible",
|
||||
"-704030023": "Propriété non inscriptible",
|
||||
"-704030033": "Propriété non abonnable",
|
||||
"-704040002": "Service n'existe pas",
|
||||
"-704040003": "Propriété n'existe pas",
|
||||
"-704040004": "Événement n'existe pas",
|
||||
"-704040005": "Action n'existe pas",
|
||||
"-704040999": "Fonction non en ligne",
|
||||
"-704042001": "Appareil n'existe pas",
|
||||
"-704042011": "Appareil hors ligne",
|
||||
"-704053036": "Délai d'opération de l'appareil dépassé",
|
||||
"-704053100": "L'appareil ne peut pas effectuer cette opération dans l'état actuel",
|
||||
"-704083036": "Délai d'opération de l'appareil dépassé",
|
||||
"-704090001": "Appareil n'existe pas",
|
||||
"-704220008": "ID invalide",
|
||||
"-704220025": "Nombre de paramètres d'action ne correspond pas",
|
||||
"-704220035": "Erreur de paramètre d'action",
|
||||
"-704220043": "Erreur de valeur de propriété",
|
||||
"-704222034": "Erreur de valeur de retour d'action",
|
||||
"-705004000": "Erreur inconnue",
|
||||
"-705004501": "Erreur inconnue",
|
||||
"-705201013": "Propriété non lisible",
|
||||
"-705201015": "Erreur d'exécution d'action",
|
||||
"-705201023": "Propriété non inscriptible",
|
||||
"-705201033": "Propriété non abonnable",
|
||||
"-706012000": "Erreur inconnue",
|
||||
"-706012013": "Propriété non lisible",
|
||||
"-706012015": "Erreur d'exécution d'action",
|
||||
"-706012023": "Propriété non inscriptible",
|
||||
"-706012033": "Propriété non abonnable",
|
||||
"-706012043": "Erreur de valeur de propriété",
|
||||
"-706014006": "Description de l'appareil introuvable"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "Dispositivi",
|
||||
"found_central_gateway": ", Trovato Gateway Hub Centrale Locale",
|
||||
"without_room": "Nessuna stanza assegnata",
|
||||
"no_display": "Non visualizzare"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "Auto",
|
||||
"cloud": "Cloud"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "Logica OR",
|
||||
"and": "Logica AND"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "Escludere",
|
||||
"include": "Includere"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "Dispositivo yunyi",
|
||||
"2": "Dispositivo Cloud",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "Dispositivo Virtuale",
|
||||
"6": "BLE",
|
||||
"7": "AP Locale",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "Altro",
|
||||
"10": "Plug-in Funzionale",
|
||||
"11": "Rete Cellulare",
|
||||
"12": "Cavo",
|
||||
"13": "NB-IoT",
|
||||
"14": "Accesso cloud di terze parti",
|
||||
"15": "Dispositivo di controllo remoto a infrarossi",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "Gruppo di Dispositivi Virtuali",
|
||||
"18": "Sottodispositivo Gateway",
|
||||
"19": "Sottodispositivo Gateway di livello di sicurezza",
|
||||
"22": "PLC",
|
||||
"23": "Solo Cavo",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+Rete Cellulare"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "Non sincronizzare",
|
||||
"home_room": "Nome Casa e Nome Stanza (Camera da Letto Xiaomi Home)",
|
||||
"room": "Nome Stanza (Camera da Letto)",
|
||||
"home": "Nome Casa (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "Abilita",
|
||||
"disable": "Disabilita"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Entità Sensore Testo",
|
||||
"bool": "Entità Sensore Binario"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Aggiungere",
|
||||
"del": "Non disponibile",
|
||||
"offline": "Offline"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[Avviso]** Rilevate più schede di rete che potrebbero essere connesse alla stessa rete. Si prega di prestare attenzione alla selezione.",
|
||||
"net_unavailable": "Interfaccia non disponibile"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "Autenticazione Avvenuta",
|
||||
"content": "Chiudere questa pagina e tornare alla pagina di autenticazione dell'account per fare clic su 'Avanti'.",
|
||||
"button": "Chiudi"
|
||||
},
|
||||
"fail": {
|
||||
"title": "Autenticazione Fallita",
|
||||
"content": "{error_msg}, chiudere questa pagina e tornare alla pagina di autenticazione dell'account per fare clic nuovamente sul link di autenticazione.",
|
||||
"button": "Chiudi"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "Parametri di risposta non validi (i campi 'code' o 'state' sono vuoti)",
|
||||
"-10101": "Campo 'state' passato non corrispondente"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "Le informazioni di autenticazione non sono valide, il collegamento al cloud non sarà disponibile, si prega di accedere alla pagina di integrazione Xiaomi Home e cliccare 'Opzioni' per ri-autenticarsi",
|
||||
"invalid_device_cache": "Le informazioni memorizzate nella cache del dispositivo sono anomale, si prega di accedere alla pagina di integrazione Xiaomi Home e cliccare 'Opzioni->Aggiorna elenco dispositivi' per aggiornare la cache locale",
|
||||
"invalid_cert_info": "Certificato utente non valido, il collegamento centrale locale non sarà disponibile, si prega di accedere alla pagina di integrazione Xiaomi Home e cliccare 'Opzioni' per ri-autenticarsi",
|
||||
"device_cloud_error": "Si è verificata un'eccezione durante l'ottenimento delle informazioni del dispositivo dal cloud, si prega di controllare la connessione alla rete locale",
|
||||
"xiaomi_home_error_title": "Errore di Integrazione Xiaomi Home",
|
||||
"xiaomi_home_error": "Rilevato errore per **{nick_name}({uid}, {cloud_server})**, si prega di accedere alla pagina delle opzioni per riconfigurare.\n\n**Messaggio di errore**: \n{message}",
|
||||
"device_list_changed_title": "Modifiche all'elenco dispositivi Xiaomi Home",
|
||||
"device_list_changed": "Rilevato cambiamento nelle informazioni del dispositivo per **{nick_name}({uid}, {cloud_server})**, si prega di accedere alla pagina delle opzioni di integrazione, cliccare `Opzioni->Aggiorna elenco dispositivi` per aggiornare le informazioni locali dei dispositivi.\n\nStato corrente della rete: {network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} nuovi dispositivi:** \n{message}",
|
||||
"device_list_del": "\n**{count} dispositivi non disponibili:** \n{message}",
|
||||
"device_list_offline": "\n**{count} dispositivi offline:** \n{message}",
|
||||
"network_status_online": "Online",
|
||||
"network_status_offline": "Offline",
|
||||
"central_state_changed_title": "Stato di connessione del gateway centrale",
|
||||
"central_state_changed": "**{nick_name}({uid}, {cloud_server})** collegamento locale del gateway centrale: {conn_status}",
|
||||
"central_state_connected": "connesso",
|
||||
"central_state_disconnected": "disconnesso",
|
||||
"device_exec_error": "Errore di esecuzione"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "Errore sconosciuto",
|
||||
"-10001": "Servizio non disponibile",
|
||||
"-10002": "Parametro non valido",
|
||||
"-10003": "Risorse insufficienti",
|
||||
"-10004": "Errore interno",
|
||||
"-10005": "Permessi insufficienti",
|
||||
"-10006": "Timeout di esecuzione",
|
||||
"-10007": "Dispositivo offline o inesistente",
|
||||
"-10020": "Non autorizzato (OAuth2)",
|
||||
"-10030": "Token non valido (HTTP)",
|
||||
"-10040": "Formato messaggio non valido",
|
||||
"-10050": "Certificato non valido",
|
||||
"-704000000": "Errore sconosciuto",
|
||||
"-704010000": "Non autorizzato (il dispositivo potrebbe essere stato eliminato)",
|
||||
"-704014006": "Descrizione del dispositivo non trovata",
|
||||
"-704030013": "Proprietà non leggibile",
|
||||
"-704030023": "Proprietà non scrivibile",
|
||||
"-704030033": "Proprietà non sottoscrivibile",
|
||||
"-704040002": "Servizio inesistente",
|
||||
"-704040003": "Proprietà inesistente",
|
||||
"-704040004": "Evento inesistente",
|
||||
"-704040005": "Azione inesistente",
|
||||
"-704040999": "Funzione non online",
|
||||
"-704042001": "Dispositivo inesistente",
|
||||
"-704042011": "Dispositivo offline",
|
||||
"-704053036": "Timeout operazione del dispositivo",
|
||||
"-704053100": "Il dispositivo non può eseguire questa operazione nello stato attuale",
|
||||
"-704083036": "Timeout operazione del dispositivo",
|
||||
"-704090001": "Dispositivo inesistente",
|
||||
"-704220008": "ID non valido",
|
||||
"-704220025": "Conteggio parametri azione non corrispondente",
|
||||
"-704220035": "Errore del parametro azione",
|
||||
"-704220043": "Errore valore proprietà",
|
||||
"-704222034": "Errore valore di ritorno dell'azione",
|
||||
"-705004000": "Errore sconosciuto",
|
||||
"-705004501": "Errore sconosciuto",
|
||||
"-705201013": "Proprietà non leggibile",
|
||||
"-705201015": "Errore di esecuzione azione",
|
||||
"-705201023": "Proprietà non scrivibile",
|
||||
"-705201033": "Proprietà non sottoscrivibile",
|
||||
"-706012000": "Errore sconosciuto",
|
||||
"-706012013": "Proprietà non leggibile",
|
||||
"-706012015": "Errore di esecuzione azione",
|
||||
"-706012023": "Proprietà non scrivibile",
|
||||
"-706012033": "Proprietà non sottoscrivibile",
|
||||
"-706012043": "Errore valore proprietà",
|
||||
"-706014006": "Descrizione del dispositivo non trovata"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "デバイス",
|
||||
"found_central_gateway": "、ローカル中央ゲートウェイが見つかりました",
|
||||
"without_room": "部屋が割り当てられていません",
|
||||
"no_display": "表示しない"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "自動",
|
||||
"cloud": "クラウド"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "ORロジック",
|
||||
"and": "ANDロジック"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "除外",
|
||||
"include": "含む"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "yunyiデバイス",
|
||||
"2": "クラウドデバイス",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "仮想デバイス",
|
||||
"6": "BLE",
|
||||
"7": "ローカルAP",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "その他",
|
||||
"10": "機能プラグイン",
|
||||
"11": "セルラーネットワーク",
|
||||
"12": "ケーブル",
|
||||
"13": "NB-IoT",
|
||||
"14": "サードパーティクラウドアクセス",
|
||||
"15": "赤外線リモコンデバイス",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "仮想デバイスグループ",
|
||||
"18": "ゲートウェイサブデバイス",
|
||||
"19": "セキュリティレベルゲートウェイサブデバイス",
|
||||
"22": "PLC",
|
||||
"23": "ケーブルのみ",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+セルラーネットワーク"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "同期しない",
|
||||
"home_room": "家の名前と部屋の名前 (Xiaomi Home 寝室)",
|
||||
"room": "部屋の名前(寝室)",
|
||||
"home": "家の名前 (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "有効",
|
||||
"disable": "無効"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "テキストセンサーエンティティ",
|
||||
"bool": "バイナリセンサーエンティティ"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "追加",
|
||||
"del": "利用不可",
|
||||
"offline": "オフライン"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[注意]** 複数のネットワークカードが同じネットワークに接続されている可能性があります。選択に注意してください。",
|
||||
"net_unavailable": "インターフェースが利用できません"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "認証成功",
|
||||
"content": "このページを閉じて、アカウント認証ページに戻り、「次へ」をクリックしてください。",
|
||||
"button": "閉じる"
|
||||
},
|
||||
"fail": {
|
||||
"title": "認証失敗",
|
||||
"content": "{error_msg}、このページを閉じて、アカウント認証ページに戻り、再度認証リンクをクリックしてください。",
|
||||
"button": "閉じる"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "無効な応答パラメータ('code'または'state'フィールドが空です)",
|
||||
"-10101": "渡された'state'フィールドが一致しません"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "認証情報が無効です。クラウドリンクは利用できません。Xiaomi Home統合ページに入り、[オプション]をクリックして再認証してください",
|
||||
"invalid_device_cache": "キャッシュデバイス情報が異常です。Xiaomi Home統合ページに入り、[オプション->デバイスリストの更新]をクリックして、ローカルキャッシュを更新してください",
|
||||
"invalid_cert_info": "無効なユーザー証明書です。ローカルセントラルリンクは利用できません。Xiaomi Home統合ページに入り、[オプション]をクリックして再認証してください",
|
||||
"device_cloud_error": "クラウドからデバイス情報を取得する際に例外が発生しました。ローカルネットワーク接続を確認してください",
|
||||
"xiaomi_home_error_title": "Xiaomi Home統合エラー",
|
||||
"xiaomi_home_error": "エラーが検出されました **{nick_name}({uid}, {cloud_server})** 、オプションページに入り再構成してください。\n\n**エラーメッセージ**: \n{message}",
|
||||
"device_list_changed_title": "Xiaomi Homeデバイスリストの変更",
|
||||
"device_list_changed": "変更が検出されました **{nick_name}({uid}, {cloud_server})** デバイス情報が変更されました。統合オプションページに入り、`オプション->デバイスリストの更新`をクリックして、ローカルデバイス情報を更新してください。\n\n現在のネットワーク状態:{network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} 新しいデバイス:** \n{message}",
|
||||
"device_list_del": "\n**{count} デバイスが利用できません:** \n{message}",
|
||||
"device_list_offline": "\n**{count} デバイスがオフライン:** \n{message}",
|
||||
"network_status_online": "オンライン",
|
||||
"network_status_offline": "オフライン",
|
||||
"central_state_changed_title": "中枢ゲートウェイ接続ステータス",
|
||||
"central_state_changed": "**{nick_name}({uid}, {cloud_server})** 中枢ゲートウェイのローカル接続リンク: {conn_status}",
|
||||
"central_state_connected": "接続済み",
|
||||
"central_state_disconnected": "切断されました",
|
||||
"device_exec_error": "実行エラー"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "不明なエラー",
|
||||
"-10001": "サービス利用不可",
|
||||
"-10002": "無効なパラメータ",
|
||||
"-10003": "リソース不足",
|
||||
"-10004": "内部エラー",
|
||||
"-10005": "権限不足",
|
||||
"-10006": "実行タイムアウト",
|
||||
"-10007": "デバイスがオフラインまたは存在しない",
|
||||
"-10020": "未認証(OAuth2)",
|
||||
"-10030": "無効なトークン(HTTP)",
|
||||
"-10040": "無効なメッセージ形式",
|
||||
"-10050": "無効な証明書",
|
||||
"-704000000": "不明なエラー",
|
||||
"-704010000": "未認証(デバイスが削除された可能性があります)",
|
||||
"-704014006": "デバイスの説明が見つかりません",
|
||||
"-704030013": "プロパティが読み取れません",
|
||||
"-704030023": "プロパティが書き込めません",
|
||||
"-704030033": "プロパティが購読できません",
|
||||
"-704040002": "サービスが存在しません",
|
||||
"-704040003": "プロパティが存在しません",
|
||||
"-704040004": "イベントが存在しません",
|
||||
"-704040005": "アクションが存在しません",
|
||||
"-704040999": "機能がオンラインではありません",
|
||||
"-704042001": "デバイスが存在しません",
|
||||
"-704042011": "デバイスがオフラインです",
|
||||
"-704053036": "デバイス操作タイムアウト",
|
||||
"-704053100": "デバイスが現在の状態でこの操作を実行できません",
|
||||
"-704083036": "デバイス操作タイムアウト",
|
||||
"-704090001": "デバイスが存在しません",
|
||||
"-704220008": "無効なID",
|
||||
"-704220025": "アクションパラメータの数が一致しません",
|
||||
"-704220035": "アクションパラメータエラー",
|
||||
"-704220043": "プロパティ値エラー",
|
||||
"-704222034": "アクションの戻り値エラー",
|
||||
"-705004000": "不明なエラー",
|
||||
"-705004501": "不明なエラー",
|
||||
"-705201013": "プロパティが読み取れません",
|
||||
"-705201015": "アクション実行エラー",
|
||||
"-705201023": "プロパティが書き込めません",
|
||||
"-705201033": "プロパティが購読できません",
|
||||
"-706012000": "不明なエラー",
|
||||
"-706012013": "プロパティが読み取れません",
|
||||
"-706012015": "アクション実行エラー",
|
||||
"-706012023": "プロパティが書き込めません",
|
||||
"-706012033": "プロパティが購読できません",
|
||||
"-706012043": "プロパティ値エラー",
|
||||
"-706014006": "デバイスの説明が見つかりません"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "Apparaten",
|
||||
"found_central_gateway": ", Lokale centrale hub-gateway gevonden",
|
||||
"without_room": "Niet toegewezen kamer",
|
||||
"no_display": "Niet weergeven"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "Automatisch",
|
||||
"cloud": "Cloud"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "OF-logica",
|
||||
"and": "EN-logica"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "Uitsluiten",
|
||||
"include": "Inclusief"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "yunyi-apparaat",
|
||||
"2": "Cloudapparaat",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "Virtueel apparaat",
|
||||
"6": "BLE",
|
||||
"7": "Lokaal AP",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "Andere",
|
||||
"10": "Functieplug-in",
|
||||
"11": "Cellulair netwerk",
|
||||
"12": "Kabel",
|
||||
"13": "NB-IoT",
|
||||
"14": "Toegang van derden tot de cloud",
|
||||
"15": "Infrarood afstandsbedieningsapparaat",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "Virtuele apparaatgroep",
|
||||
"18": "Gateway sub-apparaat",
|
||||
"19": "Beveiligingsniveau gateway sub-apparaat",
|
||||
"22": "PLC",
|
||||
"23": "Alleen kabel",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+Cellulair netwerk"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "Niet synchroniseren",
|
||||
"home_room": "Huisnaam en Kamernaam (Xiaomi Home Slaapkamer)",
|
||||
"room": "Kamernaam (Slaapkamer)",
|
||||
"home": "Huisnaam (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "Inschakelen",
|
||||
"disable": "Uitschakelen"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Tekstsensor-entiteit",
|
||||
"bool": "Binairesensor-entiteit"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Toevoegen",
|
||||
"del": "Niet beschikbaar",
|
||||
"offline": "Offline"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[Let op]** Meerdere netwerkkaarten gedetecteerd die mogelijk zijn verbonden met hetzelfde netwerk. Let op bij de selectie.",
|
||||
"net_unavailable": "Interface niet beschikbaar"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "Authenticatie geslaagd",
|
||||
"content": "Sluit deze pagina en ga terug naar de accountauthenticatiepagina om op 'Volgende' te klikken.",
|
||||
"button": "Sluiten"
|
||||
},
|
||||
"fail": {
|
||||
"title": "Authenticatie mislukt",
|
||||
"content": "{error_msg}, sluit deze pagina en ga terug naar de accountauthenticatiepagina om opnieuw op de authenticatielink te klikken.",
|
||||
"button": "Sluiten"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "Ongeldige antwoordparameters ('code' of 'state' veld is leeg)",
|
||||
"-10101": "Doorgegeven 'state' veld komt niet overeen"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "Authenticatie-informatie is ongeldig, cloudverbinding zal niet beschikbaar zijn. Ga naar de Xiaomi Home-integratiepagina en klik op 'Opties' om opnieuw te verifiëren.",
|
||||
"invalid_device_cache": "Cache van apparaatgegevens is abnormaal. Ga naar de Xiaomi Home-integratiepagina, klik op 'Opties->Apparaatlijst bijwerken' en werk de lokale cache bij.",
|
||||
"invalid_cert_info": "Ongeldig gebruikerscertificaat, lokale centrale verbinding zal niet beschikbaar zijn. Ga naar de Xiaomi Home-integratiepagina en klik op 'Opties' om opnieuw te verifiëren.",
|
||||
"device_cloud_error": "Er is een uitzondering opgetreden bij het ophalen van apparaatgegevens uit de cloud. Controleer de lokale netwerkverbinding.",
|
||||
"xiaomi_home_error_title": "Xiaomi Home-integratiefout",
|
||||
"xiaomi_home_error": "Gedetecteerd **{nick_name}({uid}, {cloud_server})** fout, ga naar de optiespagina om opnieuw te configureren.\n\n**Foutmelding**: \n{message}",
|
||||
"device_list_changed_title": "Wijzigingen in Xiaomi Home-apparaatlijst",
|
||||
"device_list_changed": "Gedetecteerd **{nick_name}({uid}, {cloud_server})** apparaatgegevens zijn gewijzigd. Ga naar de integratie-optiespagina, klik op `Opties->Apparaatlijst bijwerken` en werk lokale apparaatgegevens bij.\n\nHuidige netwerkstatus: {network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} nieuwe apparaten:** \n{message}",
|
||||
"device_list_del": "\n**{count} apparaten niet beschikbaar:** \n{message}",
|
||||
"device_list_offline": "\n**{count} apparaten offline:** \n{message}",
|
||||
"network_status_online": "Online",
|
||||
"network_status_offline": "Offline",
|
||||
"central_state_changed_title": "Verbindingsstatus van centrale gateway",
|
||||
"central_state_changed": "**{nick_name}({uid}, {cloud_server})** Lokale verbinding van centrale gateway: {conn_status}",
|
||||
"central_state_connected": "verbonden",
|
||||
"central_state_disconnected": "verbinding verbroken",
|
||||
"device_exec_error": "Uitvoeringsfout"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "Onbekende fout",
|
||||
"-10001": "Service niet beschikbaar",
|
||||
"-10002": "Ongeldige parameter",
|
||||
"-10003": "Onvoldoende middelen",
|
||||
"-10004": "Interne fout",
|
||||
"-10005": "Onvoldoende machtigingen",
|
||||
"-10006": "Uitvoeringstijd verstreken",
|
||||
"-10007": "Apparaat offline of bestaat niet",
|
||||
"-10020": "Niet geautoriseerd (OAuth2)",
|
||||
"-10030": "Ongeldig token (HTTP)",
|
||||
"-10040": "Ongeldig berichtformaat",
|
||||
"-10050": "Ongeldig certificaat",
|
||||
"-704000000": "Onbekende fout",
|
||||
"-704010000": "Niet geautoriseerd (apparaat kan zijn verwijderd)",
|
||||
"-704014006": "Apparaatbeschrijving niet gevonden",
|
||||
"-704030013": "Eigenschap niet leesbaar",
|
||||
"-704030023": "Eigenschap niet schrijfbaar",
|
||||
"-704030033": "Eigenschap niet abonneerbaar",
|
||||
"-704040002": "Service bestaat niet",
|
||||
"-704040003": "Eigenschap bestaat niet",
|
||||
"-704040004": "Gebeurtenis bestaat niet",
|
||||
"-704040005": "Actie bestaat niet",
|
||||
"-704040999": "Functie niet online",
|
||||
"-704042001": "Apparaat bestaat niet",
|
||||
"-704042011": "Apparaat offline",
|
||||
"-704053036": "Apparaatbedieningstijd verstreken",
|
||||
"-704053100": "Apparaat kan deze handeling niet uitvoeren in de huidige staat",
|
||||
"-704083036": "Apparaatbedieningstijd verstreken",
|
||||
"-704090001": "Apparaat bestaat niet",
|
||||
"-704220008": "Ongeldige ID",
|
||||
"-704220025": "Aantal actieparameters komt niet overeen",
|
||||
"-704220035": "Fout in actieparameter",
|
||||
"-704220043": "Fout in eigenschapswaarde",
|
||||
"-704222034": "Fout in retourwaarde actie",
|
||||
"-705004000": "Onbekende fout",
|
||||
"-705004501": "Onbekende fout",
|
||||
"-705201013": "Eigenschap niet leesbaar",
|
||||
"-705201015": "Fout bij uitvoeren van actie",
|
||||
"-705201023": "Eigenschap niet schrijfbaar",
|
||||
"-705201033": "Eigenschap niet abonneerbaar",
|
||||
"-706012000": "Onbekende fout",
|
||||
"-706012013": "Eigenschap niet leesbaar",
|
||||
"-706012015": "Fout bij uitvoeren van actie",
|
||||
"-706012023": "Eigenschap niet schrijfbaar",
|
||||
"-706012033": "Eigenschap niet abonneerbaar",
|
||||
"-706012043": "Fout in eigenschapswaarde",
|
||||
"-706014006": "Apparaatbeschrijving niet gevonden"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "dispositivos",
|
||||
"found_central_gateway": "encontrado o gateway central local",
|
||||
"without_room": "sem quarto atribuído",
|
||||
"no_display": "não exibir"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "automático",
|
||||
"cloud": "nuvem"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "lógica OU",
|
||||
"and": "lógica E"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "excluir",
|
||||
"include": "incluir"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "dispositivo yunyi",
|
||||
"2": "dispositivo de nuvem",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "dispositivo virtual",
|
||||
"6": "BLE",
|
||||
"7": "AP local",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "outro",
|
||||
"10": "plug-in de função",
|
||||
"11": "rede celular",
|
||||
"12": "cabo",
|
||||
"13": "NB-IoT",
|
||||
"14": "acesso à nuvem de terceiros",
|
||||
"15": "dispositivo de controle remoto infravermelho",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "grupo de dispositivos virtuais",
|
||||
"18": "subdispositivo de gateway",
|
||||
"19": "subdispositivo de gateway de nível de segurança",
|
||||
"22": "PLC",
|
||||
"23": "somente cabo",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+rede celular"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "não sincronizado",
|
||||
"home_room": "Nome da casa e nome do quarto (Xiaomi Home Quarto)",
|
||||
"room": "Nome do quarto (Quarto)",
|
||||
"home": "Nome da casa (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "habilitado",
|
||||
"disable": "desabilitado"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Entidade do sensor de texto",
|
||||
"bool": "Entidade do sensor binário"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "adicionar",
|
||||
"del": "indisponível",
|
||||
"offline": "offline"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[Aviso]** Detectado múltiplas interfaces de rede que podem estar conectando à mesma rede, por favor, selecione a correta.",
|
||||
"net_unavailable": "Interface indisponível"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "Autenticação bem-sucedida",
|
||||
"content": "Por favor, feche esta página e volte para a página de autenticação da conta para clicar em 'Próximo'.",
|
||||
"button": "Fechar"
|
||||
},
|
||||
"fail": {
|
||||
"title": "Falha na autenticação",
|
||||
"content": "{error_msg}, por favor, feche esta página e volte para a página de autenticação da conta para clicar no link de autenticação novamente.",
|
||||
"button": "Fechar"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "Parâmetros de resposta inválidos ('code' ou 'state' está vazio)",
|
||||
"-10101": "O campo 'state' fornecido não corresponde"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "Informações de autenticação inválidas, a conexão com a nuvem estará indisponível. Vá para a página de integração do Xiaomi Home e clique em 'Opções' para reautenticar.",
|
||||
"invalid_device_cache": "Informações de dispositivo no cache inválidas. Vá para a página de integração do Xiaomi Home e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.",
|
||||
"invalid_cert_info": "Certificado de usuário inválido. A conexão local do gateway central estará indisponível. Vá para a página de integração do Xiaomi Home e clique em 'Opções' para reautenticar.",
|
||||
"device_cloud_error": "Erro ao obter informações do dispositivo da nuvem. Verifique a conexão da rede local.",
|
||||
"xiaomi_home_error_title": "Erro de Integração do Xiaomi Home",
|
||||
"xiaomi_home_error": "Erro detectado em **{nick_name}({uid}, {cloud_server})**. Vá para a página de opções para reconfigurar.\n\n**Erro**: \n{message}",
|
||||
"device_list_changed_title": "Mudança na lista de dispositivos do Xiaomi Home",
|
||||
"device_list_changed": "Detectado que as informações do dispositivo **{nick_name}({uid}, {cloud_server})** mudaram. Vá para a página de integração e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.\n\nStatus atual da rede: {network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} dispositivos novos**: \n{message}",
|
||||
"device_list_del": "\n**{count} dispositivos não disponíveis**: \n{message}",
|
||||
"device_list_offline": "\n**{count} dispositivos offline**: \n{message}",
|
||||
"network_status_online": "online",
|
||||
"network_status_offline": "offline",
|
||||
"central_state_changed_title": "Status de conexão do gateway central",
|
||||
"central_state_changed": "**{nick_name}({uid}, {cloud_server})** conexão local do gateway central: {conn_status}",
|
||||
"central_state_connected": "conectado",
|
||||
"central_state_disconnected": "desconectado",
|
||||
"device_exec_error": "Erro na execução"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "Erro desconhecido",
|
||||
"-10001": "Serviço indisponível",
|
||||
"-10002": "Parâmetro inválido",
|
||||
"-10003": "Recursos insuficientes",
|
||||
"-10004": "Erro interno",
|
||||
"-10005": "Permissões insuficientes",
|
||||
"-10006": "Execução expirada",
|
||||
"-10007": "Dispositivo offline ou inexistente",
|
||||
"-10020": "OAuth2 não autorizado",
|
||||
"-10030": "Token inválido (HTTP)",
|
||||
"-10040": "Formato de mensagem inválido",
|
||||
"-10050": "Certificado inválido",
|
||||
"-704000000": "Erro desconhecido",
|
||||
"-704010000": "Não autorizado (o dispositivo pode ter sido excluído)",
|
||||
"-704014006": "Descrição do dispositivo não encontrada",
|
||||
"-704030013": "Propriedade não pode ser lida",
|
||||
"-704030023": "Propriedade não pode ser escrita",
|
||||
"-704030033": "Propriedade não pode ser assinada",
|
||||
"-704040002": "Serviço inexistente",
|
||||
"-704040003": "Propriedade inexistente",
|
||||
"-704040004": "Evento inexistente",
|
||||
"-704040005": "Ação inexistente",
|
||||
"-704040999": "Funcionalidade não lançada",
|
||||
"-704042001": "Dispositivo inexistente",
|
||||
"-704042011": "Dispositivo offline",
|
||||
"-704053036": "Tempo de operação do dispositivo expirado",
|
||||
"-704053100": "Dispositivo não pode executar esta operação no estado atual",
|
||||
"-704083036": "Tempo de operação do dispositivo expirado",
|
||||
"-704090001": "Dispositivo inexistente",
|
||||
"-704220008": "ID inválido",
|
||||
"-704220025": "Número de parâmetros de ação incompatível",
|
||||
"-704220035": "Parâmetro de ação incorreto",
|
||||
"-704220043": "Valor da propriedade incorreto",
|
||||
"-704222034": "Valor de retorno de ação incorreto",
|
||||
"-705004000": "Erro desconhecido",
|
||||
"-705004501": "Erro desconhecido",
|
||||
"-705201013": "Propriedade não pode ser lida",
|
||||
"-705201015": "Erro na execução da ação",
|
||||
"-705201023": "Propriedade não pode ser escrita",
|
||||
"-705201033": "Propriedade não pode ser assinada",
|
||||
"-706012000": "Erro desconhecido",
|
||||
"-706012013": "Propriedade não pode ser lida",
|
||||
"-706012015": "Erro na execução da ação",
|
||||
"-706012023": "Propriedade não pode ser escrita",
|
||||
"-706012033": "Propriedade não pode ser assinada",
|
||||
"-706012043": "Valor da propriedade incorreto",
|
||||
"-706014006": "Descrição do dispositivo não encontrada"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "dispositivos",
|
||||
"found_central_gateway": ", encontrou a central de gateway local",
|
||||
"without_room": "Sem quarto atribuído",
|
||||
"no_display": "Não exibir"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "Automático",
|
||||
"cloud": "Nuvem"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "Ou lógica",
|
||||
"and": "E lógica"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "Excluir",
|
||||
"include": "Incluir"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "dispositivo yunyi",
|
||||
"2": "dispositivo de nuvem",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "dispositivo virtual",
|
||||
"6": "BLE",
|
||||
"7": "AP local",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "outro",
|
||||
"10": "plug-in de função",
|
||||
"11": "rede celular",
|
||||
"12": "cabo",
|
||||
"13": "NB-IoT",
|
||||
"14": "acesso à nuvem de terceiros",
|
||||
"15": "dispositivo de controle remoto infravermelho",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "grupo de dispositivos virtuais",
|
||||
"18": "subdispositivo de gateway",
|
||||
"19": "subdispositivo de gateway de nível de segurança",
|
||||
"22": "PLC",
|
||||
"23": "somente cabo",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+rede celular"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "Não sincronizar",
|
||||
"home_room": "Nome da casa e Nome do quarto (Xiaomi Home Quarto)",
|
||||
"room": "Nome do quarto (Quarto)",
|
||||
"home": "Nome da casa (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "Habilitar",
|
||||
"disable": "Desabilitar"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Entidade do sensor de texto",
|
||||
"bool": "Entidade do sensor binário"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Adicionar",
|
||||
"del": "Indisponível",
|
||||
"offline": "Offline"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[Aviso]** Detectado que várias interfaces podem estar conectadas à mesma rede, escolha com cuidado.",
|
||||
"net_unavailable": "Interface indisponível"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "Autenticação bem-sucedida",
|
||||
"content": "Por favor, feche esta página e volte para a página de autenticação da conta para clicar em 'Seguinte'.",
|
||||
"button": "Fechar"
|
||||
},
|
||||
"fail": {
|
||||
"title": "Falha na autenticação",
|
||||
"content": "{error_msg}, por favor, feche esta página e volte para a página de autenticação da conta para clicar no link de autenticação novamente.",
|
||||
"button": "Fechar"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "Parâmetros de resposta inválidos ('code' ou 'state' está vazio)",
|
||||
"-10101": "O campo 'state' fornecido não corresponde"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "Informações de autenticação inválidas, a conexão na nuvem ficará indisponível. Por favor, acesse a página de integração do Xiaomi Home e clique em 'Opções' para autenticar novamente.",
|
||||
"invalid_device_cache": "Erro no cache de informações do dispositivo. Por favor, acesse a página de integração do Xiaomi Home e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.",
|
||||
"invalid_cert_info": "Certificado de usuário inválido, a conexão com a central local ficará indisponível. Por favor, acesse a página de integração do Xiaomi Home e clique em 'Opções' para autenticar novamente.",
|
||||
"device_cloud_error": "Erro ao obter informações do dispositivo na nuvem. Verifique a conexão de rede local.",
|
||||
"xiaomi_home_error_title": "Erro de integração do Xiaomi Home",
|
||||
"xiaomi_home_error": "Detectado erro em **{nick_name}({uid}, {cloud_server})**. Por favor, acesse a página de opções para reconfigurar.\n\n**Informação do erro**: \n{message}",
|
||||
"device_list_changed_title": "Mudança na lista de dispositivos do Xiaomi Home",
|
||||
"device_list_changed": "Detectada alteração nas informações do dispositivo de **{nick_name}({uid}, {cloud_server})**. Por favor, acesse a página de opções de integração e clique em 'Opções -> Atualizar lista de dispositivos' para atualizar as informações locais.\n\nStatus atual da rede: {network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} novos dispositivos**: \n{message}",
|
||||
"device_list_del": "\n**{count} dispositivos indisponíveis**: \n{message}",
|
||||
"device_list_offline": "\n**{count} dispositivos offline**: \n{message}",
|
||||
"network_status_online": "Online",
|
||||
"network_status_offline": "Offline",
|
||||
"central_state_changed_title": "Estado da ligação do gateway central",
|
||||
"central_state_changed": "**{nick_name}({uid}, {cloud_server})** ligação local do gateway central: {conn_status}",
|
||||
"central_state_connected": "ligado",
|
||||
"central_state_disconnected": "desligado",
|
||||
"device_exec_error": "Erro de execução"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "Erro desconhecido",
|
||||
"-10001": "Serviço indisponível",
|
||||
"-10002": "Parâmetro inválido",
|
||||
"-10003": "Recursos insuficientes",
|
||||
"-10004": "Erro interno",
|
||||
"-10005": "Permissão negada",
|
||||
"-10006": "Tempo limite de execução",
|
||||
"-10007": "Dispositivo offline ou inexistente",
|
||||
"-10020": "Não autorizado (OAuth2)",
|
||||
"-10030": "Token inválido (HTTP)",
|
||||
"-10040": "Formato de mensagem inválido",
|
||||
"-10050": "Certificado inválido",
|
||||
"-704000000": "Erro desconhecido",
|
||||
"-704010000": "Não autorizado (o dispositivo pode ter sido removido)",
|
||||
"-704014006": "Descrição do dispositivo não encontrada",
|
||||
"-704030013": "Propriedade não legível",
|
||||
"-704030023": "Propriedade não gravável",
|
||||
"-704030033": "Propriedade não subscritível",
|
||||
"-704040002": "Serviço inexistente",
|
||||
"-704040003": "Propriedade inexistente",
|
||||
"-704040004": "Evento inexistente",
|
||||
"-704040005": "Ação inexistente",
|
||||
"-704040999": "Funcionalidade não disponível",
|
||||
"-704042001": "Dispositivo inexistente",
|
||||
"-704042011": "Dispositivo offline",
|
||||
"-704053036": "Tempo limite de operação do dispositivo",
|
||||
"-704053100": "O dispositivo não pode executar esta operação no estado atual",
|
||||
"-704083036": "Tempo limite de operação do dispositivo",
|
||||
"-704090001": "Dispositivo inexistente",
|
||||
"-704220008": "ID inválido",
|
||||
"-704220025": "Número de parâmetros da ação não corresponde",
|
||||
"-704220035": "Erro nos parâmetros da ação",
|
||||
"-704220043": "Valor de propriedade inválido",
|
||||
"-704222034": "Erro no valor de retorno da ação",
|
||||
"-705004000": "Erro desconhecido",
|
||||
"-705004501": "Erro desconhecido",
|
||||
"-705201013": "Propriedade não legível",
|
||||
"-705201015": "Erro na execução da ação",
|
||||
"-705201023": "Propriedade não gravável",
|
||||
"-705201033": "Propriedade não subscritível",
|
||||
"-706012000": "Erro desconhecido",
|
||||
"-706012013": "Propriedade não legível",
|
||||
"-706012015": "Erro na execução da ação",
|
||||
"-706012023": "Propriedade não gravável",
|
||||
"-706012033": "Propriedade não subscritível",
|
||||
"-706012043": "Valor de propriedade inválido",
|
||||
"-706014006": "Descrição do dispositivo não encontrada"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "устройства",
|
||||
"found_central_gateway": ", найден локальный центральный шлюз",
|
||||
"without_room": "без комнаты",
|
||||
"no_display": "не отображать"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "автоматический",
|
||||
"cloud": "облако"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "логика ИЛИ",
|
||||
"and": "логика И"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "исключить",
|
||||
"include": "включить"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "yunyi устройство",
|
||||
"2": "Облачное устройство",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "Виртуальное устройство",
|
||||
"6": "BLE",
|
||||
"7": "Локальный AP",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "Другое",
|
||||
"10": "Плагин функции",
|
||||
"11": "Сотовая сеть",
|
||||
"12": "Кабель",
|
||||
"13": "NB-IoT",
|
||||
"14": "Доступ к стороннему облаку",
|
||||
"15": "Устройство с ИК-пультом дистанционного управления",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "Группа виртуальных устройств",
|
||||
"18": "Подустройство шлюза",
|
||||
"19": "Подустройство шлюза уровня безопасности",
|
||||
"22": "PLC",
|
||||
"23": "Только кабель",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+Сотовая сеть"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "не синхронизировать",
|
||||
"home_room": "название дома и название комнаты (Xiaomi Home Спальня)",
|
||||
"room": "название комнаты (Спальня)",
|
||||
"home": "название дома (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "Включить",
|
||||
"disable": "Отключить"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Сущность текстового датчика",
|
||||
"bool": "Сущность бинарного датчика"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Добавить",
|
||||
"del": "Недоступно",
|
||||
"offline": "Не в сети"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[Уведомление]** Обнаружено несколько сетевых карт, которые могут быть подключены к одной и той же сети. Пожалуйста, обратите внимание на выбор.",
|
||||
"net_unavailable": "Интерфейс недоступен"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "Аутентификация успешна",
|
||||
"content": "Пожалуйста, закройте эту страницу и вернитесь на страницу аутентификации учетной записи, чтобы нажать 'Далее'.",
|
||||
"button": "Закрыть"
|
||||
},
|
||||
"fail": {
|
||||
"title": "Аутентификация не удалась",
|
||||
"content": "{error_msg}, пожалуйста, закройте эту страницу и вернитесь на страницу аутентификации учетной записи, чтобы снова нажать на ссылку аутентификации.",
|
||||
"button": "Закрыть"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "Недействительные параметры ответа ('code' или 'state' поле пусто)",
|
||||
"-10101": "Переданное поле 'state' не совпадает"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "Информация об аутентификации недействительна, облако будет недоступно, пожалуйста, войдите на страницу интеграции Xiaomi Home, нажмите 'Опции' для повторной аутентификации",
|
||||
"invalid_device_cache": "Кэш информации об устройстве ненормальный, пожалуйста, войдите на страницу интеграции Xiaomi Home, нажмите 'Опции->Обновить список устройств', обновите локальный кэш",
|
||||
"invalid_cert_info": "Недействительный пользовательский сертификат, локальное центральное соединение будет недоступно, пожалуйста, войдите на страницу интеграции Xiaomi Home, нажмите 'Опции' для повторной аутентификации",
|
||||
"device_cloud_error": "При получении информации об устройстве из облака произошло исключение, пожалуйста, проверьте локальное сетевое соединение",
|
||||
"xiaomi_home_error_title": "Ошибка интеграции Xiaomi Home",
|
||||
"xiaomi_home_error": "Обнаружена ошибка **{nick_name}({uid}, {cloud_server})**, пожалуйста, войдите на страницу опций для повторной настройки.\n\n**Сообщение об ошибке**: \n{message}",
|
||||
"device_list_changed_title": "Изменения в списке устройств Xiaomi Home",
|
||||
"device_list_changed": "Обнаружены изменения в информации об устройствах **{nick_name}({uid}, {cloud_server})**, пожалуйста, войдите на страницу интеграции, нажмите `Опции->Обновить список устройств`, обновите локальную информацию об устройствах.\n\nТекущий статус сети: {network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} новых устройств:** \n{message}",
|
||||
"device_list_del": "\n**{count} устройств недоступно:** \n{message}",
|
||||
"device_list_offline": "\n**{count} устройств недоступно:** \n{message}",
|
||||
"network_status_online": "В сети",
|
||||
"network_status_offline": "Не в сети",
|
||||
"central_state_changed_title": "Статус подключения центрального шлюза",
|
||||
"central_state_changed": "**{nick_name}({uid}, {cloud_server})** локальное подключение центрального шлюза: {conn_status}",
|
||||
"central_state_connected": "подключено",
|
||||
"central_state_disconnected": "разъединено",
|
||||
"device_exec_error": "Ошибка выполнения"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "Неизвестная ошибка",
|
||||
"-10001": "Сервис недоступен",
|
||||
"-10002": "Недопустимый параметр",
|
||||
"-10003": "Недостаточно ресурсов",
|
||||
"-10004": "Внутренняя ошибка",
|
||||
"-10005": "Недостаточно прав",
|
||||
"-10006": "Тайм-аут выполнения",
|
||||
"-10007": "Устройство не в сети или не существует",
|
||||
"-10020": "Неавторизовано (OAuth2)",
|
||||
"-10030": "Недействительный токен (HTTP)",
|
||||
"-10040": "Недопустимый формат сообщения",
|
||||
"-10050": "Недействительный сертификат",
|
||||
"-704000000": "Неизвестная ошибка",
|
||||
"-704010000": "Неавторизовано (устройство могло быть удалено)",
|
||||
"-704014006": "Описание устройства не найдено",
|
||||
"-704030013": "Свойство не читается",
|
||||
"-704030023": "Свойство не записывается",
|
||||
"-704030033": "Свойство не подписывается",
|
||||
"-704040002": "Сервис не существует",
|
||||
"-704040003": "Свойство не существует",
|
||||
"-704040004": "Событие не существует",
|
||||
"-704040005": "Действие не существует",
|
||||
"-704040999": "Функция не в сети",
|
||||
"-704042001": "Устройство не существует",
|
||||
"-704042011": "Устройство не в сети",
|
||||
"-704053036": "Тайм-аут операции устройства",
|
||||
"-704053100": "Устройство не может выполнить эту операцию в текущем состоянии",
|
||||
"-704083036": "Тайм-аут операции устройства",
|
||||
"-704090001": "Устройство не существует",
|
||||
"-704220008": "Недействительный ID",
|
||||
"-704220025": "Несоответствие количества параметров действия",
|
||||
"-704220035": "Ошибка параметра действия",
|
||||
"-704220043": "Ошибка значения свойства",
|
||||
"-704222034": "Ошибка возвращаемого значения действия",
|
||||
"-705004000": "Неизвестная ошибка",
|
||||
"-705004501": "Неизвестная ошибка",
|
||||
"-705201013": "Свойство не читается",
|
||||
"-705201015": "Ошибка выполнения действия",
|
||||
"-705201023": "Свойство не записывается",
|
||||
"-705201033": "Свойство не подписывается",
|
||||
"-706012000": "Неизвестная ошибка",
|
||||
"-706012013": "Свойство не читается",
|
||||
"-706012015": "Ошибка выполнения действия",
|
||||
"-706012023": "Свойство не записывается",
|
||||
"-706012033": "Свойство не подписывается",
|
||||
"-706012043": "Ошибка значения свойства",
|
||||
"-706014006": "Описание устройства не найдено"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "Cihazlar",
|
||||
"found_central_gateway": ", Yerel Merkezi Hub Ağ Geçidi Bulundu",
|
||||
"without_room": "Oda atanmamış",
|
||||
"no_display": "Gösterme"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "Otomatik",
|
||||
"cloud": "Bulut"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "VEYA mantığı",
|
||||
"and": "VE mantığı"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "Hariç tut",
|
||||
"include": "Dahil et"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "yunyi cihazı",
|
||||
"2": "Bulut cihazı",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "Sanal cihaz",
|
||||
"6": "BLE",
|
||||
"7": "Yerel AP",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "Diğer",
|
||||
"10": "İşlev eklentisi",
|
||||
"11": "Hücresel ağ",
|
||||
"12": "Kablo",
|
||||
"13": "NB-IoT",
|
||||
"14": "Üçüncü taraf bulut erişimi",
|
||||
"15": "Kızılötesi uzaktan kumanda cihazı",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "Sanal cihaz grubu",
|
||||
"18": "Ağ geçidi alt cihazı",
|
||||
"19": "Güvenlik seviyesi ağ geçidi alt cihazı",
|
||||
"22": "PLC",
|
||||
"23": "Yalnızca kablo",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+Hücresel ağ"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "Senkronize etme",
|
||||
"home_room": "Ev Adı ve Oda Adı (Xiaomi Home Yatak Odası)",
|
||||
"room": "Oda Adı (Yatak Odası)",
|
||||
"home": "Ev Adı (Xiaomi Home)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "Etkinleştir",
|
||||
"disable": "Devre Dışı Bırak"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "Metin Sensör Varlığı",
|
||||
"bool": "İkili Sensör Varlığı"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "Ekle",
|
||||
"del": "Kullanılamıyor",
|
||||
"offline": "Çevrimdışı"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[Bildirim]** Aynı ağa bağlı olabilecek birden fazla ağ kartı algılandı. Lütfen seçime dikkat edin.",
|
||||
"net_unavailable": "Arayüz kullanılamıyor"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "Kimlik Doğrulama Başarılı",
|
||||
"content": "Lütfen bu sayfayı kapatın ve 'İleri'ye tıklamak için hesap kimlik doğrulama sayfasına geri dönün.",
|
||||
"button": "Kapat"
|
||||
},
|
||||
"fail": {
|
||||
"title": "Kimlik Doğrulama Başarısız",
|
||||
"content": "{error_msg}, lütfen bu sayfayı kapatın ve kimlik doğrulama bağlantısına tekrar tıklamak için hesap kimlik doğrulama sayfasına geri dönün.",
|
||||
"button": "Kapat"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "Geçersiz yanıt parametreleri ('code' veya 'state' alanı boş)",
|
||||
"-10101": "Geçirilen 'state' alanı uyumsuz"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "Kimlik doğrulama bilgileri geçersiz, bulut bağlantısı kullanılamayacak, lütfen Xiaomi Home entegrasyon sayfasına girin, yeniden kimlik doğrulaması yapmak için 'Seçenekler'e tıklayın",
|
||||
"invalid_device_cache": "Önbellek cihaz bilgileri anormal, lütfen Xiaomi Home entegrasyon sayfasına girin, yerel önbelleği güncellemek için 'Seçenekler->Cihaz listesini güncelle'ye tıklayın",
|
||||
"invalid_cert_info": "Geçersiz kullanıcı sertifikası, yerel merkezi bağlantı kullanılamayacak, lütfen Xiaomi Home entegrasyon sayfasına girin, yeniden kimlik doğrulaması yapmak için 'Seçenekler'e tıklayın",
|
||||
"device_cloud_error": "Buluttan cihaz bilgileri alınırken bir istisna oluştu, lütfen yerel ağ bağlantısını kontrol edin",
|
||||
"xiaomi_home_error_title": "Xiaomi Home Entegrasyon Hatası",
|
||||
"xiaomi_home_error": "**{nick_name}({uid}, {cloud_server})** hatası algılandı, lütfen yeniden yapılandırmak için seçenekler sayfasına girin.\n\n**Hata mesajı**: \n{message}",
|
||||
"device_list_changed_title": "Xiaomi Home cihaz listesi değişiklikleri",
|
||||
"device_list_changed": "**{nick_name}({uid}, {cloud_server})** cihaz bilgilerinin değiştiği algılandı, lütfen entegrasyon seçenekleri sayfasına girin, yerel cihaz bilgilerini güncellemek için `Seçenekler->Cihaz listesini güncelle`ye tıklayın.\n\nGeçerli ağ durumu: {network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} yeni cihaz:** \n{message}",
|
||||
"device_list_del": "\n**{count} cihaz kullanılamıyor:** \n{message}",
|
||||
"device_list_offline": "\n**{count} cihaz çevrimdışı:** \n{message}",
|
||||
"network_status_online": "Çevrimiçi",
|
||||
"network_status_offline": "Çevrimdışı",
|
||||
"central_state_changed_title": "Merkezi Hub Ağ Geçidi Bağlantı Durumu",
|
||||
"central_state_changed": "**{nick_name}({uid}, {cloud_server})** Xiaomi merkezi hub ağ geçidine yerel bağlantı: {conn_status}",
|
||||
"central_state_connected": "Bağlandı",
|
||||
"central_state_disconnected": "Bağlantı kesildi",
|
||||
"device_exec_error": "Yürütme hatası"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "Bilinmeyen hata",
|
||||
"-10001": "Hizmet kullanılamıyor",
|
||||
"-10002": "Geçersiz parametre",
|
||||
"-10003": "Yetersiz kaynaklar",
|
||||
"-10004": "İç hata",
|
||||
"-10005": "Yetersiz izinler",
|
||||
"-10006": "Yürütme zaman aşımı",
|
||||
"-10007": "Cihaz çevrimdışı veya mevcut değil",
|
||||
"-10020": "Yetkisiz (OAuth2)",
|
||||
"-10030": "Geçersiz token (HTTP)",
|
||||
"-10040": "Geçersiz mesaj formatı",
|
||||
"-10050": "Geçersiz sertifika",
|
||||
"-704000000": "Bilinmeyen hata",
|
||||
"-704010000": "Yetkisiz (cihaz silinmiş olabilir)",
|
||||
"-704014006": "Cihaz açıklaması bulunamadı",
|
||||
"-704030013": "Özellik okunabilir değil",
|
||||
"-704030023": "Özellik yazılabilir değil",
|
||||
"-704030033": "Özellik abone edilebilir değil",
|
||||
"-704040002": "Hizmet mevcut değil",
|
||||
"-704040003": "Özellik mevcut değil",
|
||||
"-704040004": "Olay mevcut değil",
|
||||
"-704040005": "Eylem mevcut değil",
|
||||
"-704040999": "Özellik çevrimiçi değil",
|
||||
"-704042001": "Cihaz mevcut değil",
|
||||
"-704042011": "Cihaz çevrimdışı",
|
||||
"-704053036": "Cihaz işlemi zaman aşımı",
|
||||
"-704053100": "Cihaz mevcut durumda bu işlemi gerçekleştiremiyor",
|
||||
"-704083036": "Cihaz işlemi zaman aşımı",
|
||||
"-704090001": "Cihaz mevcut değil",
|
||||
"-704220008": "Geçersiz ID",
|
||||
"-704220025": "Eylem parametre sayısı uyumsuz",
|
||||
"-704220035": "Eylem parametre hatası",
|
||||
"-704220043": "Özellik değer hatası",
|
||||
"-704222034": "Eylem dönüş değer hatası",
|
||||
"-705004000": "Bilinmeyen hata",
|
||||
"-705004501": "Bilinmeyen hata",
|
||||
"-705201013": "Özellik okunabilir değil",
|
||||
"-705201015": "Eylem yürütme hatası",
|
||||
"-705201023": "Özellik yazılabilir değil",
|
||||
"-705201033": "Özellik abone edilebilir değil",
|
||||
"-706012000": "Bilinmeyen hata",
|
||||
"-706012013": "Özellik okunabilir değil",
|
||||
"-706012015": "Eylem yürütme hatası",
|
||||
"-706012023": "Özellik yazılabilir değil",
|
||||
"-706012033": "Özellik abone edilebilir değil",
|
||||
"-706012043": "Özellik değer hatası",
|
||||
"-706014006": "Cihaz açıklaması bulunamadı"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "个设备",
|
||||
"found_central_gateway": ",发现本地中枢网关",
|
||||
"without_room": "未分配房间",
|
||||
"no_display": "不显示"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "自动",
|
||||
"cloud": "云端"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "不同步",
|
||||
"home_room": "家庭名 和 房间名 (米家 卧室)",
|
||||
"room": "房间名 (卧室)",
|
||||
"home": "家庭名 (米家)"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "或逻辑",
|
||||
"and": "与逻辑"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "排除",
|
||||
"include": "包含"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "yunyi设备",
|
||||
"2": "云接入设备",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "虚拟设备",
|
||||
"6": "BLE",
|
||||
"7": "本地AP",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "其他",
|
||||
"10": "功能插件",
|
||||
"11": "蜂窝网",
|
||||
"12": "网线",
|
||||
"13": "NB-IoT",
|
||||
"14": "第三方云接入",
|
||||
"15": "红外遥控器子设备",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "虚拟设备组",
|
||||
"18": "代理网关子设备",
|
||||
"19": "安全级代理网关子设备",
|
||||
"22": "PLC",
|
||||
"23": "仅网线",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+蜂窝网"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "启用",
|
||||
"disable": "禁用"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "文本传感器实体",
|
||||
"bool": "二进制传感器实体"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "新增",
|
||||
"del": "不可用",
|
||||
"offline": "离线"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[提示]** 检测到多个网卡可能连接同一个网络,请注意选择。",
|
||||
"net_unavailable": "接口不可用"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "认证成功",
|
||||
"content": "请关闭此页面,返回账号认证页面点击“下一步”",
|
||||
"button": "关闭"
|
||||
},
|
||||
"fail": {
|
||||
"title": "认证失败",
|
||||
"content": "{error_msg},请关闭此页面,返回账号认证页面重新点击认链接进行认证。",
|
||||
"button": "关闭"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "无效的响应参数(“code”或者“state”字段为空)",
|
||||
"-10101": "传入“state”字段不一致"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "认证信息失效,云端链路将不可用,请进入 Xiaomi Home 集成页面,点击“选项”重新认证",
|
||||
"invalid_device_cache": "缓存设备信息异常,请进入 Xiaomi Home 集成页面,点击`选项->更新设备列表`,更新本地设备信息",
|
||||
"invalid_cert_info": "无效的用户证书,本地中枢链路将不可用,请进入 Xiaomi Home 集成页面,点击“选项”重新认证",
|
||||
"device_cloud_error": "从云端获取设备信息异常,请检查本地网络连接",
|
||||
"xiaomi_home_error_title": "Xiaomi Home 集成错误",
|
||||
"xiaomi_home_error": "检测到 **{nick_name}({uid}, {cloud_server})** 出现错误,请进入选项页面重新配置。\n\n**错误信息**: \n{message}",
|
||||
"device_list_changed_title": "Xiaomi Home设备列表变化",
|
||||
"device_list_changed": "检测到 **{nick_name}({uid}, {cloud_server})** 设备信息发生变化,请进入集成选项页面,点击`选项->更新设备列表`,更新本地设备信息。\n\n当前网络状态:{network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} 个新增设备**: \n{message}",
|
||||
"device_list_del": "\n**{count} 个设备不可用**: \n{message}",
|
||||
"device_list_offline": "\n**{count} 个设备离线**: \n{message}",
|
||||
"network_status_online": "在线",
|
||||
"network_status_offline": "离线",
|
||||
"central_state_changed_title": "中枢网关连接状态",
|
||||
"central_state_changed":"**{nick_name}({uid}, {cloud_server})** 中枢网关本地连接链路: {conn_status}",
|
||||
"central_state_connected": "已连接",
|
||||
"central_state_disconnected": "断连",
|
||||
"device_exec_error": "执行错误"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "未知错误",
|
||||
"-10001": "服务不可用",
|
||||
"-10002": "参数无效",
|
||||
"-10003": "资源不足",
|
||||
"-10004": "内部错误",
|
||||
"-10005": "权限不足",
|
||||
"-10006": "执行超时",
|
||||
"-10007": "设备离线或者不存在",
|
||||
"-10020": "未授权OAuth2)",
|
||||
"-10030": "无效的token(HTTP)",
|
||||
"-10040": "无效的消息格式",
|
||||
"-10050": "无效的证书",
|
||||
"-704000000": "未知错误",
|
||||
"-704010000": "未授权(设备可能被删除)",
|
||||
"-704014006": "没找到设备描述",
|
||||
"-704030013": "Property不可读",
|
||||
"-704030023": "Property不可写",
|
||||
"-704030033": "Property不可订阅",
|
||||
"-704040002": "Service不存在",
|
||||
"-704040003": "Property不存在",
|
||||
"-704040004": "Event不存在",
|
||||
"-704040005": "Action不存在",
|
||||
"-704040999": "功能未上线",
|
||||
"-704042001": "Device不存在",
|
||||
"-704042011": "设备离线",
|
||||
"-704053036": "设备操作超时",
|
||||
"-704053100": "设备在当前状态下无法执行此操作",
|
||||
"-704083036": "设备操作超时",
|
||||
"-704090001": "Device不存在",
|
||||
"-704220008": "无效的ID",
|
||||
"-704220025": "Action参数个数不匹配",
|
||||
"-704220035": "Action参数错误",
|
||||
"-704220043": "Property值错误",
|
||||
"-704222034": "Action返回值错误",
|
||||
"-705004000": "未知错误",
|
||||
"-705004501": "未知错误",
|
||||
"-705201013": "Property不可读",
|
||||
"-705201015": "Action执行错误",
|
||||
"-705201023": "Property不可写",
|
||||
"-705201033": "Property不可订阅",
|
||||
"-706012000": "未知错误",
|
||||
"-706012013": "Property不可读",
|
||||
"-706012015": "Action执行错误",
|
||||
"-706012023": "Property不可写",
|
||||
"-706012033": "Property不可订阅",
|
||||
"-706012043": "Property值错误",
|
||||
"-706014006": "没找到设备描述"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"config": {
|
||||
"other": {
|
||||
"devices": "個設備",
|
||||
"found_central_gateway": ",發現本地中樞網關",
|
||||
"without_room": "未分配房間",
|
||||
"no_display": "不顯示"
|
||||
},
|
||||
"control_mode": {
|
||||
"auto": "自動",
|
||||
"cloud": "雲端"
|
||||
},
|
||||
"statistics_logic": {
|
||||
"or": "或邏輯",
|
||||
"and": "與邏輯"
|
||||
},
|
||||
"filter_mode": {
|
||||
"exclude": "排除",
|
||||
"include": "包含"
|
||||
},
|
||||
"connect_type": {
|
||||
"0": "WiFi",
|
||||
"1": "yunyi設備",
|
||||
"2": "雲接入設備",
|
||||
"3": "ZigBee",
|
||||
"4": "webSocket",
|
||||
"5": "虛擬設備",
|
||||
"6": "BLE",
|
||||
"7": "本地AP",
|
||||
"8": "WiFi+BLE",
|
||||
"9": "其他",
|
||||
"10": "功能插件",
|
||||
"11": "蜂窩網",
|
||||
"12": "網線",
|
||||
"13": "NB-IoT",
|
||||
"14": "第三方雲接入",
|
||||
"15": "紅外遙控器子設備",
|
||||
"16": "BLE-Mesh",
|
||||
"17": "虛擬設備組",
|
||||
"18": "代理網關子設備",
|
||||
"19": "安全級代理網關子設備",
|
||||
"22": "PLC",
|
||||
"23": "僅網線",
|
||||
"24": "Matter",
|
||||
"25": "WiFi+蜂窩網"
|
||||
},
|
||||
"room_name_rule": {
|
||||
"none": "不同步",
|
||||
"home_room": "家庭名 和 房間名 (米家 臥室)",
|
||||
"room": "房間名 (臥室)",
|
||||
"home": "家庭名 (米家)"
|
||||
},
|
||||
"option_status": {
|
||||
"enable": "啟用",
|
||||
"disable": "禁用"
|
||||
},
|
||||
"binary_mode": {
|
||||
"text": "文本傳感器實體",
|
||||
"bool": "二進制傳感器實體"
|
||||
},
|
||||
"device_state": {
|
||||
"add": "新增",
|
||||
"del": "不可用",
|
||||
"offline": "離線"
|
||||
},
|
||||
"lan_ctrl_config": {
|
||||
"notice_net_dup": "\r\n**[提示]** 檢測到多個網卡可能連接同一個網絡,請注意選擇。",
|
||||
"net_unavailable": "接口不可用"
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"success": {
|
||||
"title": "認證成功",
|
||||
"content": "請關閉此頁面,返回帳號認證頁面點擊“下一步”",
|
||||
"button": "關閉"
|
||||
},
|
||||
"fail": {
|
||||
"title": "認證失敗",
|
||||
"content": "{error_msg},請關閉此頁面,返回帳號認證頁面重新點擊認鏈接進行認證。",
|
||||
"button": "關閉"
|
||||
},
|
||||
"error_msg": {
|
||||
"-10100": "無效的響應參數(“code”或者“state”字段為空)",
|
||||
"-10101": "傳入的“state”字段不一致"
|
||||
}
|
||||
},
|
||||
"miot": {
|
||||
"client": {
|
||||
"invalid_oauth_info": "認證信息失效,雲端鏈路將不可用,請進入 Xiaomi Home 集成頁面,點擊“選項”重新認證",
|
||||
"invalid_device_cache": "緩存設備信息異常,請進入 Xiaomi Home 集成頁面,點擊`選項->更新設備列表`,更新本地設備信息",
|
||||
"invalid_cert_info": "無效的用戶證書,本地中樞鏈路將不可用,請進入 Xiaomi Home 集成頁面,點擊“選項”重新認證",
|
||||
"device_cloud_error": "從雲端獲取設備信息異常,請檢查本地網絡連接",
|
||||
"xiaomi_home_error_title": "Xiaomi Home 集成錯誤",
|
||||
"xiaomi_home_error": "檢測到 **{nick_name}({uid}, {cloud_server})** 出現錯誤,請進入選項頁面重新配置。\n\n**錯誤信息**: \n{message}",
|
||||
"device_list_changed_title": "Xiaomi Home設備列表變化",
|
||||
"device_list_changed": "檢測到 **{nick_name}({uid}, {cloud_server})** 設備信息發生變化,請進入集成選項頁面,點擊`選項->更新設備列表`,更新本地設備信息。\n\n當前網絡狀態:{network_status}\n{message}\n",
|
||||
"device_list_add": "\n**{count} 個新增設備:** \n{message}",
|
||||
"device_list_del": "\n**{count} 個設備不可用:** \n{message}",
|
||||
"device_list_offline": "\n**{count} 個設備離線:** \n{message}",
|
||||
"network_status_online": "在線",
|
||||
"network_status_offline": "離線",
|
||||
"central_state_changed_title": "中枢網關連接狀態",
|
||||
"central_state_changed":"**{nick_name}({uid}, {cloud_server})** 中枢網關本地連接鏈路: {conn_status}",
|
||||
"central_state_connected": "已連接",
|
||||
"central_state_disconnected": "断連",
|
||||
"device_exec_error": "執行錯誤"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"common": {
|
||||
"-10000": "未知錯誤",
|
||||
"-10001": "服務不可用",
|
||||
"-10002": "參數無效",
|
||||
"-10003": "資源不足",
|
||||
"-10004": "內部錯誤",
|
||||
"-10005": "權限不足",
|
||||
"-10006": "執行超時",
|
||||
"-10007": "設備離線或者不存在",
|
||||
"-10020": "未授權(OAuth2)",
|
||||
"-10030": "無效的token(HTTP)",
|
||||
"-10040": "無效的消息格式",
|
||||
"-10050": "無效的證書",
|
||||
"-704000000": "未知錯誤",
|
||||
"-704010000": "未授權(設備可能被刪除)",
|
||||
"-704014006": "沒找到設備描述",
|
||||
"-704030013": "Property不可讀",
|
||||
"-704030023": "Property不可寫",
|
||||
"-704030033": "Property不可訂閱",
|
||||
"-704040002": "Service不存在",
|
||||
"-704040003": "Property不存在",
|
||||
"-704040004": "Event不存在",
|
||||
"-704040005": "Action不存在",
|
||||
"-704040999": "功能未上線",
|
||||
"-704042001": "Device不存在",
|
||||
"-704042011": "設備離線",
|
||||
"-704053036": "設備操作超時",
|
||||
"-704053100": "設備在當前狀態下無法執行此操作",
|
||||
"-704083036": "設備操作超時",
|
||||
"-704090001": "Device不存在",
|
||||
"-704220008": "無效的ID",
|
||||
"-704220025": "Action參數個數不匹配",
|
||||
"-704220035": "Action參數錯誤",
|
||||
"-704220043": "Property值錯誤",
|
||||
"-704222034": "Action返回值錯誤",
|
||||
"-705004000": "未知錯誤",
|
||||
"-705004501": "未知錯誤",
|
||||
"-705201013": "Property不可讀",
|
||||
"-705201015": "Action執行錯誤",
|
||||
"-705201023": "Property不可寫",
|
||||
"-705201033": "Property不可訂閱",
|
||||
"-706012000": "未知錯誤",
|
||||
"-706012013": "Property不可讀",
|
||||
"-706012015": "Action執行錯誤",
|
||||
"-706012023": "Property不可寫",
|
||||
"-706012033": "Property不可訂閱",
|
||||
"-706012043": "Property值錯誤",
|
||||
"-706014006": "沒找到設備描述"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,849 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
MIoT http client.
|
||||
"""
|
||||
import asyncio
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
from urllib.parse import urlencode
|
||||
import aiohttp
|
||||
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .common import calc_group_id
|
||||
from .const import (
|
||||
UNSUPPORTED_MODELS,
|
||||
DEFAULT_OAUTH2_API_HOST,
|
||||
MIHOME_HTTP_API_TIMEOUT,
|
||||
OAUTH2_AUTH_URL)
|
||||
from .miot_error import MIoTErrorCode, MIoTHttpError, MIoTOauthError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TOKEN_EXPIRES_TS_RATIO = 0.7
|
||||
|
||||
|
||||
class MIoTOauthClient:
|
||||
"""oauth agent url, default: product env."""
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
_session: aiohttp.ClientSession
|
||||
_oauth_host: str
|
||||
_client_id: int
|
||||
_redirect_url: str
|
||||
_device_id: str
|
||||
_state: str
|
||||
|
||||
def __init__(
|
||||
self, client_id: str, redirect_url: str, cloud_server: str,
|
||||
uuid: str, loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> None:
|
||||
self._main_loop = loop or asyncio.get_running_loop()
|
||||
if client_id is None or client_id.strip() == '':
|
||||
raise MIoTOauthError('invalid client_id')
|
||||
if not redirect_url:
|
||||
raise MIoTOauthError('invalid redirect_url')
|
||||
if not cloud_server:
|
||||
raise MIoTOauthError('invalid cloud_server')
|
||||
if not uuid:
|
||||
raise MIoTOauthError('invalid uuid')
|
||||
|
||||
self._client_id = int(client_id)
|
||||
self._redirect_url = redirect_url
|
||||
if cloud_server == 'cn':
|
||||
self._oauth_host = DEFAULT_OAUTH2_API_HOST
|
||||
else:
|
||||
self._oauth_host = f'{cloud_server}.{DEFAULT_OAUTH2_API_HOST}'
|
||||
self._device_id = f'ha.{uuid}'
|
||||
self._state = hashlib.sha1(
|
||||
f'd={self._device_id}'.encode('utf-8')).hexdigest()
|
||||
self._session = aiohttp.ClientSession(loop=self._main_loop)
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
return self._state
|
||||
|
||||
async def deinit_async(self) -> None:
|
||||
if self._session and not self._session.closed:
|
||||
await self._session.close()
|
||||
|
||||
def set_redirect_url(self, redirect_url: str) -> None:
|
||||
if not isinstance(redirect_url, str) or redirect_url.strip() == '':
|
||||
raise MIoTOauthError('invalid redirect_url')
|
||||
self._redirect_url = redirect_url
|
||||
|
||||
def gen_auth_url(
|
||||
self,
|
||||
redirect_url: Optional[str] = None,
|
||||
state: Optional[str] = None,
|
||||
scope: Optional[list] = None,
|
||||
skip_confirm: Optional[bool] = False,
|
||||
) -> str:
|
||||
"""get auth url
|
||||
|
||||
Args:
|
||||
redirect_url
|
||||
state
|
||||
scope (list, optional):
|
||||
开放数据接口权限 ID,可以传递多个,用空格分隔,具体值可以参考开放
|
||||
[数据接口权限列表](https://dev.mi.com/distribute/doc/details?pId=1518).
|
||||
Defaults to None.\n
|
||||
skip_confirm (bool, optional):
|
||||
默认值为true,授权有效期内的用户在已登录情况下,不显示授权页面,直接通过。
|
||||
如果需要用户每次手动授权,设置为false. Defaults to True.\n
|
||||
|
||||
Returns:
|
||||
str: _description_
|
||||
"""
|
||||
params: dict = {
|
||||
'redirect_uri': redirect_url or self._redirect_url,
|
||||
'client_id': self._client_id,
|
||||
'response_type': 'code',
|
||||
'device_id': self._device_id,
|
||||
'state': self._state
|
||||
}
|
||||
if state:
|
||||
params['state'] = state
|
||||
if scope:
|
||||
params['scope'] = ' '.join(scope).strip()
|
||||
params['skip_confirm'] = skip_confirm
|
||||
encoded_params = urlencode(params)
|
||||
|
||||
return f'{OAUTH2_AUTH_URL}?{encoded_params}'
|
||||
|
||||
async def __get_token_async(self, data) -> dict:
|
||||
http_res = await self._session.get(
|
||||
url=f'https://{self._oauth_host}/app/v2/ha/oauth/get_token',
|
||||
params={'data': json.dumps(data)},
|
||||
headers={'content-type': 'application/x-www-form-urlencoded'},
|
||||
timeout=MIHOME_HTTP_API_TIMEOUT
|
||||
)
|
||||
if http_res.status == 401:
|
||||
raise MIoTOauthError(
|
||||
'unauthorized(401)', MIoTErrorCode.CODE_OAUTH_UNAUTHORIZED)
|
||||
if http_res.status != 200:
|
||||
raise MIoTOauthError(
|
||||
f'invalid http status code, {http_res.status}')
|
||||
|
||||
res_str = await http_res.text()
|
||||
res_obj = json.loads(res_str)
|
||||
if (
|
||||
not res_obj
|
||||
or res_obj.get('code', None) != 0
|
||||
or 'result' not in res_obj
|
||||
or not all(
|
||||
key in res_obj['result']
|
||||
for key in ['access_token', 'refresh_token', 'expires_in'])
|
||||
):
|
||||
raise MIoTOauthError(f'invalid http response, {res_str}')
|
||||
|
||||
return {
|
||||
**res_obj['result'],
|
||||
'expires_ts': int(
|
||||
time.time() +
|
||||
(res_obj['result'].get('expires_in', 0)*TOKEN_EXPIRES_TS_RATIO))
|
||||
}
|
||||
|
||||
async def get_access_token_async(self, code: str) -> dict:
|
||||
"""get access token by authorization code
|
||||
|
||||
Args:
|
||||
code (str): auth code
|
||||
|
||||
Returns:
|
||||
str: _description_
|
||||
"""
|
||||
if not isinstance(code, str):
|
||||
raise MIoTOauthError('invalid code')
|
||||
|
||||
return await self.__get_token_async(data={
|
||||
'client_id': self._client_id,
|
||||
'redirect_uri': self._redirect_url,
|
||||
'code': code,
|
||||
'device_id': self._device_id
|
||||
})
|
||||
|
||||
async def refresh_access_token_async(self, refresh_token: str) -> dict:
|
||||
"""get access token by refresh token.
|
||||
|
||||
Args:
|
||||
refresh_token (str): refresh_token
|
||||
|
||||
Returns:
|
||||
str: _description_
|
||||
"""
|
||||
if not isinstance(refresh_token, str):
|
||||
raise MIoTOauthError('invalid refresh_token')
|
||||
|
||||
return await self.__get_token_async(data={
|
||||
'client_id': self._client_id,
|
||||
'redirect_uri': self._redirect_url,
|
||||
'refresh_token': refresh_token,
|
||||
})
|
||||
|
||||
|
||||
class MIoTHttpClient:
|
||||
"""MIoT http client."""
|
||||
# pylint: disable=inconsistent-quotes
|
||||
GET_PROP_AGGREGATE_INTERVAL: float = 0.2
|
||||
GET_PROP_MAX_REQ_COUNT = 150
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
_session: aiohttp.ClientSession
|
||||
_host: str
|
||||
_base_url: str
|
||||
_client_id: str
|
||||
_access_token: str
|
||||
|
||||
_get_prop_timer: Optional[asyncio.TimerHandle]
|
||||
_get_prop_list: dict[str, dict]
|
||||
|
||||
def __init__(
|
||||
self, cloud_server: str, client_id: str, access_token: str,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> None:
|
||||
self._main_loop = loop or asyncio.get_running_loop()
|
||||
self._host = DEFAULT_OAUTH2_API_HOST
|
||||
self._base_url = ''
|
||||
self._client_id = ''
|
||||
self._access_token = ''
|
||||
|
||||
self._get_prop_timer = None
|
||||
self._get_prop_list = {}
|
||||
|
||||
if (
|
||||
not isinstance(cloud_server, str)
|
||||
or not isinstance(client_id, str)
|
||||
or not isinstance(access_token, str)
|
||||
):
|
||||
raise MIoTHttpError('invalid params')
|
||||
|
||||
self.update_http_header(
|
||||
cloud_server=cloud_server, client_id=client_id,
|
||||
access_token=access_token)
|
||||
|
||||
self._session = aiohttp.ClientSession(loop=self._main_loop)
|
||||
|
||||
async def deinit_async(self) -> None:
|
||||
if self._get_prop_timer:
|
||||
self._get_prop_timer.cancel()
|
||||
self._get_prop_timer = None
|
||||
for item in self._get_prop_list.values():
|
||||
fut: Optional[asyncio.Future] = item.get('fut', None)
|
||||
if fut:
|
||||
fut.cancel()
|
||||
self._get_prop_list.clear()
|
||||
if self._session and not self._session.closed:
|
||||
await self._session.close()
|
||||
|
||||
def update_http_header(
|
||||
self, cloud_server: Optional[str] = None,
|
||||
client_id: Optional[str] = None,
|
||||
access_token: Optional[str] = None
|
||||
) -> None:
|
||||
if isinstance(cloud_server, str):
|
||||
if cloud_server != 'cn':
|
||||
self._host = f'{cloud_server}.{DEFAULT_OAUTH2_API_HOST}'
|
||||
self._base_url = f'https://{self._host}'
|
||||
if isinstance(client_id, str):
|
||||
self._client_id = client_id
|
||||
if isinstance(access_token, str):
|
||||
self._access_token = access_token
|
||||
|
||||
@property
|
||||
def __api_request_headers(self) -> dict:
|
||||
return {
|
||||
'Host': self._host,
|
||||
'X-Client-BizId': 'haapi',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': f'Bearer{self._access_token}',
|
||||
'X-Client-AppId': self._client_id,
|
||||
}
|
||||
|
||||
# pylint: disable=unused-private-member
|
||||
async def __mihome_api_get_async(
|
||||
self, url_path: str, params: dict,
|
||||
timeout: int = MIHOME_HTTP_API_TIMEOUT
|
||||
) -> dict:
|
||||
http_res = await self._session.get(
|
||||
url=f'{self._base_url}{url_path}',
|
||||
params=params,
|
||||
headers=self.__api_request_headers,
|
||||
timeout=timeout)
|
||||
if http_res.status == 401:
|
||||
raise MIoTHttpError(
|
||||
'mihome api get failed, unauthorized(401)',
|
||||
MIoTErrorCode.CODE_HTTP_INVALID_ACCESS_TOKEN)
|
||||
if http_res.status != 200:
|
||||
raise MIoTHttpError(
|
||||
f'mihome api get failed, {http_res.status}, '
|
||||
f'{url_path}, {params}')
|
||||
res_str = await http_res.text()
|
||||
res_obj: dict = json.loads(res_str)
|
||||
if res_obj.get('code', None) != 0:
|
||||
raise MIoTHttpError(
|
||||
f'invalid response code, {res_obj.get("code",None)}, '
|
||||
f'{res_obj.get("message","")}')
|
||||
_LOGGER.debug(
|
||||
'mihome api get, %s%s, %s -> %s',
|
||||
self._base_url, url_path, params, res_obj)
|
||||
return res_obj
|
||||
|
||||
async def __mihome_api_post_async(
|
||||
self, url_path: str, data: dict,
|
||||
timeout: int = MIHOME_HTTP_API_TIMEOUT
|
||||
) -> dict:
|
||||
http_res = await self._session.post(
|
||||
url=f'{self._base_url}{url_path}',
|
||||
json=data,
|
||||
headers=self.__api_request_headers,
|
||||
timeout=timeout)
|
||||
if http_res.status == 401:
|
||||
raise MIoTHttpError(
|
||||
'mihome api get failed, unauthorized(401)',
|
||||
MIoTErrorCode.CODE_HTTP_INVALID_ACCESS_TOKEN)
|
||||
if http_res.status != 200:
|
||||
raise MIoTHttpError(
|
||||
f'mihome api post failed, {http_res.status}, '
|
||||
f'{url_path}, {data}')
|
||||
res_str = await http_res.text()
|
||||
res_obj: dict = json.loads(res_str)
|
||||
if res_obj.get('code', None) != 0:
|
||||
raise MIoTHttpError(
|
||||
f'invalid response code, {res_obj.get("code",None)}, '
|
||||
f'{res_obj.get("message","")}')
|
||||
_LOGGER.debug(
|
||||
'mihome api post, %s%s, %s -> %s',
|
||||
self._base_url, url_path, data, res_obj)
|
||||
return res_obj
|
||||
|
||||
async def get_user_info_async(self) -> dict:
|
||||
http_res = await self._session.get(
|
||||
url='https://open.account.xiaomi.com/user/profile',
|
||||
params={
|
||||
'clientId': self._client_id, 'token': self._access_token},
|
||||
headers={'content-type': 'application/x-www-form-urlencoded'},
|
||||
timeout=MIHOME_HTTP_API_TIMEOUT
|
||||
)
|
||||
|
||||
res_str = await http_res.text()
|
||||
res_obj = json.loads(res_str)
|
||||
if (
|
||||
not res_obj
|
||||
or res_obj.get('code', None) != 0
|
||||
or 'data' not in res_obj
|
||||
or 'miliaoNick' not in res_obj['data']
|
||||
):
|
||||
raise MIoTOauthError(f'invalid http response, {http_res.text}')
|
||||
|
||||
return res_obj['data']
|
||||
|
||||
async def get_central_cert_async(self, csr: str) -> str:
|
||||
if not isinstance(csr, str):
|
||||
raise MIoTHttpError('invalid params')
|
||||
|
||||
res_obj: dict = await self.__mihome_api_post_async(
|
||||
url_path='/app/v2/ha/oauth/get_central_crt',
|
||||
data={
|
||||
'csr': str(base64.b64encode(csr.encode('utf-8')), 'utf-8')
|
||||
}
|
||||
)
|
||||
if 'result' not in res_obj:
|
||||
raise MIoTHttpError('invalid response result')
|
||||
cert: str = res_obj['result'].get('cert', None)
|
||||
if not isinstance(cert, str):
|
||||
raise MIoTHttpError('invalid cert')
|
||||
|
||||
return cert
|
||||
|
||||
async def __get_dev_room_page_async(
|
||||
self, max_id: Optional[str] = None
|
||||
) -> dict:
|
||||
res_obj = await self.__mihome_api_post_async(
|
||||
url_path='/app/v2/homeroom/get_dev_room_page',
|
||||
data={
|
||||
'start_id': max_id,
|
||||
'limit': 150,
|
||||
},
|
||||
)
|
||||
if 'result' not in res_obj and 'info' not in res_obj['result']:
|
||||
raise MIoTHttpError('invalid response result')
|
||||
home_list: dict = {}
|
||||
for home in res_obj['result']['info']:
|
||||
if 'id' not in home:
|
||||
_LOGGER.error(
|
||||
'get dev room page error, invalid home, %s', home)
|
||||
continue
|
||||
home_list[str(home['id'])] = {'dids': home.get(
|
||||
'dids', None) or [], 'room_info': {}}
|
||||
for room in home.get('roomlist', []):
|
||||
if 'id' not in room:
|
||||
_LOGGER.error(
|
||||
'get dev room page error, invalid room, %s', room)
|
||||
continue
|
||||
home_list[str(home['id'])]['room_info'][str(room['id'])] = {
|
||||
'dids': room.get('dids', None) or []}
|
||||
if (
|
||||
res_obj['result'].get('has_more', False)
|
||||
and isinstance(res_obj['result'].get('max_id', None), str)
|
||||
):
|
||||
next_list = await self.__get_dev_room_page_async(
|
||||
max_id=res_obj['result']['max_id'])
|
||||
for home_id, info in next_list.items():
|
||||
home_list.setdefault(home_id, {'dids': [], 'room_info': {}})
|
||||
home_list[home_id]['dids'].extend(info['dids'])
|
||||
for room_id, info in info['room_info'].items():
|
||||
home_list[home_id]['room_info'].setdefault(
|
||||
room_id, {'dids': []})
|
||||
home_list[home_id]['room_info'][room_id]['dids'].extend(
|
||||
info['dids'])
|
||||
|
||||
return home_list
|
||||
|
||||
async def get_separated_shared_devices_async(self) -> dict[str, dict]:
|
||||
separated_shared_devices: dict = {}
|
||||
device_list: dict[str, dict] = await self.__get_device_list_page_async(
|
||||
dids=[], start_did=None)
|
||||
for did, value in device_list.items():
|
||||
if value['owner'] is not None and ('userid' in value['owner']) and (
|
||||
'nickname' in value['owner']
|
||||
):
|
||||
separated_shared_devices.setdefault(did, value['owner'])
|
||||
return separated_shared_devices
|
||||
|
||||
async def get_homeinfos_async(self) -> dict:
|
||||
res_obj = await self.__mihome_api_post_async(
|
||||
url_path='/app/v2/homeroom/gethome',
|
||||
data={
|
||||
'limit': 150,
|
||||
'fetch_share': True,
|
||||
'fetch_share_dev': True,
|
||||
'plat_form': 0,
|
||||
'app_ver': 9,
|
||||
},
|
||||
)
|
||||
if 'result' not in res_obj:
|
||||
raise MIoTHttpError('invalid response result')
|
||||
|
||||
uid: Optional[str] = None
|
||||
home_infos: dict = {}
|
||||
for device_source in ['homelist', 'share_home_list']:
|
||||
home_infos.setdefault(device_source, {})
|
||||
for home in res_obj['result'].get(device_source, []):
|
||||
if (
|
||||
'id' not in home
|
||||
or 'name' not in home
|
||||
or 'roomlist' not in home
|
||||
):
|
||||
continue
|
||||
if uid is None and device_source == 'homelist':
|
||||
uid = str(home['uid'])
|
||||
home_infos[device_source][home['id']] = {
|
||||
'home_id': home['id'],
|
||||
'home_name': home['name'],
|
||||
'city_id': home.get('city_id', None),
|
||||
'longitude': home.get('longitude', None),
|
||||
'latitude': home.get('latitude', None),
|
||||
'address': home.get('address', None),
|
||||
'dids': home.get('dids', []),
|
||||
'room_info': {
|
||||
room['id']: {
|
||||
'room_id': room['id'],
|
||||
'room_name': room['name'],
|
||||
'dids': room.get('dids', [])
|
||||
}
|
||||
for room in home.get('roomlist', [])
|
||||
if 'id' in room
|
||||
},
|
||||
'group_id': calc_group_id(
|
||||
uid=home['uid'], home_id=home['id']),
|
||||
'uid': str(home['uid'])
|
||||
}
|
||||
home_infos['uid'] = uid
|
||||
if (
|
||||
res_obj['result'].get('has_more', False)
|
||||
and isinstance(res_obj['result'].get('max_id', None), str)
|
||||
):
|
||||
more_list = await self.__get_dev_room_page_async(
|
||||
max_id=res_obj['result']['max_id'])
|
||||
for device_source in ['homelist', 'share_home_list']:
|
||||
for home_id, info in more_list.items():
|
||||
if home_id not in home_infos[device_source]:
|
||||
_LOGGER.info('unknown home, %s, %s', home_id, info)
|
||||
continue
|
||||
home_infos[device_source][home_id]['dids'].extend(
|
||||
info['dids'])
|
||||
for room_id, info in info['room_info'].items():
|
||||
home_infos[device_source][home_id][
|
||||
'room_info'].setdefault(
|
||||
room_id, {
|
||||
'room_id': room_id,
|
||||
'room_name': '',
|
||||
'dids': []})
|
||||
home_infos[device_source][home_id]['room_info'][
|
||||
room_id]['dids'].extend(info['dids'])
|
||||
|
||||
return {
|
||||
'uid': uid,
|
||||
'home_list': home_infos.get('homelist', {}),
|
||||
'share_home_list': home_infos.get('share_home_list', [])
|
||||
}
|
||||
|
||||
async def get_uid_async(self) -> str:
|
||||
return (await self.get_homeinfos_async()).get('uid', None)
|
||||
|
||||
async def __get_device_list_page_async(
|
||||
self, dids: list[str], start_did: Optional[str] = None
|
||||
) -> dict[str, dict]:
|
||||
req_data: dict = {
|
||||
'limit': 200,
|
||||
'get_split_device': True,
|
||||
'get_third_device': True,
|
||||
'dids': dids
|
||||
}
|
||||
if start_did:
|
||||
req_data['start_did'] = start_did
|
||||
device_infos: dict = {}
|
||||
res_obj = await self.__mihome_api_post_async(
|
||||
url_path='/app/v2/home/device_list_page',
|
||||
data=req_data
|
||||
)
|
||||
if 'result' not in res_obj:
|
||||
raise MIoTHttpError('invalid response result')
|
||||
res_obj = res_obj['result']
|
||||
|
||||
for device in res_obj.get('list', []) or []:
|
||||
did = device.get('did', None)
|
||||
name = device.get('name', None)
|
||||
urn = device.get('spec_type', None)
|
||||
model = device.get('model', None)
|
||||
if did is None or name is None:
|
||||
_LOGGER.info(
|
||||
'invalid device, cloud, %s', device)
|
||||
continue
|
||||
if urn is None or model is None:
|
||||
_LOGGER.info(
|
||||
'missing the urn|model field, cloud, %s', device)
|
||||
continue
|
||||
if did.startswith('miwifi.'):
|
||||
# The miwifi.* routers defined SPEC functions, but none of them
|
||||
# were implemented.
|
||||
_LOGGER.info('ignore miwifi.* device, cloud, %s', did)
|
||||
continue
|
||||
if model in UNSUPPORTED_MODELS:
|
||||
_LOGGER.info('ignore unsupported model %s, cloud, %s',
|
||||
model, did)
|
||||
continue
|
||||
device_infos[did] = {
|
||||
'did': did,
|
||||
'uid': device.get('uid', None),
|
||||
'name': name,
|
||||
'urn': urn,
|
||||
'model': model,
|
||||
'connect_type': device.get('pid', -1),
|
||||
'token': device.get('token', None),
|
||||
'online': device.get('isOnline', False),
|
||||
'icon': device.get('icon', None),
|
||||
'parent_id': device.get('parent_id', None),
|
||||
'manufacturer': model.split('.')[0],
|
||||
# 2: xiao-ai, 1: general speaker
|
||||
'voice_ctrl': device.get('voice_ctrl', 0),
|
||||
'rssi': device.get('rssi', None),
|
||||
'owner': device.get('owner', None),
|
||||
'pid': device.get('pid', None),
|
||||
'local_ip': device.get('local_ip', None),
|
||||
'ssid': device.get('ssid', None),
|
||||
'bssid': device.get('bssid', None),
|
||||
'order_time': device.get('orderTime', 0),
|
||||
'fw_version': device.get('extra', {}).get(
|
||||
'fw_version', None)
|
||||
}
|
||||
|
||||
next_start_did = res_obj.get('next_start_did', None)
|
||||
if res_obj.get('has_more', False) and next_start_did:
|
||||
device_infos.update(await self.__get_device_list_page_async(
|
||||
dids=dids, start_did=next_start_did))
|
||||
|
||||
return device_infos
|
||||
|
||||
async def get_devices_with_dids_async(
|
||||
self, dids: list[str]
|
||||
) -> Optional[dict[str, dict]]:
|
||||
results: list[dict[str, dict]] = await asyncio.gather(
|
||||
*[self.__get_device_list_page_async(dids=dids[index:index+150])
|
||||
for index in range(0, len(dids), 150)])
|
||||
devices = {}
|
||||
for result in results:
|
||||
if result is None:
|
||||
return None
|
||||
devices.update(result)
|
||||
return devices
|
||||
|
||||
async def get_devices_async(
|
||||
self, home_ids: Optional[list[str]] = None
|
||||
) -> dict[str, dict]:
|
||||
homeinfos = await self.get_homeinfos_async()
|
||||
homes: dict[str, dict[str, Any]] = {}
|
||||
devices: dict[str, dict] = {}
|
||||
for device_type in ['home_list', 'share_home_list']:
|
||||
homes.setdefault(device_type, {})
|
||||
for home_id, home_info in (homeinfos.get(
|
||||
device_type, None) or {}).items():
|
||||
if isinstance(home_ids, list) and home_id not in home_ids:
|
||||
continue
|
||||
home_name: str = home_info['home_name']
|
||||
group_id: str = home_info['group_id']
|
||||
homes[device_type].setdefault(
|
||||
home_id, {
|
||||
'home_name': home_name,
|
||||
'uid': home_info['uid'],
|
||||
'group_id': group_id,
|
||||
'room_info': {}
|
||||
})
|
||||
devices.update({did: {
|
||||
'home_id': home_id,
|
||||
'home_name': home_name,
|
||||
'room_id': home_id,
|
||||
'room_name': home_name,
|
||||
'group_id': group_id
|
||||
} for did in home_info.get('dids', [])})
|
||||
for room_id, room_info in home_info.get('room_info').items():
|
||||
room_name: str = room_info.get('room_name', '')
|
||||
homes[device_type][home_id]['room_info'][
|
||||
room_id] = room_name
|
||||
devices.update({
|
||||
did: {
|
||||
'home_id': home_id,
|
||||
'home_name': home_name,
|
||||
'room_id': room_id,
|
||||
'room_name': room_name,
|
||||
'group_id': group_id
|
||||
} for did in room_info.get('dids', [])})
|
||||
separated_shared_devices: dict = (
|
||||
await self.get_separated_shared_devices_async())
|
||||
if separated_shared_devices:
|
||||
homes.setdefault('separated_shared_list', {})
|
||||
for did, owner in separated_shared_devices.items():
|
||||
owner_id = str(owner['userid'])
|
||||
homes['separated_shared_list'].setdefault(owner_id,{
|
||||
'home_name': owner['nickname'],
|
||||
'uid': owner_id,
|
||||
'group_id': 'NotSupport',
|
||||
'room_info': {'shared_device': 'shared_device'}
|
||||
})
|
||||
devices.update({did: {
|
||||
'home_id': owner_id,
|
||||
'home_name': owner['nickname'],
|
||||
'room_id': 'shared_device',
|
||||
'room_name': 'shared_device',
|
||||
'group_id': 'NotSupport'
|
||||
}})
|
||||
dids = sorted(list(devices.keys()))
|
||||
results = await self.get_devices_with_dids_async(dids=dids)
|
||||
if results is None:
|
||||
raise MIoTHttpError('get devices failed')
|
||||
for did in dids:
|
||||
if did not in results:
|
||||
devices.pop(did, None)
|
||||
_LOGGER.info('get device info failed, %s', did)
|
||||
continue
|
||||
devices[did].update(results[did])
|
||||
# Whether sub devices
|
||||
match_str = re.search(r'\.s\d+$', did)
|
||||
if not match_str:
|
||||
continue
|
||||
device = devices.pop(did, None)
|
||||
parent_did = did.replace(match_str.group(), '')
|
||||
if parent_did in devices:
|
||||
devices[parent_did].setdefault('sub_devices', {})
|
||||
devices[parent_did]['sub_devices'][match_str.group()[
|
||||
1:]] = device
|
||||
else:
|
||||
_LOGGER.error(
|
||||
'unknown sub devices, %s, %s', did, parent_did)
|
||||
return {
|
||||
'uid': homeinfos['uid'],
|
||||
'homes': homes,
|
||||
'devices': devices
|
||||
}
|
||||
|
||||
async def get_props_async(self, params: list) -> list:
|
||||
"""
|
||||
params = [{"did": "xxxx", "siid": 2, "piid": 1},
|
||||
{"did": "xxxxxx", "siid": 2, "piid": 2}]
|
||||
"""
|
||||
res_obj = await self.__mihome_api_post_async(
|
||||
url_path='/app/v2/miotspec/prop/get',
|
||||
data={
|
||||
'datasource': 1,
|
||||
'params': params
|
||||
},
|
||||
)
|
||||
if 'result' not in res_obj:
|
||||
raise MIoTHttpError('invalid response result')
|
||||
return res_obj['result']
|
||||
|
||||
async def __get_prop_async(self, did: str, siid: int, piid: int) -> Any:
|
||||
results = await self.get_props_async(
|
||||
params=[{'did': did, 'siid': siid, 'piid': piid}])
|
||||
if not results:
|
||||
return None
|
||||
result = results[0]
|
||||
if 'value' not in result:
|
||||
return None
|
||||
return result['value']
|
||||
|
||||
async def __get_prop_handler(self) -> bool:
|
||||
props_req: set[str] = set()
|
||||
props_buffer: list[dict] = []
|
||||
|
||||
for key, item in self._get_prop_list.items():
|
||||
if item.get('tag', False):
|
||||
continue
|
||||
# NOTICE: max req prop
|
||||
if len(props_req) >= self.GET_PROP_MAX_REQ_COUNT:
|
||||
break
|
||||
item['tag'] = True
|
||||
props_buffer.append(item['param'])
|
||||
props_req.add(key)
|
||||
|
||||
if not props_buffer:
|
||||
_LOGGER.error('get prop error, empty request list')
|
||||
return False
|
||||
results = await self.get_props_async(props_buffer)
|
||||
|
||||
for result in results:
|
||||
if not all(
|
||||
key in result for key in ['did', 'siid', 'piid', 'value']):
|
||||
continue
|
||||
key = f'{result["did"]}.{result["siid"]}.{result["piid"]}'
|
||||
prop_obj = self._get_prop_list.pop(key, None)
|
||||
if prop_obj is None:
|
||||
_LOGGER.info('get prop error, key not exists, %s', result)
|
||||
continue
|
||||
prop_obj['fut'].set_result(result['value'])
|
||||
props_req.remove(key)
|
||||
|
||||
for key in props_req:
|
||||
prop_obj = self._get_prop_list.pop(key, None)
|
||||
if prop_obj is None:
|
||||
continue
|
||||
prop_obj['fut'].set_result(None)
|
||||
if props_req:
|
||||
_LOGGER.info(
|
||||
'get prop from cloud failed, %s', props_req)
|
||||
|
||||
if self._get_prop_list:
|
||||
self._get_prop_timer = self._main_loop.call_later(
|
||||
self.GET_PROP_AGGREGATE_INTERVAL,
|
||||
lambda: self._main_loop.create_task(
|
||||
self.__get_prop_handler()))
|
||||
else:
|
||||
self._get_prop_timer = None
|
||||
return True
|
||||
|
||||
async def get_prop_async(
|
||||
self, did: str, siid: int, piid: int, immediately: bool = False
|
||||
) -> Any:
|
||||
if immediately:
|
||||
return await self.__get_prop_async(did, siid, piid)
|
||||
key: str = f'{did}.{siid}.{piid}'
|
||||
prop_obj = self._get_prop_list.get(key, None)
|
||||
if prop_obj:
|
||||
return await prop_obj['fut']
|
||||
fut = self._main_loop.create_future()
|
||||
self._get_prop_list[key] = {
|
||||
'param': {'did': did, 'siid': siid, 'piid': piid},
|
||||
'fut': fut
|
||||
}
|
||||
if self._get_prop_timer is None:
|
||||
self._get_prop_timer = self._main_loop.call_later(
|
||||
self.GET_PROP_AGGREGATE_INTERVAL,
|
||||
lambda: self._main_loop.create_task(
|
||||
self.__get_prop_handler()))
|
||||
|
||||
return await fut
|
||||
|
||||
async def set_prop_async(self, params: list) -> list:
|
||||
"""
|
||||
params = [{"did": "xxxx", "siid": 2, "piid": 1, "value": False}]
|
||||
"""
|
||||
res_obj = await self.__mihome_api_post_async(
|
||||
url_path='/app/v2/miotspec/prop/set',
|
||||
data={
|
||||
'params': params
|
||||
},
|
||||
timeout=15
|
||||
)
|
||||
if 'result' not in res_obj:
|
||||
raise MIoTHttpError('invalid response result')
|
||||
|
||||
return res_obj['result']
|
||||
|
||||
async def action_async(
|
||||
self, did: str, siid: int, aiid: int, in_list: list[dict]
|
||||
) -> dict:
|
||||
"""
|
||||
params = {"did": "xxxx", "siid": 2, "aiid": 1, "in": []}
|
||||
"""
|
||||
# NOTICE: Non-standard action param
|
||||
res_obj = await self.__mihome_api_post_async(
|
||||
url_path='/app/v2/miotspec/action',
|
||||
data={
|
||||
'params': {
|
||||
'did': did,
|
||||
'siid': siid,
|
||||
'aiid': aiid,
|
||||
'in': [item['value'] for item in in_list]}
|
||||
},
|
||||
timeout=15
|
||||
)
|
||||
if 'result' not in res_obj:
|
||||
raise MIoTHttpError('invalid response result')
|
||||
|
||||
return res_obj['result']
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
MIoT error code and exception.
|
||||
"""
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
|
||||
class MIoTErrorCode(Enum):
|
||||
"""MIoT error code."""
|
||||
# Base error code
|
||||
CODE_UNKNOWN = -10000
|
||||
CODE_UNAVAILABLE = -10001
|
||||
CODE_INVALID_PARAMS = -10002
|
||||
CODE_RESOURCE_ERROR = -10003
|
||||
CODE_INTERNAL_ERROR = -10004
|
||||
CODE_UNAUTHORIZED_ACCESS = -10005
|
||||
CODE_TIMEOUT = -10006
|
||||
# OAuth error code
|
||||
CODE_OAUTH_UNAUTHORIZED = -10020
|
||||
# Http error code
|
||||
CODE_HTTP_INVALID_ACCESS_TOKEN = -10030
|
||||
# MIoT mips error code
|
||||
CODE_MIPS_INVALID_RESULT = -10040
|
||||
# MIoT cert error code
|
||||
CODE_CERT_INVALID_CERT = -10050
|
||||
# MIoT spec error code, -10060
|
||||
# MIoT storage error code, -10070
|
||||
# MIoT ev error code, -10080
|
||||
# Mips service error code, -10090
|
||||
# Config flow error code, -10100
|
||||
CODE_CONFIG_INVALID_INPUT = -10100
|
||||
CODE_CONFIG_INVALID_STATE = -10101
|
||||
# Options flow error code , -10110
|
||||
# MIoT lan error code, -10120
|
||||
CODE_LAN_UNAVAILABLE = -10120
|
||||
|
||||
|
||||
class MIoTError(Exception):
|
||||
"""MIoT error."""
|
||||
code: MIoTErrorCode
|
||||
message: Any
|
||||
|
||||
def __init__(
|
||||
self, message: Any, code: MIoTErrorCode = MIoTErrorCode.CODE_UNKNOWN
|
||||
) -> None:
|
||||
self.message = message
|
||||
self.code = code
|
||||
super().__init__(self.message)
|
||||
|
||||
def to_str(self) -> str:
|
||||
return f'{{"code":{self.code.value},"message":"{self.message}"}}'
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {"code": self.code.value, "message": self.message}
|
||||
|
||||
|
||||
class MIoTOauthError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTHttpError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTMipsError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTDeviceError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTSpecError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTStorageError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTCertError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTClientError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTEvError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MipsServiceError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTConfigError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTOptionsError(MIoTError):
|
||||
...
|
||||
|
||||
|
||||
class MIoTLanError(MIoTError):
|
||||
...
|
||||
@@ -0,0 +1,110 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
MIoT internationalization translation.
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional, Union
|
||||
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .common import load_json_file
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MIoTI18n:
|
||||
"""MIoT Internationalization Translation.
|
||||
Translate by Copilot, which does not guarantee the accuracy of the
|
||||
translation. If there is a problem with the translation, please submit
|
||||
the ISSUE feedback. After the review, we will modify it as soon as possible.
|
||||
"""
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
_lang: str
|
||||
_data: dict
|
||||
|
||||
def __init__(
|
||||
self, lang: str, loop: Optional[asyncio.AbstractEventLoop]
|
||||
) -> None:
|
||||
self._main_loop = loop or asyncio.get_event_loop()
|
||||
self._lang = lang
|
||||
self._data = {}
|
||||
|
||||
async def init_async(self) -> None:
|
||||
if self._data:
|
||||
return
|
||||
data = None
|
||||
self._data = {}
|
||||
try:
|
||||
data = await self._main_loop.run_in_executor(
|
||||
None, load_json_file,
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
f'i18n/{self._lang}.json'))
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
_LOGGER.error('load file error, %s', err)
|
||||
return
|
||||
# Check if the file is a valid JSON file
|
||||
if not isinstance(data, dict):
|
||||
_LOGGER.error('valid file, %s', data)
|
||||
return
|
||||
self._data = data
|
||||
|
||||
async def deinit_async(self) -> None:
|
||||
self._data = {}
|
||||
|
||||
def translate(
|
||||
self, key: str, replace: Optional[dict[str, str]] = None
|
||||
) -> Union[str, dict, None]:
|
||||
result = self._data
|
||||
for item in key.split('.'):
|
||||
if item not in result:
|
||||
return None
|
||||
result = result[item]
|
||||
if isinstance(result, str) and replace:
|
||||
for k, v in replace.items():
|
||||
result = result.replace('{'+k+'}', str(v))
|
||||
return result or None
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,285 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
MIoT central hub gateway service discovery.
|
||||
"""
|
||||
import asyncio
|
||||
import base64
|
||||
import binascii
|
||||
import copy
|
||||
from enum import Enum
|
||||
from typing import Callable, Coroutine, Optional
|
||||
import logging
|
||||
|
||||
from zeroconf import (
|
||||
DNSQuestionType,
|
||||
IPVersion,
|
||||
ServiceStateChange,
|
||||
Zeroconf)
|
||||
from zeroconf.asyncio import (
|
||||
AsyncServiceInfo,
|
||||
AsyncZeroconf,
|
||||
AsyncServiceBrowser)
|
||||
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .miot_error import MipsServiceError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
MIPS_MDNS_TYPE = '_miot-central._tcp.local.'
|
||||
MIPS_MDNS_REQUEST_TIMEOUT_MS = 5000
|
||||
MIPS_MDNS_UPDATE_INTERVAL_S = 600
|
||||
|
||||
|
||||
class MipsServiceState(Enum):
|
||||
ADDED = 1
|
||||
REMOVED = 2
|
||||
UPDATED = 3
|
||||
|
||||
|
||||
class MipsServiceData:
|
||||
"""Mips service data."""
|
||||
profile: str
|
||||
profile_bin: bytes
|
||||
|
||||
name: str
|
||||
addresses: list[str]
|
||||
port: int
|
||||
type: str
|
||||
server: str
|
||||
|
||||
did: str
|
||||
group_id: str
|
||||
role: int
|
||||
suite_mqtt: bool
|
||||
|
||||
def __init__(self, service_info: AsyncServiceInfo) -> None:
|
||||
if service_info is None:
|
||||
raise MipsServiceError('invalid params')
|
||||
properties: dict = service_info.decoded_properties
|
||||
if not properties:
|
||||
raise MipsServiceError('invalid service properties')
|
||||
self.profile = properties.get('profile', None)
|
||||
if self.profile is None:
|
||||
raise MipsServiceError('invalid service profile')
|
||||
self.profile_bin = base64.b64decode(self.profile)
|
||||
self.name = service_info.name
|
||||
self.addresses = service_info.parsed_addresses(
|
||||
version=IPVersion.V4Only)
|
||||
if not self.addresses:
|
||||
raise MipsServiceError('invalid addresses')
|
||||
if not service_info.port:
|
||||
raise MipsServiceError('invalid port')
|
||||
self.port = service_info.port
|
||||
self.type = service_info.type
|
||||
self.server = service_info.server or ''
|
||||
# Parse profile
|
||||
self.did = str(int.from_bytes(self.profile_bin[1:9], byteorder='big'))
|
||||
self.group_id = binascii.hexlify(
|
||||
self.profile_bin[9:17][::-1]).decode('utf-8')
|
||||
self.role = int(self.profile_bin[20] >> 4)
|
||||
self.suite_mqtt = ((self.profile_bin[22] >> 1) & 0x01) == 0x01
|
||||
|
||||
def valid_service(self) -> bool:
|
||||
if self.role != 1:
|
||||
return False
|
||||
return self.suite_mqtt
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
'name': self.name,
|
||||
'addresses': self.addresses,
|
||||
'port': self.port,
|
||||
'type': self.type,
|
||||
'server': self.server,
|
||||
'did': self.did,
|
||||
'group_id': self.group_id,
|
||||
'role': self.role,
|
||||
'suite_mqtt': self.suite_mqtt
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.to_dict())
|
||||
|
||||
|
||||
class MipsService:
|
||||
"""MIPS service discovery."""
|
||||
_aiozc: AsyncZeroconf
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
_aio_browser: AsyncServiceBrowser
|
||||
_services: dict[str, dict]
|
||||
# key = (key, group_id)
|
||||
_sub_list: dict[tuple[str, str], Callable[[
|
||||
str, MipsServiceState, dict], Coroutine]]
|
||||
|
||||
def __init__(
|
||||
self, aiozc: AsyncZeroconf,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> None:
|
||||
self._aiozc = aiozc
|
||||
self._main_loop = loop or asyncio.get_running_loop()
|
||||
|
||||
self._services = {}
|
||||
self._sub_list = {}
|
||||
|
||||
async def init_async(self) -> None:
|
||||
await self._aiozc.zeroconf.async_wait_for_start()
|
||||
|
||||
self._aio_browser = AsyncServiceBrowser(
|
||||
zeroconf=self._aiozc.zeroconf,
|
||||
type_=MIPS_MDNS_TYPE,
|
||||
handlers=[self.__on_service_state_change],
|
||||
question_type=DNSQuestionType.QM)
|
||||
|
||||
async def deinit_async(self) -> None:
|
||||
await self._aio_browser.async_cancel()
|
||||
self._services = {}
|
||||
self._sub_list = {}
|
||||
|
||||
def get_services(self, group_id: Optional[str] = None) -> dict[str, dict]:
|
||||
"""get mips services.
|
||||
|
||||
Args:
|
||||
group_id (str, optional): _description_. Defaults to None.
|
||||
|
||||
Returns: {
|
||||
[group_id:str]: {
|
||||
"name": str,
|
||||
"addresses": list[str],
|
||||
"port": number,
|
||||
"type": str,
|
||||
"server": str,
|
||||
"version": int,
|
||||
"did": str,
|
||||
"group_id": str,
|
||||
"role": int,
|
||||
"suite_mqtt": bool
|
||||
}
|
||||
}
|
||||
"""
|
||||
if group_id:
|
||||
if group_id not in self._services:
|
||||
return {}
|
||||
return {group_id: copy.deepcopy(self._services[group_id])}
|
||||
return copy.deepcopy(self._services)
|
||||
|
||||
def sub_service_change(
|
||||
self, key: str, group_id: str,
|
||||
handler: Callable[[str, MipsServiceState, dict], Coroutine]
|
||||
) -> None:
|
||||
if key is None or group_id is None or handler is None:
|
||||
raise MipsServiceError('invalid params')
|
||||
self._sub_list[(key, group_id)] = handler
|
||||
|
||||
def unsub_service_change(self, key: str) -> None:
|
||||
if key is None:
|
||||
return
|
||||
for keys in list(self._sub_list.keys()):
|
||||
if key == keys[0]:
|
||||
self._sub_list.pop(keys, None)
|
||||
|
||||
def __on_service_state_change(
|
||||
self, zeroconf: Zeroconf, service_type: str, name: str,
|
||||
state_change: ServiceStateChange
|
||||
) -> None:
|
||||
_LOGGER.debug(
|
||||
'mdns discovery changed, %s, %s, %s',
|
||||
state_change, name, service_type)
|
||||
|
||||
if state_change is ServiceStateChange.Removed:
|
||||
for item in list(self._services.values()):
|
||||
if item['name'] != name:
|
||||
continue
|
||||
# Ignore mdns REMOVED package. Let the connection close by itself.
|
||||
# service_data = self._services.pop(item['group_id'], {})
|
||||
# self.__call_service_change(
|
||||
# state=MipsServiceState.REMOVED, data=service_data)
|
||||
return
|
||||
self._main_loop.create_task(
|
||||
self.__request_service_info_async(zeroconf, service_type, name))
|
||||
|
||||
async def __request_service_info_async(
|
||||
self, zeroconf: Zeroconf, service_type: str, name: str
|
||||
) -> None:
|
||||
info = AsyncServiceInfo(service_type, name)
|
||||
await info.async_request(
|
||||
zeroconf, MIPS_MDNS_REQUEST_TIMEOUT_MS,
|
||||
question_type=DNSQuestionType.QU)
|
||||
|
||||
try:
|
||||
service_data = MipsServiceData(info)
|
||||
if not service_data.valid_service():
|
||||
raise MipsServiceError(
|
||||
'no primary role, no support mqtt connection')
|
||||
if service_data.group_id in self._services:
|
||||
# Update mips service
|
||||
buffer_data = self._services[service_data.group_id]
|
||||
if (
|
||||
service_data.did != buffer_data['did']
|
||||
or service_data.addresses != buffer_data['addresses']
|
||||
or service_data.port != buffer_data['port']
|
||||
):
|
||||
self._services[service_data.group_id].update(
|
||||
service_data.to_dict())
|
||||
self.__call_service_change(
|
||||
state=MipsServiceState.UPDATED,
|
||||
data=service_data.to_dict())
|
||||
else:
|
||||
# Add mips service
|
||||
self._services[service_data.group_id] = service_data.to_dict()
|
||||
self.__call_service_change(
|
||||
state=MipsServiceState.ADDED,
|
||||
data=self._services[service_data.group_id])
|
||||
except MipsServiceError as error:
|
||||
_LOGGER.error('invalid mips service, %s, %s', error, info)
|
||||
|
||||
def __call_service_change(
|
||||
self, state: MipsServiceState, data: dict
|
||||
) -> None:
|
||||
_LOGGER.info('call service change, %s, %s', state, data)
|
||||
for keys in list(self._sub_list.keys()):
|
||||
if keys[1] in [data.get('group_id', None), '*']:
|
||||
self._main_loop.create_task(
|
||||
self._sub_list[keys](data['group_id'], state, data))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,382 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
MIoT network utilities.
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import platform
|
||||
import socket
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
import subprocess
|
||||
from typing import Callable, Coroutine, Optional
|
||||
import aiohttp
|
||||
import psutil
|
||||
import ipaddress
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InterfaceStatus(Enum):
|
||||
"""Interface status."""
|
||||
ADD = 0
|
||||
UPDATE = auto()
|
||||
REMOVE = auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
class NetworkInfo:
|
||||
"""Network information."""
|
||||
name: str
|
||||
ip: str
|
||||
netmask: str
|
||||
net_seg: str
|
||||
|
||||
|
||||
class MIoTNetwork:
|
||||
"""MIoT network utilities."""
|
||||
_IP_ADDRESS_LIST: list[str] = [
|
||||
'1.2.4.8', # CNNIC sDNS
|
||||
'8.8.8.8', # Google Public DNS
|
||||
'9.9.9.9' # Quad9
|
||||
]
|
||||
_URL_ADDRESS_LIST: list[str] = [
|
||||
'https://www.bing.com',
|
||||
'https://www.google.com',
|
||||
'https://www.baidu.com'
|
||||
]
|
||||
_REFRESH_INTERVAL = 30
|
||||
_DETECT_TIMEOUT = 6
|
||||
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
|
||||
_ip_addr_map: dict[str, float]
|
||||
_http_addr_map: dict[str, float]
|
||||
_http_session: aiohttp.ClientSession
|
||||
|
||||
_refresh_interval: int
|
||||
_refresh_task: Optional[asyncio.Task]
|
||||
_refresh_timer: Optional[asyncio.TimerHandle]
|
||||
|
||||
_network_status: bool
|
||||
_network_info: dict[str, NetworkInfo]
|
||||
|
||||
_sub_list_network_status: dict[str, Callable[[bool], Coroutine]]
|
||||
_sub_list_network_info: dict[str, Callable[[
|
||||
InterfaceStatus, NetworkInfo], Coroutine]]
|
||||
_done_event: asyncio.Event
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ip_addr_list: Optional[list[str]] = None,
|
||||
url_addr_list: Optional[list[str]] = None,
|
||||
refresh_interval: Optional[int] = None,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> None:
|
||||
self._main_loop = loop or asyncio.get_running_loop()
|
||||
self._ip_addr_map = {
|
||||
ip: self._DETECT_TIMEOUT for ip in
|
||||
ip_addr_list or self._IP_ADDRESS_LIST}
|
||||
self._http_addr_map = {
|
||||
url: self._DETECT_TIMEOUT for url in
|
||||
url_addr_list or self._URL_ADDRESS_LIST}
|
||||
self._http_session = aiohttp.ClientSession()
|
||||
self._refresh_interval = refresh_interval or self._REFRESH_INTERVAL
|
||||
|
||||
self._refresh_task = None
|
||||
self._refresh_timer = None
|
||||
|
||||
self._network_status = False
|
||||
self._network_info = {}
|
||||
|
||||
self._sub_list_network_status = {}
|
||||
self._sub_list_network_info = {}
|
||||
|
||||
self._done_event = asyncio.Event()
|
||||
|
||||
async def init_async(self) -> bool:
|
||||
self.__refresh_timer_handler()
|
||||
# MUST get network info before starting
|
||||
return await self._done_event.wait()
|
||||
|
||||
async def deinit_async(self) -> None:
|
||||
if self._refresh_task:
|
||||
self._refresh_task.cancel()
|
||||
self._refresh_task = None
|
||||
if self._refresh_timer:
|
||||
self._refresh_timer.cancel()
|
||||
self._refresh_timer = None
|
||||
await self._http_session.close()
|
||||
|
||||
self._network_status = False
|
||||
self._network_info.clear()
|
||||
self._sub_list_network_status.clear()
|
||||
self._sub_list_network_info.clear()
|
||||
self._done_event.clear()
|
||||
|
||||
@property
|
||||
def network_status(self) -> bool:
|
||||
return self._network_status
|
||||
|
||||
@property
|
||||
def network_info(self) -> dict[str, NetworkInfo]:
|
||||
return self._network_info
|
||||
|
||||
async def update_addr_list_async(
|
||||
self,
|
||||
ip_addr_list: Optional[list[str]] = None,
|
||||
url_addr_list: Optional[list[str]] = None,
|
||||
) -> None:
|
||||
new_ip_map: dict = {}
|
||||
for ip in ip_addr_list or self._IP_ADDRESS_LIST:
|
||||
if ip in self._ip_addr_map:
|
||||
new_ip_map[ip] = self._ip_addr_map[ip]
|
||||
else:
|
||||
new_ip_map[ip] = self._DETECT_TIMEOUT
|
||||
self._ip_addr_map = new_ip_map
|
||||
new_url_map: dict = {}
|
||||
for url in url_addr_list or self._URL_ADDRESS_LIST:
|
||||
if url in self._http_addr_map:
|
||||
new_url_map[url] = self._http_addr_map[url]
|
||||
else:
|
||||
new_url_map[url] = self._DETECT_TIMEOUT
|
||||
self._http_addr_map = new_url_map
|
||||
|
||||
def sub_network_status(
|
||||
self, key: str, handler: Callable[[bool], Coroutine]
|
||||
) -> None:
|
||||
self._sub_list_network_status[key] = handler
|
||||
|
||||
def unsub_network_status(self, key: str) -> None:
|
||||
self._sub_list_network_status.pop(key, None)
|
||||
|
||||
def sub_network_info(
|
||||
self, key: str,
|
||||
handler: Callable[[InterfaceStatus, NetworkInfo], Coroutine]
|
||||
) -> None:
|
||||
self._sub_list_network_info[key] = handler
|
||||
|
||||
def unsub_network_info(self, key: str) -> None:
|
||||
self._sub_list_network_info.pop(key, None)
|
||||
|
||||
async def refresh_async(self) -> None:
|
||||
self.__refresh_timer_handler()
|
||||
|
||||
async def get_network_status_async(self) -> bool:
|
||||
try:
|
||||
ip_addr: str = ''
|
||||
ip_ts: float = self._DETECT_TIMEOUT
|
||||
for ip, ts in self._ip_addr_map.items():
|
||||
if ts < ip_ts:
|
||||
ip_addr = ip
|
||||
ip_ts = ts
|
||||
if (
|
||||
ip_ts < self._DETECT_TIMEOUT
|
||||
and await self.ping_multi_async(ip_list=[ip_addr])
|
||||
):
|
||||
return True
|
||||
url_addr: str = ''
|
||||
url_ts: float = self._DETECT_TIMEOUT
|
||||
for http, ts in self._http_addr_map.items():
|
||||
if ts < url_ts:
|
||||
url_addr = http
|
||||
url_ts = ts
|
||||
if (
|
||||
url_ts < self._DETECT_TIMEOUT
|
||||
and await self.http_multi_async(url_list=[url_addr])
|
||||
):
|
||||
return True
|
||||
# Detect all addresses
|
||||
results = await asyncio.gather(
|
||||
*[self.ping_multi_async(), self.http_multi_async()])
|
||||
return any(results)
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
_LOGGER.error('get network status error, %s', err)
|
||||
return False
|
||||
|
||||
async def get_network_info_async(self) -> dict[str, NetworkInfo]:
|
||||
return await self._main_loop.run_in_executor(
|
||||
None, self.__get_network_info)
|
||||
|
||||
async def ping_multi_async(
|
||||
self, ip_list: Optional[list[str]] = None
|
||||
) -> bool:
|
||||
addr_list = ip_list or list(self._ip_addr_map.keys())
|
||||
tasks = []
|
||||
for addr in addr_list:
|
||||
tasks.append(self.__ping_async(addr))
|
||||
results = await asyncio.gather(*tasks)
|
||||
for addr, ts in zip(addr_list, results):
|
||||
if addr in self._ip_addr_map:
|
||||
self._ip_addr_map[addr] = ts
|
||||
return any([ts < self._DETECT_TIMEOUT for ts in results])
|
||||
|
||||
async def http_multi_async(
|
||||
self, url_list: Optional[list[str]] = None
|
||||
) -> bool:
|
||||
addr_list = url_list or list(self._http_addr_map.keys())
|
||||
tasks = []
|
||||
for addr in addr_list:
|
||||
tasks.append(self.__http_async(url=addr))
|
||||
results = await asyncio.gather(*tasks)
|
||||
for addr, ts in zip(addr_list, results):
|
||||
if addr in self._http_addr_map:
|
||||
self._http_addr_map[addr] = ts
|
||||
return any([ts < self._DETECT_TIMEOUT for ts in results])
|
||||
|
||||
def __calc_network_address(self, ip: str, netmask: str) -> str:
|
||||
return str(ipaddress.IPv4Network(
|
||||
f'{ip}/{netmask}', strict=False).network_address)
|
||||
|
||||
async def __ping_async(self, address: Optional[str] = None) -> float:
|
||||
start_ts: float = self._main_loop.time()
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*(
|
||||
[
|
||||
'ping', '-n', '1', '-w',
|
||||
str(self._DETECT_TIMEOUT*1000), address]
|
||||
if platform.system().lower() == 'windows' else
|
||||
[
|
||||
'ping', '-c', '1', '-w',
|
||||
str(self._DETECT_TIMEOUT), address]),
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
await process.communicate()
|
||||
if process.returncode == 0:
|
||||
return self._main_loop.time() - start_ts
|
||||
return self._DETECT_TIMEOUT
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
_LOGGER.debug('ping error, %s',err)
|
||||
return self._DETECT_TIMEOUT
|
||||
|
||||
async def __http_async(self, url: str) -> float:
|
||||
start_ts: float = self._main_loop.time()
|
||||
try:
|
||||
async with self._http_session.get(
|
||||
url, timeout=self._DETECT_TIMEOUT):
|
||||
return self._main_loop.time() - start_ts
|
||||
except Exception: # pylint: disable=broad-exception-caught
|
||||
pass
|
||||
return self._DETECT_TIMEOUT
|
||||
|
||||
def __get_network_info(self) -> dict[str, NetworkInfo]:
|
||||
interfaces = psutil.net_if_addrs()
|
||||
results: dict[str, NetworkInfo] = {}
|
||||
for name, addresses in interfaces.items():
|
||||
# Skip hassio and docker* interface
|
||||
if name == 'hassio' or name.startswith('docker'):
|
||||
continue
|
||||
for address in addresses:
|
||||
if (
|
||||
address.family != socket.AF_INET
|
||||
or not address.address
|
||||
or not address.netmask
|
||||
):
|
||||
continue
|
||||
# skip lo interface
|
||||
if address.address == '127.0.0.1':
|
||||
continue
|
||||
results[name] = NetworkInfo(
|
||||
name=name,
|
||||
ip=address.address,
|
||||
netmask=address.netmask,
|
||||
net_seg=self.__calc_network_address(
|
||||
address.address, address.netmask))
|
||||
return results
|
||||
|
||||
def __call_network_info_change(
|
||||
self, status: InterfaceStatus, info: NetworkInfo
|
||||
) -> None:
|
||||
for handler in self._sub_list_network_info.values():
|
||||
self._main_loop.create_task(handler(status, info))
|
||||
|
||||
async def __update_status_and_info_async(self) -> None:
|
||||
try:
|
||||
status: bool = await self.get_network_status_async()
|
||||
infos = await self.get_network_info_async()
|
||||
|
||||
if self._network_status != status:
|
||||
for handler in self._sub_list_network_status.values():
|
||||
self._main_loop.create_task(handler(status))
|
||||
self._network_status = status
|
||||
|
||||
for name in list(self._network_info.keys()):
|
||||
info = infos.pop(name, None)
|
||||
if info:
|
||||
# Update
|
||||
if (
|
||||
info.ip != self._network_info[name].ip
|
||||
or info.netmask != self._network_info[name].netmask
|
||||
):
|
||||
self._network_info[name] = info
|
||||
self.__call_network_info_change(
|
||||
InterfaceStatus.UPDATE, info)
|
||||
else:
|
||||
# Remove
|
||||
self.__call_network_info_change(
|
||||
InterfaceStatus.REMOVE,
|
||||
self._network_info.pop(name))
|
||||
# Add
|
||||
for name, info in infos.items():
|
||||
self._network_info[name] = info
|
||||
self.__call_network_info_change(InterfaceStatus.ADD, info)
|
||||
|
||||
if not self._done_event.is_set():
|
||||
self._done_event.set()
|
||||
except asyncio.CancelledError:
|
||||
_LOGGER.error('update_status_and_info task was cancelled')
|
||||
|
||||
def __refresh_timer_handler(self) -> None:
|
||||
if self._refresh_timer:
|
||||
self._refresh_timer.cancel()
|
||||
self._refresh_timer = None
|
||||
if self._refresh_task is None or self._refresh_task.done():
|
||||
self._refresh_task = self._main_loop.create_task(
|
||||
self.__update_status_and_info_async())
|
||||
self._refresh_timer = self._main_loop.call_later(
|
||||
self._refresh_interval, self.__refresh_timer_handler)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,788 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
MIoT storage and certificate management.
|
||||
"""
|
||||
import os
|
||||
import asyncio
|
||||
import binascii
|
||||
import json
|
||||
import shutil
|
||||
import time
|
||||
import traceback
|
||||
import hashlib
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum, auto
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Union
|
||||
import logging
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.x509.oid import NameOID
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||
|
||||
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .const import (
|
||||
MANUFACTURER_EFFECTIVE_TIME,
|
||||
MIHOME_CA_CERT_STR,
|
||||
MIHOME_CA_CERT_SHA256)
|
||||
from .common import MIoTHttp
|
||||
from .miot_error import MIoTCertError, MIoTError, MIoTStorageError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MIoTStorageType(Enum):
|
||||
LOAD = auto()
|
||||
LOAD_FILE = auto()
|
||||
SAVE = auto()
|
||||
SAVE_FILE = auto()
|
||||
DEL = auto()
|
||||
DEL_FILE = auto()
|
||||
CLEAR = auto()
|
||||
|
||||
|
||||
class MIoTStorage:
|
||||
"""File management.
|
||||
|
||||
User data will be stored in the `.storage` directory of Home Assistant.
|
||||
"""
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
_file_future: dict[str, tuple[MIoTStorageType, asyncio.Future]]
|
||||
|
||||
_root_path: str
|
||||
|
||||
def __init__(
|
||||
self, root_path: str,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> None:
|
||||
"""Initialize with a root path."""
|
||||
self._main_loop = loop or asyncio.get_running_loop()
|
||||
self._file_future = {}
|
||||
|
||||
self._root_path = os.path.abspath(root_path)
|
||||
os.makedirs(self._root_path, exist_ok=True)
|
||||
|
||||
_LOGGER.debug('root path, %s', self._root_path)
|
||||
|
||||
def __get_full_path(self, domain: str, name: str, suffix: str) -> str:
|
||||
return os.path.join(
|
||||
self._root_path, domain, f'{name}.{suffix}')
|
||||
|
||||
def __add_file_future(
|
||||
self, key: str, op_type: MIoTStorageType, fut: asyncio.Future
|
||||
) -> None:
|
||||
def fut_done_callback(fut: asyncio.Future):
|
||||
del fut
|
||||
self._file_future.pop(key, None)
|
||||
|
||||
fut.add_done_callback(fut_done_callback)
|
||||
self._file_future[key] = op_type, fut
|
||||
|
||||
def __load(
|
||||
self, full_path: str, type_: type = bytes, with_hash_check: bool = True
|
||||
) -> Union[bytes, str, dict, list, None]:
|
||||
if not os.path.exists(full_path):
|
||||
_LOGGER.debug('load error, file does not exist, %s', full_path)
|
||||
return None
|
||||
if not os.access(full_path, os.R_OK):
|
||||
_LOGGER.error('load error, file not readable, %s', full_path)
|
||||
return None
|
||||
try:
|
||||
with open(full_path, 'rb') as r_file:
|
||||
r_data: bytes = r_file.read()
|
||||
if r_data is None:
|
||||
_LOGGER.error('load error, empty file, %s', full_path)
|
||||
return None
|
||||
data_bytes: bytes
|
||||
# Hash check
|
||||
if with_hash_check:
|
||||
if len(r_data) <= 32:
|
||||
return None
|
||||
data_bytes = r_data[:-32]
|
||||
hash_value = r_data[-32:]
|
||||
if hashlib.sha256(data_bytes).digest() != hash_value:
|
||||
_LOGGER.error(
|
||||
'load error, hash check failed, %s', full_path)
|
||||
return None
|
||||
else:
|
||||
data_bytes = r_data
|
||||
if type_ == bytes:
|
||||
return data_bytes
|
||||
if type_ == str:
|
||||
return str(data_bytes, 'utf-8')
|
||||
if type_ in [dict, list]:
|
||||
return json.loads(data_bytes)
|
||||
_LOGGER.error(
|
||||
'load error, unsupported data type, %s', type_.__name__)
|
||||
return None
|
||||
except (OSError, TypeError) as e:
|
||||
_LOGGER.error('load error, %s, %s', e, traceback.format_exc())
|
||||
return None
|
||||
|
||||
def load(
|
||||
self, domain: str, name: str, type_: type = bytes
|
||||
) -> Union[bytes, str, dict, list, None]:
|
||||
full_path = self.__get_full_path(
|
||||
domain=domain, name=name, suffix=type_.__name__)
|
||||
return self.__load(full_path=full_path, type_=type_)
|
||||
|
||||
async def load_async(
|
||||
self, domain: str, name: str, type_: type = bytes
|
||||
) -> Union[bytes, str, dict, list, None]:
|
||||
full_path = self.__get_full_path(
|
||||
domain=domain, name=name, suffix=type_.__name__)
|
||||
if full_path in self._file_future:
|
||||
# Waiting for the last task to be completed
|
||||
op_type, fut = self._file_future[full_path]
|
||||
if op_type == MIoTStorageType.LOAD:
|
||||
if not fut.done():
|
||||
return await fut
|
||||
else:
|
||||
await fut
|
||||
fut = self._main_loop.run_in_executor(
|
||||
None, self.__load, full_path, type_)
|
||||
if not fut.done():
|
||||
self.__add_file_future(full_path, MIoTStorageType.LOAD, fut)
|
||||
return await fut
|
||||
|
||||
def __save(
|
||||
self, full_path: str, data: Union[bytes, str, dict, list, None],
|
||||
cover: bool = True, with_hash: bool = True
|
||||
) -> bool:
|
||||
if data is None:
|
||||
_LOGGER.error('save error, save data is None')
|
||||
return False
|
||||
if os.path.exists(full_path):
|
||||
if not cover:
|
||||
_LOGGER.error('save error, file exists, cover is False')
|
||||
return False
|
||||
if not os.access(full_path, os.W_OK):
|
||||
_LOGGER.error('save error, file not writeable, %s', full_path)
|
||||
return False
|
||||
else:
|
||||
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
||||
try:
|
||||
w_bytes: bytes
|
||||
if isinstance(data, bytes):
|
||||
w_bytes = data
|
||||
elif isinstance(data, str):
|
||||
w_bytes = data.encode('utf-8')
|
||||
elif isinstance(data, (dict, list)):
|
||||
w_bytes = json.dumps(data).encode('utf-8')
|
||||
else:
|
||||
_LOGGER.error(
|
||||
'save error, unsupported data type, %s',
|
||||
type(data).__name__)
|
||||
return False
|
||||
with open(full_path, 'wb') as w_file:
|
||||
w_file.write(w_bytes)
|
||||
if with_hash:
|
||||
w_file.write(hashlib.sha256(w_bytes).digest())
|
||||
return True
|
||||
except (OSError, TypeError) as e:
|
||||
_LOGGER.error('save error, %s, %s', e, traceback.format_exc())
|
||||
return False
|
||||
|
||||
def save(
|
||||
self, domain: str, name: str, data: Union[bytes, str, dict, list, None]
|
||||
) -> bool:
|
||||
full_path = self.__get_full_path(
|
||||
domain=domain, name=name, suffix=type(data).__name__)
|
||||
return self.__save(full_path=full_path, data=data)
|
||||
|
||||
async def save_async(
|
||||
self, domain: str, name: str, data: Union[bytes, str, dict, list, None]
|
||||
) -> bool:
|
||||
full_path = self.__get_full_path(
|
||||
domain=domain, name=name, suffix=type(data).__name__)
|
||||
if full_path in self._file_future:
|
||||
# Waiting for the last task to be completed
|
||||
fut = self._file_future[full_path][1]
|
||||
await fut
|
||||
fut = self._main_loop.run_in_executor(
|
||||
None, self.__save, full_path, data)
|
||||
if not fut.done():
|
||||
self.__add_file_future(full_path, MIoTStorageType.SAVE, fut)
|
||||
return await fut
|
||||
|
||||
def __remove(self, full_path: str) -> bool:
|
||||
item = Path(full_path)
|
||||
if item.is_file() or item.is_symlink():
|
||||
item.unlink()
|
||||
return True
|
||||
|
||||
def remove(self, domain: str, name: str, type_: type) -> bool:
|
||||
full_path = self.__get_full_path(
|
||||
domain=domain, name=name, suffix=type_.__name__)
|
||||
return self.__remove(full_path=full_path)
|
||||
|
||||
async def remove_async(self, domain: str, name: str, type_: type) -> bool:
|
||||
full_path = self.__get_full_path(
|
||||
domain=domain, name=name, suffix=type_.__name__)
|
||||
if full_path in self._file_future:
|
||||
# Waiting for the last task to be completed
|
||||
op_type, fut = self._file_future[full_path]
|
||||
if op_type == MIoTStorageType.DEL:
|
||||
if not fut.done():
|
||||
return await fut
|
||||
else:
|
||||
await fut
|
||||
fut = self._main_loop.run_in_executor(None, self.__remove, full_path)
|
||||
if not fut.done():
|
||||
self.__add_file_future(full_path, MIoTStorageType.DEL, fut)
|
||||
return await fut
|
||||
|
||||
def __remove_domain(self, full_path: str) -> bool:
|
||||
path_obj = Path(full_path)
|
||||
if path_obj.exists():
|
||||
# Recursive deletion
|
||||
shutil.rmtree(path_obj)
|
||||
return True
|
||||
|
||||
def remove_domain(self, domain: str) -> bool:
|
||||
full_path = os.path.join(self._root_path, domain)
|
||||
return self.__remove_domain(full_path=full_path)
|
||||
|
||||
async def remove_domain_async(self, domain: str) -> bool:
|
||||
full_path = os.path.join(self._root_path, domain)
|
||||
if full_path in self._file_future:
|
||||
# Waiting for the last task to be completed
|
||||
op_type, fut = self._file_future[full_path]
|
||||
if op_type == MIoTStorageType.DEL:
|
||||
if not fut.done():
|
||||
return await fut
|
||||
else:
|
||||
await fut
|
||||
# Waiting domain tasks finish
|
||||
for path, value in self._file_future.items():
|
||||
if path.startswith(full_path):
|
||||
await value[1]
|
||||
fut = self._main_loop.run_in_executor(
|
||||
None, self.__remove_domain, full_path)
|
||||
if not fut.done():
|
||||
self.__add_file_future(full_path, MIoTStorageType.DEL, fut)
|
||||
return await fut
|
||||
|
||||
def get_names(self, domain: str, type_: type) -> list[str]:
|
||||
path: str = os.path.join(self._root_path, domain)
|
||||
type_str = f'.{type_.__name__}'
|
||||
names: list[str] = []
|
||||
for item in Path(path).glob(f'*{type_str}'):
|
||||
if not item.is_file() and not item.is_symlink():
|
||||
continue
|
||||
names.append(item.name.replace(type_str, ''))
|
||||
return names
|
||||
|
||||
def file_exists(self, domain: str, name_with_suffix: str) -> bool:
|
||||
return os.path.exists(
|
||||
os.path.join(self._root_path, domain, name_with_suffix))
|
||||
|
||||
def save_file(
|
||||
self, domain: str, name_with_suffix: str, data: bytes
|
||||
) -> bool:
|
||||
if not isinstance(data, bytes):
|
||||
_LOGGER.error('save file error, file must be bytes')
|
||||
return False
|
||||
full_path = os.path.join(self._root_path, domain, name_with_suffix)
|
||||
return self.__save(full_path=full_path, data=data, with_hash=False)
|
||||
|
||||
async def save_file_async(
|
||||
self, domain: str, name_with_suffix: str, data: bytes
|
||||
) -> bool:
|
||||
if not isinstance(data, bytes):
|
||||
_LOGGER.error('save file error, file must be bytes')
|
||||
return False
|
||||
full_path = os.path.join(self._root_path, domain, name_with_suffix)
|
||||
if full_path in self._file_future:
|
||||
# Waiting for the last task to be completed
|
||||
fut = self._file_future[full_path][1]
|
||||
await fut
|
||||
fut = self._main_loop.run_in_executor(
|
||||
None, self.__save, full_path, data, True, False)
|
||||
if not fut.done():
|
||||
self.__add_file_future(full_path, MIoTStorageType.SAVE_FILE, fut)
|
||||
return await fut
|
||||
|
||||
def load_file(self, domain: str, name_with_suffix: str) -> Optional[bytes]:
|
||||
full_path = os.path.join(self._root_path, domain, name_with_suffix)
|
||||
return self.__load(
|
||||
full_path=full_path, type_=bytes,
|
||||
with_hash_check=False) # type: ignore
|
||||
|
||||
async def load_file_async(
|
||||
self, domain: str, name_with_suffix: str
|
||||
) -> Optional[bytes]:
|
||||
full_path = os.path.join(self._root_path, domain, name_with_suffix)
|
||||
if full_path in self._file_future:
|
||||
# Waiting for the last task to be completed
|
||||
op_type, fut = self._file_future[full_path]
|
||||
if op_type == MIoTStorageType.LOAD_FILE:
|
||||
if not fut.done():
|
||||
return await fut
|
||||
else:
|
||||
await fut
|
||||
fut = self._main_loop.run_in_executor(
|
||||
None, self.__load, full_path, bytes, False)
|
||||
if not fut.done():
|
||||
self.__add_file_future(full_path, MIoTStorageType.LOAD_FILE, fut)
|
||||
return await fut # type: ignore
|
||||
|
||||
def remove_file(self, domain: str, name_with_suffix: str) -> bool:
|
||||
full_path = os.path.join(self._root_path, domain, name_with_suffix)
|
||||
return self.__remove(full_path=full_path)
|
||||
|
||||
async def remove_file_async(
|
||||
self, domain: str, name_with_suffix: str
|
||||
) -> bool:
|
||||
full_path = os.path.join(self._root_path, domain, name_with_suffix)
|
||||
if full_path in self._file_future:
|
||||
# Waiting for the last task to be completed
|
||||
op_type, fut = self._file_future[full_path]
|
||||
if op_type == MIoTStorageType.DEL_FILE:
|
||||
if not fut.done():
|
||||
return await fut
|
||||
else:
|
||||
await fut
|
||||
fut = self._main_loop.run_in_executor(None, self.__remove, full_path)
|
||||
if not fut.done():
|
||||
self.__add_file_future(full_path, MIoTStorageType.DEL_FILE, fut)
|
||||
return await fut
|
||||
|
||||
def clear(self) -> bool:
|
||||
root_path = Path(self._root_path)
|
||||
for item in root_path.iterdir():
|
||||
if item.is_file() or item.is_symlink():
|
||||
item.unlink()
|
||||
elif item.is_dir():
|
||||
shutil.rmtree(item)
|
||||
return True
|
||||
|
||||
async def clear_async(self) -> bool:
|
||||
if self._root_path in self._file_future:
|
||||
op_type, fut = self._file_future[self._root_path]
|
||||
if op_type == MIoTStorageType.CLEAR and not fut.done():
|
||||
return await fut
|
||||
# Waiting all future resolve
|
||||
for value in self._file_future.values():
|
||||
await value[1]
|
||||
|
||||
fut = self._main_loop.run_in_executor(None, self.clear)
|
||||
if not fut.done():
|
||||
self.__add_file_future(
|
||||
self._root_path, MIoTStorageType.CLEAR, fut)
|
||||
return await fut
|
||||
|
||||
def update_user_config(
|
||||
self, uid: str, cloud_server: str, config: Optional[dict[str, Any]],
|
||||
replace: bool = False
|
||||
) -> bool:
|
||||
if config is not None and len(config) == 0:
|
||||
# Do nothing
|
||||
return True
|
||||
|
||||
config_domain = 'miot_config'
|
||||
config_name = f'{uid}_{cloud_server}'
|
||||
if config is None:
|
||||
# Remove config file
|
||||
return self.remove(
|
||||
domain=config_domain, name=config_name, type_=dict)
|
||||
if replace:
|
||||
# Replace config file
|
||||
return self.save(
|
||||
domain=config_domain, name=config_name, data=config)
|
||||
local_config = (self.load(domain=config_domain,
|
||||
name=config_name, type_=dict)) or {}
|
||||
local_config.update(config) # type: ignore
|
||||
return self.save(
|
||||
domain=config_domain, name=config_name, data=local_config)
|
||||
|
||||
async def update_user_config_async(
|
||||
self, uid: str, cloud_server: str, config: Optional[dict[str, Any]],
|
||||
replace: bool = False
|
||||
) -> bool:
|
||||
"""Update user configuration.
|
||||
|
||||
Args:
|
||||
uid (str): user_id
|
||||
config (Optional[dict[str]]):
|
||||
remove config file if config is None
|
||||
replace (bool, optional):
|
||||
replace all config item. Defaults to False.
|
||||
|
||||
Returns:
|
||||
bool: result code
|
||||
"""
|
||||
if config is not None and len(config) == 0:
|
||||
# Do nothing
|
||||
return True
|
||||
|
||||
config_domain = 'miot_config'
|
||||
config_name = f'{uid}_{cloud_server}'
|
||||
if config is None:
|
||||
# Remove config file
|
||||
return await self.remove_async(
|
||||
domain=config_domain, name=config_name, type_=dict)
|
||||
if replace:
|
||||
# Replace config file
|
||||
return await self.save_async(
|
||||
domain=config_domain, name=config_name, data=config)
|
||||
local_config = (await self.load_async(
|
||||
domain=config_domain, name=config_name, type_=dict)) or {}
|
||||
local_config.update(config) # type: ignore
|
||||
return await self.save_async(
|
||||
domain=config_domain, name=config_name, data=local_config)
|
||||
|
||||
def load_user_config(
|
||||
self, uid: str, cloud_server: str, keys: Optional[list[str]] = None
|
||||
) -> dict[str, Any]:
|
||||
if isinstance(keys, list) and len(keys) == 0:
|
||||
# Do nothing
|
||||
return {}
|
||||
config_domain = 'miot_config'
|
||||
config_name = f'{uid}_{cloud_server}'
|
||||
local_config = (self.load(domain=config_domain,
|
||||
name=config_name, type_=dict))
|
||||
if not isinstance(local_config, dict):
|
||||
return {}
|
||||
if keys is None:
|
||||
return local_config
|
||||
return {
|
||||
key: local_config[key] for key in keys
|
||||
if key in local_config}
|
||||
|
||||
async def load_user_config_async(
|
||||
self, uid: str, cloud_server: str, keys: Optional[list[str]] = None
|
||||
) -> dict:
|
||||
"""Load user configuration.
|
||||
|
||||
Args:
|
||||
uid (str): user id
|
||||
keys (list[str]):
|
||||
query key list, return all config item if keys is None
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: query result
|
||||
"""
|
||||
if isinstance(keys, list) and len(keys) == 0:
|
||||
# Do nothing
|
||||
return {}
|
||||
config_domain = 'miot_config'
|
||||
config_name = f'{uid}_{cloud_server}'
|
||||
local_config = (await self.load_async(
|
||||
domain=config_domain, name=config_name, type_=dict))
|
||||
if not isinstance(local_config, dict):
|
||||
return {}
|
||||
if keys is None:
|
||||
return local_config
|
||||
return {
|
||||
key: local_config[key] for key in keys
|
||||
if key in local_config}
|
||||
|
||||
def gen_storage_path(
|
||||
self, domain: Optional[str] = None,
|
||||
name_with_suffix: Optional[str] = None
|
||||
) -> str:
|
||||
"""Generate file path."""
|
||||
result = self._root_path
|
||||
if domain:
|
||||
result = os.path.join(result, domain)
|
||||
if name_with_suffix:
|
||||
result = os.path.join(result, name_with_suffix)
|
||||
return result
|
||||
|
||||
|
||||
class MIoTCert:
|
||||
"""MIoT certificate file management."""
|
||||
CERT_DOMAIN: str = 'cert'
|
||||
CA_NAME: str = 'mihome_ca.cert'
|
||||
_loop: asyncio.AbstractEventLoop
|
||||
_storage: MIoTStorage
|
||||
_uid: str
|
||||
_cloud_server: str
|
||||
|
||||
_key_name: str
|
||||
_cert_name: str
|
||||
|
||||
def __init__(
|
||||
self, storage: MIoTStorage, uid: str, cloud_server: str,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> None:
|
||||
if not isinstance(storage, MIoTStorage) or not isinstance(uid, str):
|
||||
raise MIoTError('invalid params')
|
||||
self._loop = loop or asyncio.get_running_loop()
|
||||
self._storage = storage
|
||||
self._uid = uid
|
||||
self._cloud_server = cloud_server
|
||||
self._key_name = f'{uid}_{cloud_server}.key'
|
||||
self._cert_name = f'{uid}_{cloud_server}.cert'
|
||||
|
||||
@property
|
||||
def ca_file(self) -> str:
|
||||
"""CA certificate file path."""
|
||||
return self._storage.gen_storage_path(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self.CA_NAME)
|
||||
|
||||
@property
|
||||
def key_file(self) -> str:
|
||||
"""User private key file file path."""
|
||||
return self._storage.gen_storage_path(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self._key_name)
|
||||
|
||||
@property
|
||||
def cert_file(self) -> str:
|
||||
"""User certificate file path."""
|
||||
return self._storage.gen_storage_path(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self._cert_name)
|
||||
|
||||
async def verify_ca_cert_async(self) -> bool:
|
||||
"""Verify the integrity of the CA certificate file."""
|
||||
ca_data = await self._storage.load_file_async(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self.CA_NAME)
|
||||
if ca_data is None:
|
||||
if not await self._storage.save_file_async(
|
||||
domain=self.CERT_DOMAIN,
|
||||
name_with_suffix=self.CA_NAME,
|
||||
data=MIHOME_CA_CERT_STR.encode('utf-8')):
|
||||
raise MIoTStorageError('ca cert save failed')
|
||||
ca_data = await self._storage.load_file_async(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self.CA_NAME)
|
||||
if ca_data is None:
|
||||
raise MIoTStorageError('ca cert load failed')
|
||||
_LOGGER.debug('ca cert save success')
|
||||
# Compare the file sha256sum
|
||||
ca_cert_hash = hashlib.sha256(ca_data).digest()
|
||||
hash_str = binascii.hexlify(ca_cert_hash).decode('utf-8')
|
||||
if hash_str != MIHOME_CA_CERT_SHA256:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def user_cert_remaining_time_async(
|
||||
self, cert_data: Optional[bytes] = None, did: Optional[str] = None
|
||||
) -> int:
|
||||
"""Get the remaining time of user certificate validity.
|
||||
|
||||
Returns:
|
||||
If the certificate is not valid, return 0.
|
||||
"""
|
||||
if cert_data is None:
|
||||
cert_data = await self._storage.load_file_async(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self._cert_name)
|
||||
if cert_data is None:
|
||||
return 0
|
||||
# Check user cert
|
||||
try:
|
||||
user_cert: x509.Certificate = x509.load_pem_x509_certificate(
|
||||
cert_data, default_backend())
|
||||
cert_info = {}
|
||||
for attribute in user_cert.subject:
|
||||
if attribute.oid == x509.NameOID.COMMON_NAME:
|
||||
cert_info['CN'] = attribute.value
|
||||
elif attribute.oid == x509.NameOID.COUNTRY_NAME:
|
||||
cert_info['C'] = attribute.value
|
||||
elif attribute.oid == x509.NameOID.ORGANIZATION_NAME:
|
||||
cert_info['O'] = attribute.value
|
||||
|
||||
if len(cert_info) != 3:
|
||||
raise MIoTCertError('invalid cert info')
|
||||
if (
|
||||
did and cert_info['CN'] !=
|
||||
f'mips.{self._uid}.{self.__did_hash(did=did)}.2'
|
||||
):
|
||||
raise MIoTCertError('invalid COMMON_NAME')
|
||||
if 'C' not in cert_info or cert_info['C'] != 'CN':
|
||||
raise MIoTCertError('invalid COUNTRY_NAME')
|
||||
if 'O' not in cert_info or cert_info['O'] != 'Mijia Device':
|
||||
raise MIoTCertError('invalid ORGANIZATION_NAME')
|
||||
now_utc: datetime = datetime.now(timezone.utc)
|
||||
if (
|
||||
now_utc < user_cert.not_valid_before_utc or
|
||||
now_utc > user_cert.not_valid_after_utc
|
||||
):
|
||||
raise MIoTCertError('cert is not valid')
|
||||
return int((user_cert.not_valid_after_utc-now_utc).total_seconds())
|
||||
except (MIoTCertError, ValueError) as error:
|
||||
_LOGGER.error(
|
||||
'load_pem_x509_certificate failed, %s, %s',
|
||||
error, traceback.format_exc())
|
||||
return 0
|
||||
|
||||
def gen_user_key(self) -> str:
|
||||
"""Generate user private key."""
|
||||
private_key = ed25519.Ed25519PrivateKey.generate()
|
||||
return private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption()
|
||||
).decode('utf-8')
|
||||
|
||||
def gen_user_csr(self, user_key: str, did: str) -> str:
|
||||
"""Generate CSR of user certificate."""
|
||||
private_key = serialization.load_pem_private_key(
|
||||
data=user_key.encode('utf-8'), password=None)
|
||||
did_hash = self.__did_hash(did=did)
|
||||
builder = x509.CertificateSigningRequestBuilder().subject_name(
|
||||
x509.Name([
|
||||
# Central hub gateway service is only supported in China.
|
||||
x509.NameAttribute(NameOID.COUNTRY_NAME, 'CN'),
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'Mijia Device'),
|
||||
x509.NameAttribute(
|
||||
NameOID.COMMON_NAME, f'mips.{self._uid}.{did_hash}.2'),
|
||||
]))
|
||||
csr = builder.sign(
|
||||
private_key, algorithm=None, # type: ignore
|
||||
backend=default_backend())
|
||||
return csr.public_bytes(serialization.Encoding.PEM).decode('utf-8')
|
||||
|
||||
async def load_user_key_async(self) -> Optional[str]:
|
||||
"""Load user private key."""
|
||||
data = await self._storage.load_file_async(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self._key_name)
|
||||
return data.decode('utf-8') if data else None
|
||||
|
||||
async def update_user_key_async(self, key: str) -> bool:
|
||||
"""Update user private key."""
|
||||
return await self._storage.save_file_async(
|
||||
domain=self.CERT_DOMAIN,
|
||||
name_with_suffix=self._key_name,
|
||||
data=key.encode('utf-8'))
|
||||
|
||||
async def load_user_cert_async(self) -> Optional[str]:
|
||||
"""Load user certificate."""
|
||||
data = await self._storage.load_file_async(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self._cert_name)
|
||||
return data.decode('utf-8') if data else None
|
||||
|
||||
async def update_user_cert_async(self, cert: str) -> bool:
|
||||
"""Update user certificate."""
|
||||
return await self._storage.save_file_async(
|
||||
domain=self.CERT_DOMAIN,
|
||||
name_with_suffix=self._cert_name,
|
||||
data=cert.encode('utf-8'))
|
||||
|
||||
async def remove_ca_cert_async(self) -> bool:
|
||||
"""Remove CA certificate."""
|
||||
return await self._storage.remove_file_async(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self.CA_NAME)
|
||||
|
||||
async def remove_user_key_async(self) -> bool:
|
||||
"""Remove user private key."""
|
||||
return await self._storage.remove_file_async(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self._key_name)
|
||||
|
||||
async def remove_user_cert_async(self) -> bool:
|
||||
"""Remove user certificate."""
|
||||
return await self._storage.remove_file_async(
|
||||
domain=self.CERT_DOMAIN, name_with_suffix=self._cert_name)
|
||||
|
||||
def __did_hash(self, did: str) -> str:
|
||||
sha1_hash = hashes.Hash(hashes.SHA1(), backend=default_backend())
|
||||
sha1_hash.update(did.encode('utf-8'))
|
||||
return binascii.hexlify(sha1_hash.finalize()).decode('utf-8')
|
||||
|
||||
|
||||
class DeviceManufacturer:
|
||||
"""Device manufacturer."""
|
||||
DOMAIN: str = 'miot_specs'
|
||||
_main_loop: asyncio.AbstractEventLoop
|
||||
_storage: MIoTStorage
|
||||
_data: dict
|
||||
|
||||
def __init__(
|
||||
self, storage: MIoTStorage,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
) -> None:
|
||||
self._main_loop = loop or asyncio.get_event_loop()
|
||||
self._storage = storage
|
||||
self._data = {}
|
||||
|
||||
async def init_async(self) -> None:
|
||||
if self._data:
|
||||
return
|
||||
data_cache = await self._storage.load_async(
|
||||
domain=self.DOMAIN, name='manufacturer', type_=dict)
|
||||
if (
|
||||
isinstance(data_cache, dict)
|
||||
and 'data' in data_cache
|
||||
and 'ts' in data_cache
|
||||
and (int(time.time()) - data_cache['ts']) <
|
||||
MANUFACTURER_EFFECTIVE_TIME
|
||||
):
|
||||
self._data = data_cache['data']
|
||||
_LOGGER.debug('load manufacturer data success')
|
||||
return
|
||||
|
||||
data_cloud = None
|
||||
try:
|
||||
data_cloud = await MIoTHttp.get_json_async(
|
||||
url='https://cdn.cnbj1.fds.api.mi-img.com/res-conf/xiaomi-home/'
|
||||
'manufacturer.json',
|
||||
loop=self._main_loop)
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
_LOGGER.error('get manufacturer info failed, %s', err)
|
||||
|
||||
if data_cloud:
|
||||
await self._storage.save_async(
|
||||
domain=self.DOMAIN, name='manufacturer',
|
||||
data={'data': data_cloud, 'ts': int(time.time())})
|
||||
self._data = data_cloud
|
||||
_LOGGER.debug('update manufacturer data success')
|
||||
else:
|
||||
if isinstance(data_cache, dict):
|
||||
self._data = data_cache.get('data', {})
|
||||
_LOGGER.error('load manufacturer data failed, use local data')
|
||||
else:
|
||||
_LOGGER.error('load manufacturer data failed')
|
||||
|
||||
async def deinit_async(self) -> None:
|
||||
self._data.clear()
|
||||
|
||||
def get_name(self, short_name: str) -> str:
|
||||
if not self._data or not short_name or short_name not in self._data:
|
||||
return short_name
|
||||
return self._data[short_name].get('name', None) or short_name
|
||||
@@ -0,0 +1,136 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="https://cdn.web-global.fds.api.mi-img.com/mcfe--mi-account/static/favicon_new.ico">
|
||||
<link as="style"
|
||||
href="https://font.sec.miui.com/font/css?family=MiSans:300,400,500,600,700:Chinese_Simplify,Chinese_Traditional,Latin&display=swap"
|
||||
rel="preload">
|
||||
<title>TITLE_PLACEHOLDER</title>
|
||||
<style>
|
||||
body {
|
||||
background: white;
|
||||
color: black;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: MiSans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: black;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.frame {
|
||||
background: rgb(255 255 255 / 5%);
|
||||
width: 360px;
|
||||
padding: 40 45;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 20px 50px 0 hsla(0, 0%, 64%, .1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo-frame {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title-frame {
|
||||
margin: 20px 0 20px 0;
|
||||
font-size: 26px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.content-frame {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 20px;
|
||||
background-color: #ff5c00;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0 20px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
transition: all .3s cubic-bezier(.645, .045, .355, 1);
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="frame">
|
||||
<!-- XIAOMI LOGO-->
|
||||
<div class="logo-frame">
|
||||
<svg width="50" height="50" viewBox="0 0 193 193" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<polygon id="path-1"
|
||||
points="1.78097075e-14 0.000125324675 192.540685 0.000125324675 192.540685 192.540058 1.78097075e-14 192.540058">
|
||||
</polygon>
|
||||
</defs>
|
||||
<g id="\u9875\u9762-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="\u7F16\u7EC4">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Clip-2"></g>
|
||||
<path
|
||||
d="M172.473071,20.1164903 C154.306633,2.02148701 128.188344,-1.78097075e-14 96.2706558,-1.78097075e-14 C64.312237,-1.78097075e-14 38.155724,2.0452987 19.9974318,20.1872987 C1.84352597,38.3261656 1.78097075e-14,64.4406948 1.78097075e-14,96.3640227 C1.78097075e-14,128.286724 1.84352597,154.415039 20.0049513,172.556412 C38.1638701,190.704052 64.3141169,192.540058 96.2706558,192.540058 C128.225942,192.540058 154.376815,190.704052 172.53636,172.556412 C190.694653,154.409399 192.540685,128.286724 192.540685,96.3640227 C192.540685,64.3999643 190.672721,38.2553571 172.473071,20.1164903"
|
||||
id="Fill-1" fill="#FF6900" mask="url(#mask-2)"></path>
|
||||
<path
|
||||
d="M89.1841721,131.948836 C89.1841721,132.594885 88.640263,133.130648 87.9779221,133.130648 L71.5585097,133.130648 C70.8848896,133.130648 70.338474,132.594885 70.338474,131.948836 L70.338474,89.0100961 C70.338474,88.3584078 70.8848896,87.8251513 71.5585097,87.8251513 L87.9779221,87.8251513 C88.640263,87.8251513 89.1841721,88.3584078 89.1841721,89.0100961 L89.1841721,131.948836 Z"
|
||||
id="Fill-3" fill="#FFFFFF" mask="url(#mask-2)"></path>
|
||||
<path
|
||||
d="M121.332896,131.948836 C121.332896,132.594885 120.786481,133.130648 120.121633,133.130648 L104.492393,133.130648 C103.821906,133.130648 103.275491,132.594885 103.275491,131.948836 L103.275491,131.788421 L103.275491,94.9022357 C103.259198,88.4342292 102.889491,81.7863818 99.5502146,78.445226 C96.6790263,75.5652649 91.3251562,74.9054305 85.7557276,74.7669468 L57.4242049,74.7669468 C56.7555977,74.7669468 56.2154484,75.3045896 56.2154484,75.9512649 L56.2154484,128.074424 L56.2154484,131.948836 C56.2154484,132.594885 55.6640198,133.130648 54.9954127,133.130648 L39.3555198,133.130648 C38.6875393,133.130648 38.1498964,132.594885 38.1498964,131.948836 L38.1498964,60.5996188 C38.1498964,59.9447974 38.6875393,59.4121675 39.3555198,59.4121675 L84.4786692,59.4121675 C96.2717211,59.4121675 108.599909,59.9498104 114.680036,66.0380831 C120.786481,72.1533006 121.332896,84.4595571 121.332896,96.2657682 L121.332896,131.948836 Z"
|
||||
id="Fill-5" fill="#FFFFFF" mask="url(#mask-2)"></path>
|
||||
<path
|
||||
d="M153.53056,131.948836 C153.53056,132.594885 152.978505,133.130648 152.316164,133.130648 L136.678778,133.130648 C136.010797,133.130648 135.467515,132.594885 135.467515,131.948836 L135.467515,60.5996188 C135.467515,59.9447974 136.010797,59.4121675 136.678778,59.4121675 L152.316164,59.4121675 C152.978505,59.4121675 153.53056,59.9447974 153.53056,60.5996188 L153.53056,131.948836 Z"
|
||||
id="Fill-7" fill="#FFFFFF" mask="url(#mask-2)"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- TITLE -->
|
||||
<div class="title-frame">
|
||||
<a id="titleArea">TITLE_PLACEHOLDER</a>
|
||||
</div>
|
||||
<!-- CONTENT -->
|
||||
<div class="content-frame">
|
||||
<a id="contentArea">CONTENT_PLACEHOLDER</a>
|
||||
</div>
|
||||
<!-- BUTTON -->
|
||||
<button onClick="window.close();" id="buttonArea">BUTTON_PLACEHOLDER</button>
|
||||
</div>
|
||||
<script>
|
||||
if (STATUS_PLACEHOLDER) {
|
||||
window.opener = null;
|
||||
window.open('', '_self');
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,261 @@
|
||||
data:
|
||||
urn:miot-spec-v2:property:air-cooler:000000EB: open_close
|
||||
urn:miot-spec-v2:property:alarm:00000012: open_close
|
||||
urn:miot-spec-v2:property:anion:00000025: open_close
|
||||
urn:miot-spec-v2:property:anti-fake:00000130: yes_no
|
||||
urn:miot-spec-v2:property:arrhythmia:000000B4: yes_no
|
||||
urn:miot-spec-v2:property:auto-cleanup:00000124: open_close
|
||||
urn:miot-spec-v2:property:auto-deodorization:00000125: open_close
|
||||
urn:miot-spec-v2:property:auto-keep-warm:0000002B: open_close
|
||||
urn:miot-spec-v2:property:automatic-feeding:000000F0: open_close
|
||||
urn:miot-spec-v2:property:blow:000000CD: open_close
|
||||
urn:miot-spec-v2:property:card-insertion-state:00000106: yes_no
|
||||
urn:miot-spec-v2:property:contact-state:0000007C: contact_state
|
||||
urn:miot-spec-v2:property:current-physical-control-lock:00000099: open_close
|
||||
urn:miot-spec-v2:property:delay:0000014F: yes_no
|
||||
urn:miot-spec-v2:property:deodorization:000000C6: open_close
|
||||
urn:miot-spec-v2:property:dns-auto-mode:000000DC: open_close
|
||||
urn:miot-spec-v2:property:driving-status:000000B9: yes_no
|
||||
urn:miot-spec-v2:property:dryer:00000027: open_close
|
||||
urn:miot-spec-v2:property:eco:00000024: open_close
|
||||
urn:miot-spec-v2:property:glimmer-full-color:00000089: open_close
|
||||
urn:miot-spec-v2:property:guard-mode:000000B6: open_close
|
||||
urn:miot-spec-v2:property:heater:00000026: open_close
|
||||
urn:miot-spec-v2:property:heating:000000C7: open_close
|
||||
urn:miot-spec-v2:property:horizontal-swing:00000017: open_close
|
||||
urn:miot-spec-v2:property:hot-water-recirculation:0000011C: open_close
|
||||
urn:miot-spec-v2:property:image-distortion-correction:0000010F: open_close
|
||||
urn:miot-spec-v2:property:local-storage:0000011E: yes_no
|
||||
urn:miot-spec-v2:property:motion-detection:00000056: open_close
|
||||
urn:miot-spec-v2:property:motion-state:0000007D: motion_state
|
||||
urn:miot-spec-v2:property:motion-tracking:0000008A: open_close
|
||||
urn:miot-spec-v2:property:motor-reverse:00000072: yes_no
|
||||
urn:miot-spec-v2:property:mute:00000040: open_close
|
||||
urn:miot-spec-v2:property:off-delay:00000053: open_close
|
||||
urn:miot-spec-v2:property:on:00000006: open_close
|
||||
urn:miot-spec-v2:property:physical-controls-locked:0000001D: open_close
|
||||
urn:miot-spec-v2:property:plasma:00000132: yes_no
|
||||
urn:miot-spec-v2:property:preheat:00000103: open_close
|
||||
urn:miot-spec-v2:property:seating-state:000000B8: yes_no
|
||||
urn:miot-spec-v2:property:silent-execution:000000FB: yes_no
|
||||
urn:miot-spec-v2:property:sleep-aid-mode:0000010B: open_close
|
||||
urn:miot-spec-v2:property:sleep-mode:00000028: open_close
|
||||
urn:miot-spec-v2:property:snore-state:0000012A: yes_no
|
||||
urn:miot-spec-v2:property:soft-wind:000000CF: open_close
|
||||
urn:miot-spec-v2:property:speed-control:000000E8: open_close
|
||||
urn:miot-spec-v2:property:submersion-state:0000007E: yes_no
|
||||
urn:miot-spec-v2:property:time-watermark:00000087: open_close
|
||||
urn:miot-spec-v2:property:un-straight-blowing:00000100: open_close
|
||||
urn:miot-spec-v2:property:uv:00000029: open_close
|
||||
urn:miot-spec-v2:property:valve-switch:000000FE: open_close
|
||||
urn:miot-spec-v2:property:ventilation:000000CE: open_close
|
||||
urn:miot-spec-v2:property:vertical-swing:00000018: open_close
|
||||
urn:miot-spec-v2:property:wake-up-mode:00000107: open_close
|
||||
urn:miot-spec-v2:property:water-pump:000000F2: open_close
|
||||
urn:miot-spec-v2:property:watering:000000CC: open_close
|
||||
urn:miot-spec-v2:property:wdr-mode:00000088: open_close
|
||||
urn:miot-spec-v2:property:wet:0000002A: open_close
|
||||
urn:miot-spec-v2:property:wifi-band-combine:000000E0: open_close
|
||||
urn:miot-spec-v2:property:wifi-ssid-hidden:000000E3: yes_no
|
||||
urn:miot-spec-v2:property:wind-reverse:00000117: yes_no
|
||||
translate:
|
||||
default:
|
||||
de:
|
||||
'false': Falsch
|
||||
'true': Wahr
|
||||
en:
|
||||
'false': 'False'
|
||||
'true': 'True'
|
||||
es:
|
||||
'false': Falso
|
||||
'true': Verdadero
|
||||
fr:
|
||||
'false': Faux
|
||||
'true': Vrai
|
||||
it:
|
||||
'false': Falso
|
||||
'true': Vero
|
||||
ja:
|
||||
'false': 偽
|
||||
'true': 真
|
||||
nl:
|
||||
'false': 'False'
|
||||
'true': 'True'
|
||||
pt:
|
||||
'false': 'False'
|
||||
'true': 'True'
|
||||
pt-BR:
|
||||
'false': 'False'
|
||||
'true': 'True'
|
||||
ru:
|
||||
'false': Ложь
|
||||
'true': Истина
|
||||
tr:
|
||||
'false': Yanlış
|
||||
'true': Doğru
|
||||
zh-Hans:
|
||||
'false': 假
|
||||
'true': 真
|
||||
zh-Hant:
|
||||
'false': 假
|
||||
'true': 真
|
||||
contact_state:
|
||||
de:
|
||||
'false': Kein Kontakt
|
||||
'true': Kontakt
|
||||
en:
|
||||
'false': No Contact
|
||||
'true': Contact
|
||||
es:
|
||||
'false': Sin contacto
|
||||
'true': Contacto
|
||||
fr:
|
||||
'false': Pas de contact
|
||||
'true': Contact
|
||||
it:
|
||||
'false': Nessun contatto
|
||||
'true': Contatto
|
||||
ja:
|
||||
'false': 非接触
|
||||
'true': 接触
|
||||
nl:
|
||||
'false': Geen contact
|
||||
'true': Contact
|
||||
pt:
|
||||
'false': Sem contato
|
||||
'true': Contato
|
||||
pt-BR:
|
||||
'false': Sem contato
|
||||
'true': Contato
|
||||
ru:
|
||||
'false': Нет контакта
|
||||
'true': Контакт
|
||||
tr:
|
||||
'false': Temas Yok
|
||||
'true': Temas
|
||||
zh-Hans:
|
||||
'false': 分离
|
||||
'true': 接触
|
||||
zh-Hant:
|
||||
'false': 分離
|
||||
'true': 接觸
|
||||
motion_state:
|
||||
de:
|
||||
'false': Keine Bewegung erkannt
|
||||
'true': Bewegung erkannt
|
||||
en:
|
||||
'false': No Motion Detected
|
||||
'true': Motion Detected
|
||||
es:
|
||||
'false': No se detecta movimiento
|
||||
'true': Movimiento detectado
|
||||
fr:
|
||||
'false': Aucun mouvement détecté
|
||||
'true': Mouvement détecté
|
||||
it:
|
||||
'false': Nessun Movimento Rilevato
|
||||
'true': Movimento Rilevato
|
||||
ja:
|
||||
'false': 動きが検出されません
|
||||
'true': 動きを検知
|
||||
nl:
|
||||
'false': Geen contact
|
||||
'true': Contact
|
||||
pt:
|
||||
'false': Sem contato
|
||||
'true': Contato
|
||||
pt-BR:
|
||||
'false': Sem contato
|
||||
'true': Contato
|
||||
ru:
|
||||
'false': Движение не обнаружено
|
||||
'true': Обнаружено движение
|
||||
tr:
|
||||
'false': Hareket Algılanmadı
|
||||
'true': Hareket Algılandı
|
||||
zh-Hans:
|
||||
'false': 无人
|
||||
'true': 有人
|
||||
zh-Hant:
|
||||
'false': 無人
|
||||
'true': 有人
|
||||
open_close:
|
||||
de:
|
||||
'false': Schließen
|
||||
'true': Öffnen
|
||||
en:
|
||||
'false': Close
|
||||
'true': Open
|
||||
es:
|
||||
'false': Cerrado
|
||||
'true': Abierto
|
||||
fr:
|
||||
'false': Fermer
|
||||
'true': Ouvert
|
||||
it:
|
||||
'false': Chiuso
|
||||
'true': Aperto
|
||||
ja:
|
||||
'false': 閉じる
|
||||
'true': 開く
|
||||
nl:
|
||||
'false': Dicht
|
||||
'true': Open
|
||||
pt:
|
||||
'false': Fechado
|
||||
'true': Aberto
|
||||
pt-BR:
|
||||
'false': Fechado
|
||||
'true': Aberto
|
||||
ru:
|
||||
'false': Закрыть
|
||||
'true': Открыть
|
||||
tr:
|
||||
'false': Kapalı
|
||||
'true': Açık
|
||||
zh-Hans:
|
||||
'false': 关闭
|
||||
'true': 开启
|
||||
zh-Hant:
|
||||
'false': 關閉
|
||||
'true': 開啟
|
||||
yes_no:
|
||||
de:
|
||||
'false': Nein
|
||||
'true': Ja
|
||||
en:
|
||||
'false': 'No'
|
||||
'true': 'Yes'
|
||||
es:
|
||||
'false': 'No'
|
||||
'true': Sí
|
||||
fr:
|
||||
'false': Non
|
||||
'true': Oui
|
||||
it:
|
||||
'false': 'No'
|
||||
'true': Si
|
||||
ja:
|
||||
'false': いいえ
|
||||
'true': はい
|
||||
nl:
|
||||
'false': Nee
|
||||
'true': Ja
|
||||
pt:
|
||||
'false': Não
|
||||
'true': Sim
|
||||
pt-BR:
|
||||
'false': Não
|
||||
'true': Sim
|
||||
ru:
|
||||
'false': Нет
|
||||
'true': Да
|
||||
tr:
|
||||
'false': Hayır
|
||||
'true': Evet
|
||||
zh-Hans:
|
||||
'false': 否
|
||||
'true': 是
|
||||
zh-Hant:
|
||||
'false': 否
|
||||
'true': 是
|
||||
@@ -0,0 +1,618 @@
|
||||
{
|
||||
"urn:miot-spec-v2:device:bath-heater:0000A028:yeelink-v10": {
|
||||
"en": {
|
||||
"service:003:property:001:valuelist:000": "Idle",
|
||||
"service:003:property:001:valuelist:001": "Dry"
|
||||
},
|
||||
"tr": {
|
||||
"service:003:property:001:valuelist:000": "Boşta",
|
||||
"service:003:property:001:valuelist:001": "Kurutma"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:electronic-valve:0000A0A7:lxzn-02": {
|
||||
"tr": {
|
||||
"service:004:property:001": "Yüksek Güç - Eşik Ayarı"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:004:property:001": "功率过高-阈值设置"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:electronic-valve:0000A0A7:ykcn-cbcs": {
|
||||
"tr": {
|
||||
"service:004:property:001": "Yüksek Güç - Eşik Ayarı",
|
||||
"service:004:property:009": "Düşük Voltaj Alarmı - Eşik Ayarı"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:004:property:001": "功率过高-阈值设置",
|
||||
"service:004:property:009": "欠压告警-阈值设置"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:fan:0000A005:zhimi-za1": {
|
||||
"tr": {
|
||||
"service:002:property:005:valuelist:000": "Doğal Rüzgar",
|
||||
"service:002:property:005:valuelist:001": "Doğrudan Üfleme"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:002:property:005:valuelist:000": "自然风",
|
||||
"service:002:property:005:valuelist:001": "直吹风"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
||||
"de": {
|
||||
"service:001": "Geräteinformationen",
|
||||
"service:001:property:003": "Geräte-ID",
|
||||
"service:001:property:005": "Seriennummer (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Netzwerk geändert",
|
||||
"service:002:event:002": "Netzwerk geändert",
|
||||
"service:002:property:001": "Zugriffsmethode",
|
||||
"service:002:property:001:valuelist:000": "Kabelgebunden",
|
||||
"service:002:property:001:valuelist:001": "5G Drahtlos",
|
||||
"service:002:property:001:valuelist:002": "2.4G Drahtlos",
|
||||
"service:002:property:002": "IP-Adresse",
|
||||
"service:002:property:003": "WiFi-Netzwerkname",
|
||||
"service:002:property:004": "Aktuelle Zeit",
|
||||
"service:002:property:005": "DHCP-Server-MAC-Adresse",
|
||||
"service:003": "Anzeigelampe",
|
||||
"service:003:property:001": "Schalter",
|
||||
"service:004": "Virtueller Dienst",
|
||||
"service:004:action:001": "Virtuelles Ereignis erzeugen",
|
||||
"service:004:event:001": "Virtuelles Ereignis aufgetreten",
|
||||
"service:004:property:001": "Ereignisname"
|
||||
},
|
||||
"en": {
|
||||
"service:001": "Device Information",
|
||||
"service:001:property:003": "Device ID",
|
||||
"service:001:property:005": "Serial Number (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Network Changed",
|
||||
"service:002:event:002": "Network Changed",
|
||||
"service:002:property:001": "Access Method",
|
||||
"service:002:property:001:valuelist:000": "Wired",
|
||||
"service:002:property:001:valuelist:001": "5G Wireless",
|
||||
"service:002:property:001:valuelist:002": "2.4G Wireless",
|
||||
"service:002:property:002": "IP Address",
|
||||
"service:002:property:003": "WiFi Network Name",
|
||||
"service:002:property:004": "Current Time",
|
||||
"service:002:property:005": "DHCP Server MAC Address",
|
||||
"service:003": "Indicator Light",
|
||||
"service:003:property:001": "Switch",
|
||||
"service:004": "Virtual Service",
|
||||
"service:004:action:001": "Generate Virtual Event",
|
||||
"service:004:event:001": "Virtual Event Occurred",
|
||||
"service:004:property:001": "Event Name"
|
||||
},
|
||||
"es": {
|
||||
"service:001": "Información del dispositivo",
|
||||
"service:001:property:003": "ID del dispositivo",
|
||||
"service:001:property:005": "Número de serie (SN)",
|
||||
"service:002": "Puerta de enlace",
|
||||
"service:002:event:001": "Cambio de red",
|
||||
"service:002:event:002": "Cambio de red",
|
||||
"service:002:property:001": "Método de acceso",
|
||||
"service:002:property:001:valuelist:000": "Cableado",
|
||||
"service:002:property:001:valuelist:001": "5G inalámbrico",
|
||||
"service:002:property:001:valuelist:002": "2.4G inalámbrico",
|
||||
"service:002:property:002": "Dirección IP",
|
||||
"service:002:property:003": "Nombre de red WiFi",
|
||||
"service:002:property:004": "Hora actual",
|
||||
"service:002:property:005": "Dirección MAC del servidor DHCP",
|
||||
"service:003": "Luz indicadora",
|
||||
"service:003:property:001": "Interruptor",
|
||||
"service:004": "Servicio virtual",
|
||||
"service:004:action:001": "Generar evento virtual",
|
||||
"service:004:event:001": "Ocurrió un evento virtual",
|
||||
"service:004:property:001": "Nombre del evento"
|
||||
},
|
||||
"fr": {
|
||||
"service:001": "Informations sur l'appareil",
|
||||
"service:001:property:003": "ID de l'appareil",
|
||||
"service:001:property:005": "Numéro de série (SN)",
|
||||
"service:002": "Passerelle",
|
||||
"service:002:event:001": "Changement de réseau",
|
||||
"service:002:event:002": "Changement de réseau",
|
||||
"service:002:property:001": "Méthode d'accès",
|
||||
"service:002:property:001:valuelist:000": "Câblé",
|
||||
"service:002:property:001:valuelist:001": "Sans fil 5G",
|
||||
"service:002:property:001:valuelist:002": "Sans fil 2.4G",
|
||||
"service:002:property:002": "Adresse IP",
|
||||
"service:002:property:003": "Nom du réseau WiFi",
|
||||
"service:002:property:004": "Heure actuelle",
|
||||
"service:002:property:005": "Adresse MAC du serveur DHCP",
|
||||
"service:003": "Voyant lumineux",
|
||||
"service:003:property:001": "Interrupteur",
|
||||
"service:004": "Service virtuel",
|
||||
"service:004:action:001": "Générer un événement virtuel",
|
||||
"service:004:event:001": "Événement virtuel survenu",
|
||||
"service:004:property:001": "Nom de l'événement"
|
||||
},
|
||||
"ja": {
|
||||
"service:001": "デバイス情報",
|
||||
"service:001:property:003": "デバイスID",
|
||||
"service:001:property:005": "シリアル番号 (SN)",
|
||||
"service:002": "ゲートウェイ",
|
||||
"service:002:event:001": "ネットワークが変更されました",
|
||||
"service:002:event:002": "ネットワークが変更されました",
|
||||
"service:002:property:001": "アクセス方法",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G ワイヤレス",
|
||||
"service:002:property:001:valuelist:002": "2.4G ワイヤレス",
|
||||
"service:002:property:002": "IPアドレス",
|
||||
"service:002:property:003": "WiFiネットワーク名",
|
||||
"service:002:property:004": "現在の時間",
|
||||
"service:002:property:005": "DHCPサーバーMACアドレス",
|
||||
"service:003": "インジケータライト",
|
||||
"service:003:property:001": "スイッチ",
|
||||
"service:004": "バーチャルサービス",
|
||||
"service:004:action:001": "バーチャルイベントを生成",
|
||||
"service:004:event:001": "バーチャルイベントが発生しました",
|
||||
"service:004:property:001": "イベント名"
|
||||
},
|
||||
"ru": {
|
||||
"service:001": "Информация об устройстве",
|
||||
"service:001:property:003": "ID устройства",
|
||||
"service:001:property:005": "Серийный номер (SN)",
|
||||
"service:002": "Шлюз",
|
||||
"service:002:event:001": "Сеть изменена",
|
||||
"service:002:event:002": "Сеть изменена",
|
||||
"service:002:property:001": "Метод доступа",
|
||||
"service:002:property:001:valuelist:000": "Проводной",
|
||||
"service:002:property:001:valuelist:001": "5G Беспроводной",
|
||||
"service:002:property:001:valuelist:002": "2.4G Беспроводной",
|
||||
"service:002:property:002": "IP Адрес",
|
||||
"service:002:property:003": "Название WiFi сети",
|
||||
"service:002:property:004": "Текущее время",
|
||||
"service:002:property:005": "MAC адрес DHCP сервера",
|
||||
"service:003": "Световой индикатор",
|
||||
"service:003:property:001": "Переключатель",
|
||||
"service:004": "Виртуальная служба",
|
||||
"service:004:action:001": "Создать виртуальное событие",
|
||||
"service:004:event:001": "Произошло виртуальное событие",
|
||||
"service:004:property:001": "Название события"
|
||||
},
|
||||
"tr": {
|
||||
"service:001": "Cihaz Bilgileri",
|
||||
"service:001:property:003": "Cihaz ID",
|
||||
"service:001:property:005": "Seri Numarası (SN)",
|
||||
"service:002": "Ağ Geçidi",
|
||||
"service:002:event:001": "Ağ Değişti",
|
||||
"service:002:event:002": "Ağ Değişti",
|
||||
"service:002:property:001": "Erişim Yöntemi",
|
||||
"service:002:property:001:valuelist:000": "Kablolu",
|
||||
"service:002:property:001:valuelist:001": "5G Kablosuz",
|
||||
"service:002:property:001:valuelist:002": "2.4G Kablosuz",
|
||||
"service:002:property:002": "IP Adresi",
|
||||
"service:002:property:003": "WiFi Ağ Adı",
|
||||
"service:002:property:004": "Geçerli Zaman",
|
||||
"service:002:property:005": "DHCP Sunucusu MAC Adresi",
|
||||
"service:003": "Gösterge Işığı",
|
||||
"service:003:property:001": "Anahtar",
|
||||
"service:004": "Sanal Hizmet",
|
||||
"service:004:action:001": "Sanal Olay Oluştur",
|
||||
"service:004:event:001": "Sanal Olay Gerçekleşti",
|
||||
"service:004:property:001": "Olay Adı"
|
||||
},
|
||||
"zh-Hant": {
|
||||
"service:001": "設備信息",
|
||||
"service:001:property:003": "設備ID",
|
||||
"service:001:property:005": "序號 (SN)",
|
||||
"service:002": "網關",
|
||||
"service:002:event:001": "網路發生變化",
|
||||
"service:002:event:002": "網路發生變化",
|
||||
"service:002:property:001": "接入方式",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G 無線",
|
||||
"service:002:property:001:valuelist:002": "2.4G 無線",
|
||||
"service:002:property:002": "IP地址",
|
||||
"service:002:property:003": "WiFi網路名稱",
|
||||
"service:002:property:004": "當前時間",
|
||||
"service:002:property:005": "DHCP伺服器MAC地址",
|
||||
"service:003": "指示燈",
|
||||
"service:003:property:001": "開關",
|
||||
"service:004": "虛擬服務",
|
||||
"service:004:action:001": "產生虛擬事件",
|
||||
"service:004:event:001": "虛擬事件發生",
|
||||
"service:004:property:001": "事件名稱"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:lock:0000A038:loock-t2pv1": {
|
||||
"tr": {
|
||||
"service:003:property:1021:valuelist:000": "Kilitli",
|
||||
"service:003:property:1021:valuelist:001": "Kilitli (Çocuk Kilidi)",
|
||||
"service:003:property:1021:valuelist:002": "Kilitli (İçten Kilitli)",
|
||||
"service:003:property:1021:valuelist:003": "Kilitli (İçten Kilitli + Çocuk Kilidi)",
|
||||
"service:003:property:1021:valuelist:004": "Kilidi Açık",
|
||||
"service:003:property:1021:valuelist:008": "Kapı Kapalı Değil (Zaman Aşımı)",
|
||||
"service:003:property:1021:valuelist:012": "Kapı Aralık"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:003:property:1021:valuelist:000": "已上锁",
|
||||
"service:003:property:1021:valuelist:001": "已上锁(童锁)",
|
||||
"service:003:property:1021:valuelist:002": "已上锁(反锁)",
|
||||
"service:003:property:1021:valuelist:003": "已上锁(反锁+童锁)",
|
||||
"service:003:property:1021:valuelist:004": "已开锁",
|
||||
"service:003:property:1021:valuelist:008": "门未关(门超时未关)",
|
||||
"service:003:property:1021:valuelist:012": "门虚掩"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:plant-monitor:0000A030:hhcc-v1": {
|
||||
"en": {
|
||||
"service:002:property:001": "Soil Moisture"
|
||||
},
|
||||
"tr": {
|
||||
"service:002:property:001": "Toprak Nemi",
|
||||
"service:002:property:003": "Işık Yoğunluğu"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:002:property:001": "土壤湿度",
|
||||
"service:002:property:003": "光照强度"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:switch:0000A003:090615-x1tpm": {
|
||||
"en": {
|
||||
"service:027:property:001": "Fan Switch",
|
||||
"service:027:property:003": "Light Switch",
|
||||
"service:027:property:004": "Fan and Light Switch"
|
||||
},
|
||||
"tr": {
|
||||
"service:027:property:001": "Vantilatör Anahtarı",
|
||||
"service:027:property:003": "Işık Anahtarı",
|
||||
"service:027:property:004": "Vantilatör ve Işık Anahtarı"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040": {
|
||||
"en": {
|
||||
"service:011": "Right Button On and Off",
|
||||
"service:011:property:001": "Right Button On and Off",
|
||||
"service:015:action:001": "Left Button Identify",
|
||||
"service:016:action:001": "Middle Button Identify",
|
||||
"service:017:action:001": "Right Button Identify"
|
||||
},
|
||||
"tr": {
|
||||
"service:011": "Sağ Düğme Açma ve Kapama",
|
||||
"service:011:property:001": "Sağ Düğme Açma ve Kapama",
|
||||
"service:015:action:001": "Sol Düğme Tanımlama",
|
||||
"service:016:action:001": "Orta Düğme Tanımlama",
|
||||
"service:017:action:001": "Sağ Düğme Tanımlama"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:015:action:001": "左键确认",
|
||||
"service:016:action:001": "中键确认",
|
||||
"service:017:action:001": "右键确认"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123e": {
|
||||
"ru": {
|
||||
"service:002": "термостат",
|
||||
"service:002:property:001": "выключатель",
|
||||
"service:002:property:002": "режим нагрузки",
|
||||
"service:002:property:002:valuelist:000": "без подогрева",
|
||||
"service:002:property:002:valuelist:001": "нагрев",
|
||||
"service:002:property:003": "неисправность",
|
||||
"service:002:property:003:valuelist:000": "ошибка датчика",
|
||||
"service:002:property:003:valuelist:001": "без ошибок",
|
||||
"service:002:property:003:valuelist:002": "защита от высоких температур",
|
||||
"service:002:property:003:valuelist:003": "криогенная защита",
|
||||
"service:002:property:004": "режим",
|
||||
"service:002:property:004:valuelist:000": "ручной режим",
|
||||
"service:002:property:004:valuelist:001": "домашний режим",
|
||||
"service:002:property:004:valuelist:002": "режим выхода из дома",
|
||||
"service:002:property:004:valuelist:003": "автоматический режим",
|
||||
"service:002:property:004:valuelist:004": "Режим сна",
|
||||
"service:002:property:005": "температура цели",
|
||||
"service:002:property:007": "текущая температура",
|
||||
"service:004": "Пользовательские услуги",
|
||||
"service:004:property:001": "детский замок",
|
||||
"service:004:property:002": "тип датчика",
|
||||
"service:004:property:002:valuelist:000": "внутренний датчик",
|
||||
"service:004:property:002:valuelist:001": "выносной датчик",
|
||||
"service:004:property:002:valuelist:002": "встроенный и внешний датчик",
|
||||
"service:004:property:003": "пусковая разность температур",
|
||||
"service:004:property:004": "компенсационная температура",
|
||||
"service:004:property:005": "температура выносного датчика",
|
||||
"service:004:property:006": "максимальная температура цели",
|
||||
"service:004:property:007": "минимальная температура цели "
|
||||
},
|
||||
"tr": {
|
||||
"service:002": "Termostat",
|
||||
"service:002:property:001": "Anahtar",
|
||||
"service:002:property:002": "Yük Modu",
|
||||
"service:002:property:002:valuelist:000": "Isıtmasız",
|
||||
"service:002:property:002:valuelist:001": "Isıtma",
|
||||
"service:002:property:003": "Arıza",
|
||||
"service:002:property:003:valuelist:000": "Sensör Hatası",
|
||||
"service:002:property:003:valuelist:001": "Hata Yok",
|
||||
"service:002:property:003:valuelist:002": "Yüksek Sıcaklık Koruması",
|
||||
"service:002:property:003:valuelist:003": "Düşük Sıcaklık Koruması",
|
||||
"service:002:property:004": "Mod",
|
||||
"service:002:property:004:valuelist:000": "Manuel Mod",
|
||||
"service:002:property:004:valuelist:001": "Ev Modu",
|
||||
"service:002:property:004:valuelist:002": "Dışarıda Modu",
|
||||
"service:002:property:004:valuelist:003": "Otomatik Mod",
|
||||
"service:002:property:004:valuelist:004": "Uyku Modu",
|
||||
"service:002:property:005": "Hedef Sıcaklık",
|
||||
"service:002:property:007": "Geçerli Sıcaklık",
|
||||
"service:004": "Özel Hizmetler",
|
||||
"service:004:property:001": "Çocuk Kilidi",
|
||||
"service:004:property:002": "Sensör Tipi",
|
||||
"service:004:property:002:valuelist:000": "Dahili Sensör",
|
||||
"service:004:property:002:valuelist:001": "Harici Sensör",
|
||||
"service:004:property:002:valuelist:002": "Dahili ve Harici Sensör",
|
||||
"service:004:property:003": "Başlangıç Sıcaklık Farkı",
|
||||
"service:004:property:004": "Telafi Sıcaklığı",
|
||||
"service:004:property:005": "Harici Sensör Sıcaklığı",
|
||||
"service:004:property:006": "Maksimum Hedef Sıcaklık",
|
||||
"service:004:property:007": "Minimum Hedef Sıcaklık"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123w": {
|
||||
"ru": {
|
||||
"service:002": "термостат",
|
||||
"service:002:property:001": "выключатель",
|
||||
"service:002:property:002": "режим нагрузки",
|
||||
"service:002:property:002:valuelist:000": "нагрев",
|
||||
"service:002:property:002:valuelist:001": "без подогрева",
|
||||
"service:002:property:003": "неисправность",
|
||||
"service:002:property:003:valuelist:000": "без ошибок",
|
||||
"service:002:property:003:valuelist:001": "ошибка датчика",
|
||||
"service:002:property:003:valuelist:002": "защита от высоких температур",
|
||||
"service:002:property:003:valuelist:003": "криогенная защита",
|
||||
"service:002:property:004": "режим",
|
||||
"service:002:property:004:valuelist:000": "ручной режим",
|
||||
"service:002:property:004:valuelist:001": "домашний режим",
|
||||
"service:002:property:004:valuelist:002": "режим выхода из дома",
|
||||
"service:002:property:004:valuelist:003": "автоматический режим",
|
||||
"service:002:property:004:valuelist:004": "Режим сна",
|
||||
"service:002:property:005": "температура цели",
|
||||
"service:002:property:007": "текущая температура",
|
||||
"service:004": "Пользовательские услуги",
|
||||
"service:004:property:001": "детский замок",
|
||||
"service:004:property:002": "тип датчика",
|
||||
"service:004:property:002:valuelist:000": "внутренний датчик",
|
||||
"service:004:property:002:valuelist:001": "выносной датчик",
|
||||
"service:004:property:002:valuelist:002": "встроенный и внешний датчик",
|
||||
"service:004:property:003": "пусковая разность температур",
|
||||
"service:004:property:004": "компенсационная температура",
|
||||
"service:004:property:005": "температура выносного датчика",
|
||||
"service:004:property:006": "максимальная температура цели",
|
||||
"service:004:property:007": "минимальная температура цели "
|
||||
},
|
||||
"tr": {
|
||||
"service:002": "Termostat",
|
||||
"service:002:property:001": "Anahtar",
|
||||
"service:002:property:002": "Yük Modu",
|
||||
"service:002:property:002:valuelist:000": "Isıtma",
|
||||
"service:002:property:002:valuelist:001": "Isıtmasız",
|
||||
"service:002:property:003": "Arıza",
|
||||
"service:002:property:003:valuelist:000": "Hata Yok",
|
||||
"service:002:property:003:valuelist:001": "Sensör Hatası",
|
||||
"service:002:property:003:valuelist:002": "Yüksek Sıcaklık Koruması",
|
||||
"service:002:property:003:valuelist:003": "Düşük Sıcaklık Koruması",
|
||||
"service:002:property:004": "Mod",
|
||||
"service:002:property:004:valuelist:000": "Manuel Mod",
|
||||
"service:002:property:004:valuelist:001": "Ev Modu",
|
||||
"service:002:property:004:valuelist:002": "Dışarıda Modu",
|
||||
"service:002:property:004:valuelist:003": "Otomatik Mod",
|
||||
"service:002:property:004:valuelist:004": "Uyku Modu",
|
||||
"service:002:property:005": "Hedef Sıcaklık",
|
||||
"service:002:property:007": "Geçerli Sıcaklık",
|
||||
"service:004": "Özel Hizmetler",
|
||||
"service:004:property:001": "Çocuk Kilidi",
|
||||
"service:004:property:002": "Sensör Tipi",
|
||||
"service:004:property:002:valuelist:000": "Dahili Sensör",
|
||||
"service:004:property:002:valuelist:001": "Harici Sensör",
|
||||
"service:004:property:002:valuelist:002": "Dahili ve Harici Sensör",
|
||||
"service:004:property:003": "Başlangıç Sıcaklık Farkı",
|
||||
"service:004:property:004": "Telafi Sıcaklığı",
|
||||
"service:004:property:005": "Harici Sensör Sıcaklığı",
|
||||
"service:004:property:006": "Maksimum Hedef Sıcaklık",
|
||||
"service:004:property:007": "Minimum Hedef Sıcaklık"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01": {
|
||||
"en": {
|
||||
"service:002": "Thermostat",
|
||||
"service:002:property:002": "Air Conditioner Mode",
|
||||
"service:004": "Air Conditioner"
|
||||
},
|
||||
"tr": {
|
||||
"service:002": "Yerden Isıtma",
|
||||
"service:002:property:002": "Klima Modu",
|
||||
"service:004": "Klima"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:002": "地暖",
|
||||
"service:004": "空调"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:vacuum:0000A006:ijai-v1": {
|
||||
"tr": {
|
||||
"service:007:property:005:valuelist:000": "Sessiz",
|
||||
"service:007:property:005:valuelist:001": "Standart",
|
||||
"service:007:property:005:valuelist:002": "Orta",
|
||||
"service:007:property:005:valuelist:003": "Güçlü"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:007:property:005:valuelist:000": "安静",
|
||||
"service:007:property:005:valuelist:001": "标准",
|
||||
"service:007:property:005:valuelist:002": "中档",
|
||||
"service:007:property:005:valuelist:003": "强力"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:vacuum:0000A006:narwa-001": {
|
||||
"tr": {
|
||||
"service:001": "Cihaz Bilgileri",
|
||||
"service:002": "Pil",
|
||||
"service:002:property:001": "Pil Seviyesi",
|
||||
"service:003": "Robot Süpürge",
|
||||
"service:003:property:001": "Çalışma Durumu",
|
||||
"service:004": "Temizleme",
|
||||
"service:004:property:001": "Temizlik Modu",
|
||||
"service:004:property:002": "Emme Gücü",
|
||||
"service:005": "Harita Yönetimi",
|
||||
"service:005:property:001": "Harita Listesi",
|
||||
"service:005:property:002": "Aktif Harita",
|
||||
"service:005:property:003": "Harita Adı",
|
||||
"service:006": "Tüketim Malzemeleri",
|
||||
"service:006:property:001": "Ana Fırça Ömrü",
|
||||
"service:006:property:002": "Yan Fırça Ömrü",
|
||||
"service:006:property:003": "Filtre Ömrü"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:vacuum:0000A006:narwa-ax11": {
|
||||
"tr": {
|
||||
"service:001": "Cihaz Bilgileri",
|
||||
"service:002": "Pil",
|
||||
"service:002:property:001": "Pil Seviyesi",
|
||||
"service:003": "Robot Süpürge",
|
||||
"service:003:property:001": "Çalışma Durumu",
|
||||
"service:004": "Temizleme",
|
||||
"service:004:property:001": "Temizlik Modu",
|
||||
"service:004:property:002": "Emme Gücü",
|
||||
"service:005": "Harita Yönetimi",
|
||||
"service:005:property:001": "Harita Listesi",
|
||||
"service:005:property:002": "Aktif Harita",
|
||||
"service:005:property:003": "Harita Adı",
|
||||
"service:006": "Tüketim Malzemeleri",
|
||||
"service:006:property:001": "Ana Fırça Ömrü",
|
||||
"service:006:property:002": "Yan Fırça Ömrü",
|
||||
"service:006:property:003": "Filtre Ömrü"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:vacuum:0000A006:roidmi-v60": {
|
||||
"tr": {
|
||||
"service:001": "Cihaz Bilgileri",
|
||||
"service:002": "Pil",
|
||||
"service:002:property:001": "Pil Seviyesi",
|
||||
"service:003": "Robot Süpürge",
|
||||
"service:003:property:001": "Çalışma Durumu",
|
||||
"service:004": "Temizleme",
|
||||
"service:004:property:001": "Temizlik Modu",
|
||||
"service:004:property:002": "Emme Gücü",
|
||||
"service:004:property:003": "Temizlik Süresi",
|
||||
"service:004:property:004": "Temizlenen Alan",
|
||||
"service:005": "Harita Yönetimi",
|
||||
"service:005:property:001": "Harita Listesi",
|
||||
"service:005:property:002": "Aktif Harita",
|
||||
"service:005:property:003": "Harita Adı",
|
||||
"service:006": "Tüketim Malzemeleri",
|
||||
"service:006:property:001": "Ana Fırça Ömrü",
|
||||
"service:006:property:002": "Yan Fırça Ömrü",
|
||||
"service:006:property:003": "Filtre Ömrü",
|
||||
"service:007": "Emme Gücü",
|
||||
"service:007:property:001": "Güç Seviyesi",
|
||||
"service:007:property:001:valuelist:000": "Sessiz",
|
||||
"service:007:property:001:valuelist:001": "Standart",
|
||||
"service:007:property:001:valuelist:002": "Orta",
|
||||
"service:007:property:001:valuelist:003": "Güçlü"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:vacuum:0000A006:viomi-v38": {
|
||||
"tr": {
|
||||
"service:001": "Cihaz Bilgileri",
|
||||
"service:002": "Pil",
|
||||
"service:002:property:001": "Pil Seviyesi",
|
||||
"service:002:property:002": "Şarj Durumu",
|
||||
"service:003": "Robot Süpürge",
|
||||
"service:003:property:001": "Çalışma Durumu",
|
||||
"service:003:property:002": "Hata Kodu",
|
||||
"service:004": "Temizleme",
|
||||
"service:004:property:001": "Temizlik Modu",
|
||||
"service:004:property:002": "Emme Gücü",
|
||||
"service:004:property:003": "Su Seviyesi",
|
||||
"service:004:property:004": "Temizlik Süresi",
|
||||
"service:004:property:005": "Temizlenen Alan",
|
||||
"service:005": "Tüketim Malzemeleri",
|
||||
"service:005:property:001": "Ana Fırça Ömrü",
|
||||
"service:005:property:002": "Yan Fırça Ömrü",
|
||||
"service:005:property:003": "Filtre Ömrü",
|
||||
"service:005:property:004": "Sensör Ömrü",
|
||||
"service:005:property:005": "Paspas Bezi Ömrü",
|
||||
"service:006": "Harita Yönetimi",
|
||||
"service:006:property:001": "Harita Listesi",
|
||||
"service:006:property:002": "Aktif Harita",
|
||||
"service:006:property:003": "Harita Adı",
|
||||
"service:006:property:004": "Harita ID",
|
||||
"service:007": "Emme Gücü",
|
||||
"service:007:property:001": "Güç Seviyesi",
|
||||
"service:007:property:001:valuelist:000": "Sessiz",
|
||||
"service:007:property:001:valuelist:001": "Standart",
|
||||
"service:007:property:001:valuelist:002": "Orta",
|
||||
"service:007:property:001:valuelist:003": "Güçlü",
|
||||
"service:007:property:001:valuelist:004": "Maksimum",
|
||||
"service:008": "Su Tankı",
|
||||
"service:008:property:001": "Su Seviyesi",
|
||||
"service:008:property:001:valuelist:000": "Düşük",
|
||||
"service:008:property:001:valuelist:001": "Orta",
|
||||
"service:008:property:001:valuelist:002": "Yüksek",
|
||||
"service:009": "Bölge Temizliği",
|
||||
"service:009:property:001": "Oda Seçimi",
|
||||
"service:009:property:002": "Bölge Temizliği",
|
||||
"service:010": "Zamanlama",
|
||||
"service:010:property:001": "Zamanlayıcı",
|
||||
"service:010:property:002": "Temizlik Zamanı",
|
||||
"service:011": "Ses ve Bildirim",
|
||||
"service:011:property:001": "Ses Seviyesi",
|
||||
"service:011:property:002": "Sesli Bildirimler",
|
||||
"service:012": "Gelişmiş Ayarlar",
|
||||
"service:012:property:001": "Halı Modu",
|
||||
"service:012:property:002": "Tekrar Geçiş",
|
||||
"service:012:property:003": "Kenar Temizliği",
|
||||
"service:012:property:004": "Engel Algılama"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:vacuum:0000A006:viomi-v5": {
|
||||
"tr": {
|
||||
"service:001": "Cihaz Bilgileri",
|
||||
"service:002": "Pil",
|
||||
"service:002:property:001": "Pil Seviyesi",
|
||||
"service:002:property:002": "Şarj Durumu",
|
||||
"service:003": "Robot Süpürge",
|
||||
"service:003:property:001": "Çalışma Durumu",
|
||||
"service:003:property:002": "Hata Kodu",
|
||||
"service:004": "Temizleme",
|
||||
"service:004:property:001": "Temizlik Modu",
|
||||
"service:004:property:002": "Emme Gücü",
|
||||
"service:004:property:003": "Su Seviyesi",
|
||||
"service:004:property:004": "Temizlik Süresi",
|
||||
"service:004:property:005": "Temizlenen Alan",
|
||||
"service:005": "Tüketim Malzemeleri",
|
||||
"service:005:property:001": "Ana Fırça Ömrü",
|
||||
"service:005:property:002": "Yan Fırça Ömrü",
|
||||
"service:005:property:003": "Filtre Ömrü",
|
||||
"service:005:property:004": "Sensör Ömrü",
|
||||
"service:005:property:005": "Paspas Bezi Ömrü",
|
||||
"service:006": "Harita Yönetimi",
|
||||
"service:006:property:001": "Harita Listesi",
|
||||
"service:006:property:002": "Aktif Harita",
|
||||
"service:006:property:003": "Harita Adı",
|
||||
"service:006:property:004": "Harita ID",
|
||||
"service:007": "Emme Gücü",
|
||||
"service:007:property:001": "Güç Seviyesi",
|
||||
"service:007:property:001:valuelist:000": "Sessiz",
|
||||
"service:007:property:001:valuelist:001": "Standart",
|
||||
"service:007:property:001:valuelist:002": "Orta",
|
||||
"service:007:property:001:valuelist:003": "Güçlü",
|
||||
"service:007:property:001:valuelist:004": "Maksimum",
|
||||
"service:008": "Su Tankı",
|
||||
"service:008:property:001": "Su Seviyesi",
|
||||
"service:008:property:001:valuelist:000": "Düşük",
|
||||
"service:008:property:001:valuelist:001": "Orta",
|
||||
"service:008:property:001:valuelist:002": "Yüksek",
|
||||
"service:009": "Bölge Temizliği",
|
||||
"service:009:property:001": "Oda Seçimi",
|
||||
"service:009:property:002": "Bölge Temizliği",
|
||||
"service:010": "Zamanlama",
|
||||
"service:010:property:001": "Zamanlayıcı",
|
||||
"service:010:property:002": "Temizlik Zamanı",
|
||||
"service:011": "Ses ve Bildirim",
|
||||
"service:011:property:001": "Ses Seviyesi",
|
||||
"service:011:property:002": "Sesli Bildirimler",
|
||||
"service:012": "Gelişmiş Ayarlar",
|
||||
"service:012:property:001": "Halı Modu",
|
||||
"service:012:property:002": "Tekrar Geçiş",
|
||||
"service:012:property:003": "Kenar Temizliği",
|
||||
"service:012:property:004": "Engel Algılama"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:090615-ktf:
|
||||
services:
|
||||
- '4'
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:759413-iez:
|
||||
properties:
|
||||
- '2.3'
|
||||
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:
|
||||
properties:
|
||||
- 9.*
|
||||
- 13.*
|
||||
- 15.*
|
||||
services:
|
||||
- '10'
|
||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-lyjpro:
|
||||
properties:
|
||||
- '3.2'
|
||||
urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01:
|
||||
properties:
|
||||
- '5.1'
|
||||
services:
|
||||
- '4'
|
||||
- '7'
|
||||
- '8'
|
||||
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:
|
||||
events:
|
||||
- '2.1'
|
||||
urn:miot-spec-v2:device:health-pot:0000A051:chunmi-a1:
|
||||
services:
|
||||
- '5'
|
||||
urn:miot-spec-v2:device:light:0000A001:philips-strip3:
|
||||
properties:
|
||||
- '2.2'
|
||||
services:
|
||||
- '1'
|
||||
- '3'
|
||||
urn:miot-spec-v2:device:light:0000A001:yeelink-color2:
|
||||
properties:
|
||||
- 3.*
|
||||
- '2.5'
|
||||
urn:miot-spec-v2:device:light:0000A001:yeelink-dnlight2:
|
||||
services:
|
||||
- '3'
|
||||
urn:miot-spec-v2:device:light:0000A001:yeelink-mbulb3:
|
||||
services:
|
||||
- '3'
|
||||
urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1:
|
||||
services:
|
||||
- '1'
|
||||
- '5'
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-l15a:
|
||||
properties:
|
||||
- '3.3'
|
||||
- '6.1'
|
||||
- '6.2'
|
||||
- '6.3'
|
||||
- '6.4'
|
||||
urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01:
|
||||
services:
|
||||
- '2'
|
||||
- '4'
|
||||
urn:miot-spec-v2:device:vacuum:0000A006:narwa-001:
|
||||
services:
|
||||
- '*'
|
||||
urn:miot-spec-v2:device:vacuum:0000A006:narwa-ax11:
|
||||
services:
|
||||
- '*'
|
||||
urn:miot-spec-v2:device:vacuum:0000A006:roidmi-v60:
|
||||
actions:
|
||||
- '2.3'
|
||||
@@ -0,0 +1,615 @@
|
||||
urn:miot-spec-v2:device:air-condition-outlet:0000A045:lumi-mcn04:1:
|
||||
prop.3.4:
|
||||
format: uint8
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:daikin-k2:1:
|
||||
prop.2.1:
|
||||
name: ac-on
|
||||
format: string
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
prop.2.2:
|
||||
name: ac-mode
|
||||
format: string
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
prop.3.1:
|
||||
name: ac-fan-level
|
||||
format: string
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:qdhkl-a42:1:
|
||||
prop.2.2:
|
||||
value-list:
|
||||
- value: 1
|
||||
description: Cool
|
||||
- value: 2
|
||||
description: Dry
|
||||
- value: 4
|
||||
description: Fan
|
||||
- value: 8
|
||||
description: Heat
|
||||
- value: 9
|
||||
description: Auto
|
||||
- value: 10
|
||||
description: Heat_cool
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c17:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c17:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c17:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:1:
|
||||
prop.8.6:
|
||||
name: power-consumption
|
||||
unit: kWh
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c35:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c35:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c35:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h09h00:4:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h40h00:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h43h00:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h43h00:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h43h00:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m16:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m16:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m16:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m16:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m16:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m16:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m16:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:5: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:7: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
prop.3.12:
|
||||
name: vertical-swing-left-up
|
||||
prop.3.13:
|
||||
name: vertical-swing-left-down
|
||||
prop.3.14:
|
||||
name: vertical-swing-right-up
|
||||
prop.3.15:
|
||||
name: vertical-swing-right-down
|
||||
prop.3.20:
|
||||
name: horizontal-swing-left
|
||||
prop.3.22:
|
||||
name: horizontal-swing-right
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1
|
||||
urn:miot-spec-v2:device:air-fresh:0000A012:daikin-k33:1:
|
||||
prop.2.1:
|
||||
name: ac-on
|
||||
format: string
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
prop.2.3:
|
||||
name: ac-mode
|
||||
format: string
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
prop.2.5:
|
||||
name: ac-fan-level
|
||||
format: string
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-cgd1st:1:
|
||||
prop.3.7:
|
||||
value-range:
|
||||
- -30
|
||||
- 100
|
||||
- 0.1
|
||||
urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-s1:1:
|
||||
prop.2.5:
|
||||
name: voc-density
|
||||
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ua1a:1: urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ua1a:3
|
||||
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ua1a:2: urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ua1a:3
|
||||
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ua1a:3:
|
||||
prop.3.5:
|
||||
expr: (src_value*6)
|
||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-lyjpro:1:
|
||||
prop.2.3:
|
||||
name: current-position-a
|
||||
prop.2.8:
|
||||
name: target-position-a
|
||||
prop.2.9:
|
||||
name: target-position-b
|
||||
prop.2.11:
|
||||
expr: (100-src_value)
|
||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1:
|
||||
prop.2.3:
|
||||
value-range:
|
||||
- 0
|
||||
- 1
|
||||
- 1
|
||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:2: urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1
|
||||
urn:miot-spec-v2:device:airer:0000A00D:mrbond-m1t:1:
|
||||
prop.2.3:
|
||||
name: current-position-a
|
||||
urn:miot-spec-v2:device:airer:0000A00D:mrbond-m33a:1:
|
||||
prop.2.3:
|
||||
name: current-position-a
|
||||
prop.2.11:
|
||||
name: current-position-b
|
||||
urn:miot-spec-v2:device:bath-heater:0000A028:mike-2:1:
|
||||
prop.3.1:
|
||||
name: mode-a
|
||||
prop.3.11:
|
||||
name: mode-b
|
||||
prop.3.12:
|
||||
name: mode-c
|
||||
urn:miot-spec-v2:device:bath-heater:0000A028:opple-acmoto:1:
|
||||
prop.5.2:
|
||||
value-list:
|
||||
- value: 1
|
||||
description: low
|
||||
- value: 128
|
||||
description: medium
|
||||
- value: 255
|
||||
description: high
|
||||
urn:miot-spec-v2:device:bath-heater:0000A028:xiaomi-s1:1:
|
||||
prop.4.4:
|
||||
name: fan-level-ventilation
|
||||
urn:miot-spec-v2:device:curtain:0000A00C:bjkcz-kczble:1:0000D031:
|
||||
prop.2.2:
|
||||
name: status-a
|
||||
urn:miot-spec-v2:device:dehumidifier:0000A02D:xiaomi-lite:1:
|
||||
prop.3.2:
|
||||
value-range:
|
||||
- -30
|
||||
- 100
|
||||
- 0.1
|
||||
urn:miot-spec-v2:device:electronic-valve:0000A0A7:lxzn-02:1:0000C833:
|
||||
prop.3.1:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 999999
|
||||
- 0.01
|
||||
expr: (src_value/100)
|
||||
prop.3.2:
|
||||
unit: mA
|
||||
prop.3.3:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 65535
|
||||
- 0.1
|
||||
expr: (src_value/10)
|
||||
prop.4.1:
|
||||
unit: kW
|
||||
urn:miot-spec-v2:device:electronic-valve:0000A0A7:sanmei-s1:1:
|
||||
prop.3.1:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 99999999
|
||||
- 0.01
|
||||
expr: round(src_value/100, 2)
|
||||
prop.3.2:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 16777216
|
||||
- 0.01
|
||||
expr: round(src_value/100, 2)
|
||||
prop.3.3:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 65535
|
||||
- 0.1
|
||||
expr: round(src_value/10, 1)
|
||||
urn:miot-spec-v2:device:electronic-valve:0000A0A7:ykcn-cbcs:1:0000C833:
|
||||
prop.3.1:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 99999999
|
||||
- 0.01
|
||||
expr: round(src_value/100, 2)
|
||||
prop.3.2:
|
||||
unit: mA
|
||||
prop.3.3:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 65535
|
||||
- 0.1
|
||||
expr: round(src_value/10, 1)
|
||||
prop.4.1:
|
||||
unit: kW
|
||||
urn:miot-spec-v2:device:fan:0000A005:dmaker-p33:1:
|
||||
prop.2.2:
|
||||
name: fan-level-a
|
||||
prop.2.6:
|
||||
name: fan-level
|
||||
access:
|
||||
- read
|
||||
- write
|
||||
- notify
|
||||
urn:miot-spec-v2:device:fan:0000A005:dmaker-p5:1:
|
||||
prop.2.4:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p43:1:
|
||||
prop.2.2:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p45:1:0000D062:
|
||||
prop.2.4:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p51:1:
|
||||
prop.2.2:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p69:1:0000D062:
|
||||
prop.2.4:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p70:1:0000D062:
|
||||
prop.2.4:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p76:1:0000D062:
|
||||
prop.2.4:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:fan:0000A005:zhimi-sa1:3:
|
||||
prop.2.2:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:fan:0000A005:zhimi-v3:3:
|
||||
prop.2.2:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:fan:0000A005:zhimi-za4:3:
|
||||
prop.2.2:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:gateway:0000A019:lumi-mcn001:1:
|
||||
prop.2.1:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
prop.2.2:
|
||||
icon: mdi:ip
|
||||
prop.2.3:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
prop.2.5:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1:
|
||||
prop.2.1:
|
||||
name: access-mode
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
prop.2.2:
|
||||
name: ip-address
|
||||
icon: mdi:ip
|
||||
prop.2.3:
|
||||
name: wifi-ssid
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:2: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1
|
||||
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:3: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1
|
||||
urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:1: urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3
|
||||
urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:2: urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3
|
||||
urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3:
|
||||
prop.3.1:
|
||||
name: on-ventilation
|
||||
urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:4: urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3
|
||||
urn:miot-spec-v2:device:humidifier:0000A00E:zhimi-ca4:2:
|
||||
prop.2.7:
|
||||
unit: percentage
|
||||
expr: round(src_value*0.83)
|
||||
urn:miot-spec-v2:device:kettle:0000A009:yunmi-r3:1:
|
||||
prop.3.1:
|
||||
unit: ppm
|
||||
urn:miot-spec-v2:device:light:0000A001:shhf-sfla10:1:
|
||||
prop.8.9:
|
||||
name: wind-reverse
|
||||
urn:miot-spec-v2:device:light:0000A001:shhf-sfla12:1:
|
||||
prop.8.11:
|
||||
name: on-a
|
||||
urn:miot-spec-v2:device:light:0000A001:shhf-sflt11:1:0000C802:
|
||||
prop.11.14:
|
||||
name: on-power
|
||||
urn:miot-spec-v2:device:magnet-sensor:0000A016:linp-m1:1:
|
||||
prop.2.1004:
|
||||
name: contact-state
|
||||
value-list:
|
||||
- value: 0
|
||||
description: open
|
||||
- value: 1
|
||||
description: closed
|
||||
expr: (src_value!=1)
|
||||
urn:miot-spec-v2:device:motion-sensor:0000A014:lumi-acn001:1:
|
||||
prop.3.2:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
unit: mV
|
||||
urn:miot-spec-v2:device:motion-sensor:0000A014:lumi-bmgl01:1:
|
||||
prop.2.2:
|
||||
value-list:
|
||||
- value: 0
|
||||
description: 0 Seconds
|
||||
- value: 2
|
||||
description: 2 Minutes
|
||||
- value: 5
|
||||
description: 5 Minutes
|
||||
urn:miot-spec-v2:device:motor-controller:0000A01D:adp-adswb4:1:0000C837:
|
||||
prop.2.1:
|
||||
name: motor-switch
|
||||
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:1: urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:2
|
||||
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:2:
|
||||
prop.2.5:
|
||||
name: people-number
|
||||
prop.2.8:
|
||||
name: people-number
|
||||
prop.2.11:
|
||||
name: people-number
|
||||
prop.2.14:
|
||||
name: people-number
|
||||
prop.2.17:
|
||||
name: people-number
|
||||
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:izq-24:2:0000C824:
|
||||
prop.2.6:
|
||||
unit: cm
|
||||
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:linp-hb01:2:0000C824:
|
||||
prop.3.3:
|
||||
unit: m
|
||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3
|
||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:2: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3
|
||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3:
|
||||
prop.5.1:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 65535
|
||||
- 0.01
|
||||
expr: round(src_value*6/1000000, 2)
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp1md:1:
|
||||
prop.2.2:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 65535
|
||||
- 0.01
|
||||
expr: round(src_value/1000, 2)
|
||||
prop.2.3:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 3000
|
||||
- 0.1
|
||||
expr: round(src_value/10, 1)
|
||||
prop.2.4:
|
||||
unit: mA
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:1: urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2:
|
||||
prop.2.3:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 3000
|
||||
- 0.1
|
||||
expr: round(src_value/10, 1)
|
||||
prop.2.4:
|
||||
unit: mA
|
||||
prop.3.2:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 65535
|
||||
- 0.1
|
||||
expr: round(src_value/10, 1)
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2d:1:
|
||||
prop.3.2:
|
||||
unit: mA
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp7pd:1:
|
||||
prop.11.1:
|
||||
unit: Wh
|
||||
value-range:
|
||||
- 0
|
||||
- 65535
|
||||
- 0.001
|
||||
expr: (src_value*1000)
|
||||
prop.11.4:
|
||||
value-range:
|
||||
- 0
|
||||
- 10000
|
||||
- 0.01
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1:
|
||||
prop.11.1:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 65535
|
||||
- 0.01
|
||||
expr: round(src_value/100, 2)
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:2: urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1
|
||||
urn:miot-spec-v2:device:outlet:0000A002:giot-v8icm:1:0000C816:
|
||||
prop.4.1:
|
||||
unit: mWh
|
||||
urn:miot-spec-v2:device:outlet:0000A002:qmi-psv3:1:0000C816:
|
||||
prop.3.3:
|
||||
unit: mV
|
||||
prop.3.4:
|
||||
unit: mA
|
||||
urn:miot-spec-v2:device:outlet:0000A002:yutai-fsov8m:1:0000C816:
|
||||
prop.4.1:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 429497
|
||||
- 0.01
|
||||
expr: round(src_value/10000, 2)
|
||||
urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:1:0000C816: urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816
|
||||
urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816:
|
||||
prop.3.1:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 350000
|
||||
- 0.01
|
||||
expr: round(src_value/100, 2)
|
||||
urn:miot-spec-v2:device:plant-monitor:0000A030:hhcc-v1:1:
|
||||
prop.2.1:
|
||||
name: soil-moisture
|
||||
icon: mdi:watering-can
|
||||
prop.2.2:
|
||||
name: soil-ec
|
||||
icon: mdi:sprout-outline
|
||||
unit: μS/cm
|
||||
urn:miot-spec-v2:device:relay:0000A03D:lumi-c2acn01:1:
|
||||
prop.4.1:
|
||||
unit: kWh
|
||||
urn:miot-spec-v2:device:router:0000A036:xiaomi-rd08:1:
|
||||
prop.2.1:
|
||||
name: download-speed
|
||||
icon: mdi:download
|
||||
unit: B/s
|
||||
prop.2.2:
|
||||
name: upload-speed
|
||||
icon: mdi:upload
|
||||
unit: B/s
|
||||
urn:miot-spec-v2:device:safe-box:0000A042:loock-v1:1:
|
||||
prop.5.1:
|
||||
name: contact-state
|
||||
expr: (src_value!=1)
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-l04m:2:
|
||||
prop.3.1:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-lx04:2:
|
||||
prop.3.1:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-lx06:2:
|
||||
prop.3.1:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-x08c:2:
|
||||
prop.2.1:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
urn:miot-spec-v2:device:speaker:0000A015:xiaomi-x08e:1:
|
||||
prop.3.1:
|
||||
name: playing-state-a
|
||||
urn:miot-spec-v2:device:switch:0000A003:090615-x1tpm:1:0000D042:
|
||||
prop.27.3:
|
||||
name: light-on
|
||||
prop.27.4:
|
||||
name: light-fan-on
|
||||
urn:miot-spec-v2:device:switch:0000A003:lxzn-cbcsmj:1:0000D00D:
|
||||
prop.3.1:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 99999999
|
||||
- 0.01
|
||||
expr: round(src_value/100, 2)
|
||||
prop.3.2:
|
||||
unit: mA
|
||||
prop.3.3:
|
||||
format: float
|
||||
value-range:
|
||||
- 0
|
||||
- 65535
|
||||
- 0.1
|
||||
expr: round(src_value/10, 1)
|
||||
urn:miot-spec-v2:device:thermostat:0000A031:suittc-wk168:1:
|
||||
prop.2.3:
|
||||
value-list:
|
||||
- value: 1
|
||||
description: '1'
|
||||
- value: 2
|
||||
description: '2'
|
||||
- value: 3
|
||||
description: '3'
|
||||
- value: 4
|
||||
description: '4'
|
||||
- value: 5
|
||||
description: '5'
|
||||
- value: 6
|
||||
description: '6'
|
||||
- value: 7
|
||||
description: '7'
|
||||
- value: 8
|
||||
description: '8'
|
||||
- value: 9
|
||||
description: '9'
|
||||
- value: 10
|
||||
description: '10'
|
||||
- value: 11
|
||||
description: '11'
|
||||
- value: 12
|
||||
description: '12'
|
||||
- value: 13
|
||||
description: '13'
|
||||
- value: 14
|
||||
description: '14'
|
||||
- value: 15
|
||||
description: '15'
|
||||
- value: 16
|
||||
description: '16'
|
||||
urn:miot-spec-v2:device:toothbrush:0000A07E:xiaomi-p001:1:
|
||||
prop.4.1041:
|
||||
unit: days
|
||||
urn:miot-spec-v2:device:water-purifier:0000A013:roswan-lte01:1:0000D05A:
|
||||
prop.4.1:
|
||||
unit: ppm
|
||||
prop.4.2:
|
||||
unit: ppm
|
||||
urn:miot-spec-v2:device:water-purifier:0000A013:xiaomi-s1200g:1:0000D05A:
|
||||
prop.3.2:
|
||||
unit: days
|
||||
prop.3.3:
|
||||
unit: days
|
||||
prop.3.4:
|
||||
unit: L
|
||||
prop.3.5:
|
||||
unit: L
|
||||
prop.5.2:
|
||||
unit: days
|
||||
prop.5.3:
|
||||
unit: days
|
||||
prop.5.4:
|
||||
unit: L
|
||||
prop.5.5:
|
||||
unit: L
|
||||
urn:miot-spec-v2:device:water-purifier:0000A013:xiaomi-s1200g:2:0000D05A: urn:miot-spec-v2:device:water-purifier:0000A013:xiaomi-s1200g:1:0000D05A
|
||||
urn:miot-spec-v2:device:water-purifier:0000A013:yunmi-s20:1:
|
||||
prop.4.1:
|
||||
unit: ppm
|
||||
prop.4.2:
|
||||
unit: ppm
|
||||
@@ -0,0 +1,716 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
Conversion rules of MIoT-Spec-V2 instance to Home Assistant entity.
|
||||
"""
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.components.sensor import SensorStateClass
|
||||
from homeassistant.components.event import EventDeviceClass
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
|
||||
from homeassistant.const import (CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
EntityCategory, LIGHT_LUX, UnitOfEnergy,
|
||||
UnitOfPower, UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential, UnitOfTemperature,
|
||||
UnitOfPressure, PERCENTAGE)
|
||||
|
||||
# pylint: disable=pointless-string-statement
|
||||
"""SPEC_DEVICE_TRANS_MAP
|
||||
{
|
||||
'<device instance name>':{
|
||||
'required':{
|
||||
'<service instance name>':{
|
||||
'required':{
|
||||
'properties': {
|
||||
'<property instance name>': set<property access: str>
|
||||
},
|
||||
'events': set<event instance name: str>,
|
||||
'actions': set<action instance name: str>
|
||||
},
|
||||
'optional':{
|
||||
'properties': set<property instance name: str>,
|
||||
'events': set<event instance name: str>,
|
||||
'actions': set<action instance name: str>
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional':{
|
||||
'<service instance name>':{
|
||||
'required':{
|
||||
'properties': {
|
||||
'<property instance name>': set<property access: str>
|
||||
},
|
||||
'events': set<event instance name: str>,
|
||||
'actions': set<action instance name: str>
|
||||
},
|
||||
'optional':{
|
||||
'properties': set<property instance name: str>,
|
||||
'events': set<event instance name: str>,
|
||||
'actions': set<action instance name: str>
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': str
|
||||
}
|
||||
}
|
||||
"""
|
||||
SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
'humidifier': {
|
||||
'required': {
|
||||
'humidifier': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'mode', 'target-humidity'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'environment': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'relative-humidity': {'read'}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'humidifier'
|
||||
},
|
||||
'dehumidifier': {
|
||||
'required': {
|
||||
'dehumidifier': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'mode', 'target-humidity'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'environment': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'relative-humidity': {'read'}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'dehumidifier'
|
||||
},
|
||||
'vacuum': {
|
||||
'required': {
|
||||
'vacuum': {
|
||||
'required': {
|
||||
'actions': {'start-sweep', 'stop-sweeping'},
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'status', 'fan-level'},
|
||||
'actions': {
|
||||
'pause-sweeping', 'continue-sweep', 'stop-and-gocharge'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'identify': {
|
||||
'required': {
|
||||
'actions': {'identify'}
|
||||
}
|
||||
},
|
||||
'battery': {
|
||||
'required': {
|
||||
'actions': {
|
||||
'start-charge'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'vacuum'
|
||||
},
|
||||
'air-conditioner': {
|
||||
'required': {
|
||||
'air-conditioner': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'},
|
||||
'mode': {'read', 'write'},
|
||||
'target-temperature': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'target-humidity'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'fan-control': {
|
||||
'required': {},
|
||||
'optional': {
|
||||
'properties': {
|
||||
'on', 'fan-level', 'horizontal-swing', 'vertical-swing'
|
||||
}
|
||||
}
|
||||
},
|
||||
'environment': {
|
||||
'required': {},
|
||||
'optional': {
|
||||
'properties': {'temperature', 'relative-humidity'}
|
||||
}
|
||||
},
|
||||
'air-condition-outlet-matching': {
|
||||
'required': {},
|
||||
'optional': {
|
||||
'properties': {'ac-state'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'air-conditioner'
|
||||
},
|
||||
'air-condition-outlet': 'air-conditioner',
|
||||
'thermostat': {
|
||||
'required': {
|
||||
'thermostat': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {
|
||||
'target-temperature', 'mode', 'fan-level', 'temperature'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'environment': {
|
||||
'required': {},
|
||||
'optional': {
|
||||
'properties': {'temperature', 'relative-humidity'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'thermostat'
|
||||
},
|
||||
'heater': {
|
||||
'required': {
|
||||
'heater': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'target-temperature', 'heat-level'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'environment': {
|
||||
'required': {},
|
||||
'optional': {
|
||||
'properties': {'temperature', 'relative-humidity'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'heater'
|
||||
},
|
||||
'bath-heater': {
|
||||
'required': {
|
||||
'ptc-bath-heater': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'mode': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'target-temperature', 'temperature'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'fan-control': {
|
||||
'required': {},
|
||||
'optional': {
|
||||
'properties': {
|
||||
'on', 'fan-level', 'horizontal-swing', 'vertical-swing'
|
||||
}
|
||||
}
|
||||
},
|
||||
'environment': {
|
||||
'required': {},
|
||||
'optional': {
|
||||
'properties': {'temperature'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'bath-heater',
|
||||
},
|
||||
'electric-blanket': {
|
||||
'required': {
|
||||
'electric-blanket': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'},
|
||||
'target-temperature': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'mode', 'temperature'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {},
|
||||
'entity': 'electric-blanket'
|
||||
},
|
||||
'speaker': {
|
||||
'required': {
|
||||
'speaker': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'volume': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'mute'}
|
||||
}
|
||||
},
|
||||
'play-control': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'playing-state': {'read'}
|
||||
},
|
||||
'actions': {'play'}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'play-loop-mode'},
|
||||
'actions': {'pause', 'stop', 'next', 'previous'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {},
|
||||
'entity': 'wifi-speaker'
|
||||
},
|
||||
'television': {
|
||||
'required': {
|
||||
'speaker': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'volume': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'mute'}
|
||||
}
|
||||
},
|
||||
'television': {
|
||||
'required': {
|
||||
'actions': {'turn-off'}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'input-control'},
|
||||
'actions': {'turn-on'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'play-control': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'playing-state': {'read'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'play-loop-mode'},
|
||||
'actions': {'play', 'pause', 'stop', 'next', 'previous'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'television'
|
||||
},
|
||||
'tv-box':{
|
||||
'required': {
|
||||
'speaker': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'volume': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'mute'}
|
||||
}
|
||||
},
|
||||
'tv-box': {
|
||||
'required': {
|
||||
'actions': {'turn-off'}
|
||||
},
|
||||
'optional': {
|
||||
'actions': {'turn-on'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'play-control': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'playing-state': {'read'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'play-loop-mode'},
|
||||
'actions': {'play', 'pause', 'stop', 'next', 'previous'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'television'
|
||||
},
|
||||
'watch': {
|
||||
'required': {
|
||||
'watch': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'longitude': {'read'},
|
||||
'latitude': {'read'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'area-id'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'battery': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'battery-level': {'read'}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'device_tracker'
|
||||
}
|
||||
}
|
||||
|
||||
"""SPEC_SERVICE_TRANS_MAP
|
||||
{
|
||||
'<service instance name>':{
|
||||
'required':{
|
||||
'properties': {
|
||||
'<property instance name>': set<property access: str>
|
||||
},
|
||||
'events': set<event instance name: str>,
|
||||
'actions': set<action instance name: str>
|
||||
},
|
||||
'optional':{
|
||||
'properties': set<property instance name: str>,
|
||||
'events': set<event instance name: str>,
|
||||
'actions': set<action instance name: str>
|
||||
},
|
||||
'entity': str,
|
||||
'entity_category'?: str
|
||||
}
|
||||
}
|
||||
"""
|
||||
SPEC_SERVICE_TRANS_MAP: dict = {
|
||||
'light': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'mode', 'brightness', 'color', 'color-temperature'}
|
||||
},
|
||||
'entity': 'light'
|
||||
},
|
||||
'ambient-light': 'light',
|
||||
'night-light': 'light',
|
||||
'white-light': 'light',
|
||||
'indicator-light': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {
|
||||
'mode',
|
||||
'brightness',
|
||||
}
|
||||
},
|
||||
'entity': 'light',
|
||||
'entity_category': EntityCategory.CONFIG
|
||||
},
|
||||
'fan': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'},
|
||||
'fan-level': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'mode', 'horizontal-swing', 'wind-reverse'}
|
||||
},
|
||||
'entity': 'fan'
|
||||
},
|
||||
'fan-control': 'fan',
|
||||
'ceiling-fan': 'fan',
|
||||
'air-fresh': 'fan',
|
||||
'air-purifier': 'fan',
|
||||
'water-heater': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'temperature', 'target-temperature', 'mode'}
|
||||
},
|
||||
'entity': 'water_heater'
|
||||
},
|
||||
'curtain': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'motor-control': {'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'status', 'current-position', 'target-position'}
|
||||
},
|
||||
'entity': 'cover'
|
||||
},
|
||||
'window-opener': 'curtain',
|
||||
'motor-controller': 'curtain',
|
||||
'airer': 'curtain',
|
||||
'air-conditioner': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'},
|
||||
'mode': {'read', 'write'},
|
||||
'target-temperature': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'target-humidity'}
|
||||
},
|
||||
'entity': 'air-conditioner'
|
||||
}
|
||||
}
|
||||
|
||||
"""SPEC_PROP_TRANS_MAP
|
||||
{
|
||||
'entities':{
|
||||
'<entity name>':{
|
||||
'format': set<str>,
|
||||
'access': set<str>
|
||||
}
|
||||
},
|
||||
'properties': {
|
||||
'<property instance name>':{
|
||||
'device_class': str,
|
||||
'entity': str,
|
||||
'state_class'?: str,
|
||||
'unit_of_measurement'?: str
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
SPEC_PROP_TRANS_MAP: dict = {
|
||||
'entities': {
|
||||
'sensor': {
|
||||
'format': {'int', 'float'},
|
||||
'access': {'read'}
|
||||
},
|
||||
'binary_sensor': {
|
||||
'format': {'bool', 'int'},
|
||||
'access': {'read'}
|
||||
},
|
||||
'switch': {
|
||||
'format': {'bool'},
|
||||
'access': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'properties': {
|
||||
'submersion-state': {
|
||||
'device_class': BinarySensorDeviceClass.MOISTURE,
|
||||
'entity': 'binary_sensor'
|
||||
},
|
||||
'contact-state': {
|
||||
'device_class': BinarySensorDeviceClass.DOOR,
|
||||
'entity': 'binary_sensor'
|
||||
},
|
||||
'occupancy-status': {
|
||||
'device_class': BinarySensorDeviceClass.OCCUPANCY,
|
||||
'entity': 'binary_sensor',
|
||||
},
|
||||
'temperature': {
|
||||
'device_class': SensorDeviceClass.TEMPERATURE,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': UnitOfTemperature.CELSIUS
|
||||
},
|
||||
'relative-humidity': {
|
||||
'device_class': SensorDeviceClass.HUMIDITY,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': PERCENTAGE
|
||||
},
|
||||
'air-quality-index': {
|
||||
'device_class': SensorDeviceClass.AQI,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
'pm2.5-density': {
|
||||
'device_class': SensorDeviceClass.PM25,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||
},
|
||||
'pm10-density': {
|
||||
'device_class': SensorDeviceClass.PM10,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||
},
|
||||
'pm1': {
|
||||
'device_class': SensorDeviceClass.PM1,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||
},
|
||||
'atmospheric-pressure': {
|
||||
'device_class': SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': UnitOfPressure.PA
|
||||
},
|
||||
'tvoc-density': {
|
||||
'device_class': SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT
|
||||
},
|
||||
'voc-density': {
|
||||
'device_class': SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT
|
||||
},
|
||||
'battery-level': {
|
||||
'device_class': SensorDeviceClass.BATTERY,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': PERCENTAGE
|
||||
},
|
||||
'voltage': {
|
||||
'device_class': SensorDeviceClass.VOLTAGE,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': UnitOfElectricPotential.VOLT
|
||||
},
|
||||
'electric-current': {
|
||||
'device_class': SensorDeviceClass.CURRENT,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': UnitOfElectricCurrent.AMPERE
|
||||
},
|
||||
'illumination': {
|
||||
'device_class': SensorDeviceClass.ILLUMINANCE,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': LIGHT_LUX
|
||||
},
|
||||
'no-one-determine-time': {
|
||||
'device_class': SensorDeviceClass.DURATION,
|
||||
'entity': 'sensor'
|
||||
},
|
||||
'has-someone-duration': 'no-one-determine-time',
|
||||
'no-one-duration': 'no-one-determine-time',
|
||||
'electric-power': {
|
||||
'device_class': SensorDeviceClass.POWER,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': UnitOfPower.WATT
|
||||
},
|
||||
'surge-power': {
|
||||
'device_class': SensorDeviceClass.POWER,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': UnitOfPower.WATT
|
||||
},
|
||||
'power-consumption': {
|
||||
'device_class': SensorDeviceClass.ENERGY,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.TOTAL_INCREASING,
|
||||
'unit_of_measurement': UnitOfEnergy.KILO_WATT_HOUR
|
||||
},
|
||||
'power': {
|
||||
'device_class': SensorDeviceClass.POWER,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': UnitOfPower.WATT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"""SPEC_EVENT_TRANS_MAP
|
||||
{
|
||||
'<event instance name>': str
|
||||
}
|
||||
"""
|
||||
SPEC_EVENT_TRANS_MAP: dict[str, str] = {
|
||||
'click': EventDeviceClass.BUTTON,
|
||||
'double-click': EventDeviceClass.BUTTON,
|
||||
'long-press': EventDeviceClass.BUTTON,
|
||||
'motion-detected': EventDeviceClass.MOTION,
|
||||
'no-motion': EventDeviceClass.MOTION,
|
||||
'doorbell-ring': EventDeviceClass.DOORBELL
|
||||
}
|
||||
|
||||
SPEC_ACTION_TRANS_MAP = {}
|
||||
@@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
MIoT redirect web pages.
|
||||
"""
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
web_template = ''
|
||||
|
||||
|
||||
def _load_page_template():
|
||||
path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
'resource/oauth_redirect_page.html')
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
global web_template
|
||||
web_template = f.read()
|
||||
|
||||
|
||||
async def oauth_redirect_page(
|
||||
title: str, content: str, button: str, success: bool
|
||||
) -> str:
|
||||
"""Return oauth redirect page."""
|
||||
if web_template == '':
|
||||
await asyncio.get_running_loop().run_in_executor(
|
||||
None, _load_page_template)
|
||||
web_page = web_template.replace('TITLE_PLACEHOLDER', title)
|
||||
web_page = web_page.replace('CONTENT_PLACEHOLDER', content)
|
||||
web_page = web_page.replace('BUTTON_PLACEHOLDER', button)
|
||||
web_page = web_page.replace(
|
||||
'STATUS_PLACEHOLDER', 'true' if success else 'false')
|
||||
return web_page
|
||||
Reference in New Issue
Block a user