diff --git a/custom_components/xiaomi_miot_raw/__init__.py b/custom_components/xiaomi_miot_raw/__init__.py index c22f44f..a78bcf4 100644 --- a/custom_components/xiaomi_miot_raw/__init__.py +++ b/custom_components/xiaomi_miot_raw/__init__.py @@ -195,6 +195,7 @@ async def async_remove_entry(hass, entry): async def _setup_micloud_entry(hass, config_entry): """Thanks to @AlexxIT """ data: dict = config_entry.data.copy() + hass.data[DOMAIN]['micloud_config'] = data server_location = data.get('server_location') or 'cn' session = aiohttp_client.async_create_clientsession(hass, auto_cleanup=False) diff --git a/custom_components/xiaomi_miot_raw/basic_dev_class.py b/custom_components/xiaomi_miot_raw/basic_dev_class.py index ed58f42..350ddf1 100644 --- a/custom_components/xiaomi_miot_raw/basic_dev_class.py +++ b/custom_components/xiaomi_miot_raw/basic_dev_class.py @@ -2,7 +2,7 @@ import json import time import logging -from datetime import timedelta +from datetime import timedelta, datetime from functools import partial from dataclasses import dataclass @@ -62,7 +62,9 @@ class GenericMiotDevice(Entity): """通用 MiOT 设备""" - + + lastAutoUpdateAccountTime=datetime.now()-timedelta(seconds=3600) + def __init__(self, device, config, device_info, hass = None, mi_type = None): """Initialize the entity.""" @@ -506,7 +508,56 @@ def pre_process_data(key, value): statedict[key] = pre_process_data(key, dict1[value['siid']][value['piid']]) else: - pass + # auth err + # 小米账号登录信息失效 + # 自动重新登录,间隔3600秒 + lostTime=(datetime.now()-GenericMiotDevice.lastAutoUpdateAccountTime).total_seconds() + _LOGGER.debug("miaccount auth err:lostTime:%d" % (lostTime)) + if (lostTime>3600): + _LOGGER.warning("auto update mi_account token") + GenericMiotDevice.lastAutoUpdateAccountTime=datetime.now() + + config=self.hass.data[DOMAIN]['micloud_config'] + #_LOGGER.debug(f"self.hass.data[DOMAIN]['config']: {json.dumps (config)}") + if 'username' in config: + cloud=self._cloud_instance + resp = await cloud.login(config['username'], + config['password']) + if resp == (0, None): + #让新 token 实时生效 + for item in self.hass.data[DOMAIN]['cloud_instance_list']: + mc = item['cloud_instance'] + mc.login_by_credientals( + cloud.auth["user_id"], + cloud.auth['service_token'], + cloud.auth['ssecurity'] + ) + + if self.hass.config_entries.async_entries(DOMAIN): #更新每个设备的token + _LOGGER.warning("Found existing config entries") + for entry in self.hass.config_entries.async_entries(DOMAIN): + if ( + entry.data.get("update_from_cloud") + ): + _LOGGER.warning("Updating existing entry") + update_from_cloud=entry.data.get("update_from_cloud") + update_from_cloud_new={ + "did": update_from_cloud["did"], + "userId": update_from_cloud["userId"], + "serviceToken": cloud.auth['service_token'], + "ssecurity": cloud.auth['ssecurity'], + "server_location": update_from_cloud["server_location"] + } + entry_data_new=dict(entry.data) + entry_data_new.update({"update_from_cloud":update_from_cloud_new}) + entry_id = entry.entry_id + self.hass.data[DOMAIN]['configs'][entry_id] = entry_data_new + self.hass.config_entries.async_update_entry( #保存新token到文件 + entry, + data=entry_data_new, + ) + else: + _LOGGER.error("config.data no username") self._fail_count = 0 self._state_attrs.update(statedict) @@ -681,8 +732,7 @@ def _handle_coordinator_update(self) -> None: def _handle_platform_specific_attrs(self): pass - @asyncio.coroutine - def async_service_handler(self, service): + async def async_service_handler(self, service): """Map services to methods on XiaomiMiioDevice.""" method = SERVICE_TO_METHOD.get(service.service) params = { @@ -702,11 +752,11 @@ def async_service_handler(self, service): update_tasks = [] for device in devices: - yield from getattr(device, method["method"])(**params) + yield getattr(device, method["method"])(**params) update_tasks.append(device.async_update_ha_state(True)) if update_tasks: - yield from asyncio.wait(update_tasks, loop=self.hass.loop) + yield asyncio.wait(update_tasks, loop=self.hass.loop) class ToggleableMiotDevice(GenericMiotDevice, ToggleEntity): def __init__(self, device, config, device_info, hass = None, mi_type = None): diff --git a/custom_components/xiaomi_miot_raw/binary_sensor.py b/custom_components/xiaomi_miot_raw/binary_sensor.py index 8745735..5bfaf04 100644 --- a/custom_components/xiaomi_miot_raw/binary_sensor.py +++ b/custom_components/xiaomi_miot_raw/binary_sensor.py @@ -58,7 +58,6 @@ } # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): hass.data[DOMAIN]['add_handler'].setdefault(TYPE, {}) if 'config_entry' in config: diff --git a/custom_components/xiaomi_miot_raw/camera.py b/custom_components/xiaomi_miot_raw/camera.py new file mode 100644 index 0000000..8847554 --- /dev/null +++ b/custom_components/xiaomi_miot_raw/camera.py @@ -0,0 +1,151 @@ +"""Platform for camera integration.""" +import asyncio +import logging +from functools import partial +import collections + +from datetime import timedelta +import json +from collections import OrderedDict +import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from homeassistant.components.camera import ( + SUPPORT_ON_OFF, SUPPORT_STREAM, + PLATFORM_SCHEMA, + Camera +) +from homeassistant.const import * +from homeassistant.exceptions import PlatformNotReady +from homeassistant.util import color +from miio.exceptions import DeviceException +from .deps.miio_new import MiotDevice +import miio +import copy + +from .basic_dev_class import ( + GenericMiotDevice, + ToggleableMiotDevice, + MiotSubDevice, + MiotSubToggleableDevice, + MiotIRDevice, +) +from . import async_generic_setup_platform +from .deps.const import ( + DOMAIN, + ATTR_SYSSTATUS, + CONF_UPDATE_INSTANT, + CONF_MAPPING, + CONF_CONTROL_PARAMS, + CONF_CLOUD, + CONF_MODEL, + ATTR_STATE_VALUE, + ATTR_MODEL, + ATTR_FIRMWARE_VERSION, + ATTR_HARDWARE_VERSION, + SCHEMA, + MAP, + DUMMY_IP, + DUMMY_TOKEN, +) + +TYPE = 'camera' + +_LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(seconds=10) +DEFAULT_NAME = "Generic MIoT " + TYPE +DATA_KEY = TYPE + '.' + DOMAIN + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + SCHEMA +) + +# pylint: disable=unused-argument +@asyncio.coroutine +async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + await async_generic_setup_platform( + hass, + config, + async_add_devices, + discovery_info, + TYPE, + {'default': MiotCamera} + ) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + config = copy.copy(hass.data[DOMAIN]['configs'].get(config_entry.entry_id, dict(config_entry.data))) + await async_setup_platform(hass, config, async_add_entities) + + +class MiotCamera(GenericMiotDevice, Camera): + def enable_motion_detection(self) -> None: + pass + + def disable_motion_detection(self) -> None: + pass + + def __init__(self, device, config, device_info, hass, main_mi_type): + self._device: miio.chuangmi_camera.ChuangmiCamera = None # Just for type hint + self._state: bool = None + GenericMiotDevice.__init__(self, device, config, device_info, hass, main_mi_type) + Camera.__init__(self) + self.register_callback(self.update_callback) + + @property + def should_poll(self): + return True + + def update_callback(self): + device_status = self.get_devicestatus() + # sleep will return [{'sysstatus': 'sleep'}] + # otherwise will return all other status + self._state = (len(device_status) > 1) + _LOGGER.debug(f"camera.update_callback result: {self._state}") + + def get_devicestatus(self): + return self._device.send('get_devicestatus', { + 'alarmsensitivity': "", + 'cameraprompt': "", + 'flip': "", + 'infraredlight': "", + 'ledstatus': "", + 'recordtype': "", + 'wakeuplevel': "", + }) + + @property + def supported_features(self) -> int: + return SUPPORT_ON_OFF + + @property + def is_recording(self) -> bool: + return self._state + + @property + def is_streaming(self) -> bool: + return False + + @property + def is_on(self) -> bool: + return True + + async def async_turn_on(self) -> None: + await self.async_do_turn_on(True) + + async def async_turn_off(self) -> None: + await self.async_do_turn_on(False) + + async def async_do_turn_on(self, new_status) -> None: + _LOGGER.info(f"Start camera.async_do_turn_on( {new_status} )") + + if new_status: + cmd = "normal" + else: + cmd = "sleep" + result = self._device.send("set_" + ATTR_SYSSTATUS, [cmd]) + if result != ['ok']: + _LOGGER.warning("result for send {}, {}".format(cmd, result)) + return + + self._state = new_status + self.schedule_update_ha_state() diff --git a/custom_components/xiaomi_miot_raw/climate.py b/custom_components/xiaomi_miot_raw/climate.py index 4eb764b..77271e2 100644 --- a/custom_components/xiaomi_miot_raw/climate.py +++ b/custom_components/xiaomi_miot_raw/climate.py @@ -75,7 +75,6 @@ SCAN_INTERVAL = timedelta(seconds=10) # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): hass.data[DOMAIN]['add_handler'].setdefault(TYPE, {}) if 'config_entry' in config: diff --git a/custom_components/xiaomi_miot_raw/config_flow.py b/custom_components/xiaomi_miot_raw/config_flow.py index fb53bf8..19ebb6d 100644 --- a/custom_components/xiaomi_miot_raw/config_flow.py +++ b/custom_components/xiaomi_miot_raw/config_flow.py @@ -1,6 +1,7 @@ import json import re +import logging import asyncio from types import coroutine from typing import OrderedDict @@ -40,6 +41,8 @@ from .deps.special_devices import SPECIAL_DEVICES from .deps.xiaomi_cloud_new import MiCloud +_LOGGER = logging.getLogger(__name__) + SERVERS = { 'cn': "China", 'de': "Europe", @@ -735,6 +738,40 @@ async def async_step_update_xiaomi_account(self, user_input=None, error=None, hi if resp == (0, None): self._all_config.update(user_input) self._all_config.update(cloud.auth) + + #让新 token 实时生效 + for item in self.hass.data[DOMAIN]['cloud_instance_list']: + mc = item['cloud_instance'] + mc.login_by_credientals( + cloud.auth["user_id"], + cloud.auth['service_token'], + cloud.auth['ssecurity'] + ) + + if self.hass.config_entries.async_entries(DOMAIN): #更新每个设备的token + _LOGGER.info("Found existing config entries") + for entry in self.hass.config_entries.async_entries(DOMAIN): + if ( + entry.data.get("update_from_cloud") + ): + _LOGGER.info("Updating existing entry") + update_from_cloud=entry.data.get("update_from_cloud") + update_from_cloud_new={ + "did": update_from_cloud["did"], + "userId": update_from_cloud["userId"], + "serviceToken": cloud.auth['service_token'], + "ssecurity": cloud.auth['ssecurity'], + "server_location": update_from_cloud["server_location"] + } + entry_data_new=dict(entry.data) + entry_data_new.update({"update_from_cloud":update_from_cloud_new}) + entry_id = entry.entry_id + self.hass.data[DOMAIN]['configs'][entry_id] = entry_data_new + self.hass.config_entries.async_update_entry( #保存新token到文件 + entry, + data=entry_data_new, + ) + self._steps.pop(0) return await self._steps[0] elif resp[0] == -2: diff --git a/custom_components/xiaomi_miot_raw/cover.py b/custom_components/xiaomi_miot_raw/cover.py index cb9fd77..aeced94 100644 --- a/custom_components/xiaomi_miot_raw/cover.py +++ b/custom_components/xiaomi_miot_raw/cover.py @@ -60,7 +60,6 @@ SCAN_INTERVAL = timedelta(seconds=2) # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): hass.data[DOMAIN]['add_handler'].setdefault(TYPE, {}) if 'config_entry' in config: @@ -121,7 +120,7 @@ def is_closed(self): """ Most of Xiaomi covers does not report position as 0 when they are fully closed. It can be 0, 1, 2... So we consider it closed when it is <= 3. The _current_position has been converted so it is always percentage. (#227) """ - return self.current_cover_position <= 3 + return int(self.current_cover_position) <= 3 @property def is_closing(self): diff --git a/custom_components/xiaomi_miot_raw/deps/const.py b/custom_components/xiaomi_miot_raw/deps/const.py index 87d6a86..5721a16 100644 --- a/custom_components/xiaomi_miot_raw/deps/const.py +++ b/custom_components/xiaomi_miot_raw/deps/const.py @@ -15,9 +15,11 @@ ATTR_MODEL = "model" ATTR_FIRMWARE_VERSION = "firmware_version" ATTR_HARDWARE_VERSION = "hardware_version" +ATTR_SYSSTATUS = "sysstatus" DOMAIN = 'xiaomi_miot_raw' SUPPORTED_DOMAINS = [ + "camera", "sensor", "switch", "light", @@ -85,6 +87,9 @@ } MAP = { + "camera": { + "camera", + }, "sensor": { "air_fryer", "air_monitor", @@ -177,6 +182,7 @@ "fan_control", "dryer", "toilet", + "function", "settings", "settings_2", "air_fresh_heater", @@ -224,17 +230,17 @@ UNIT_MAPPING = { "percentage" : PERCENTAGE , # 百分比 - "celsius" : TEMP_CELSIUS , # 摄氏度 + "celsius" : UnitOfTemperature.CELSIUS , # 摄氏度 "seconds" : "秒" , # 秒 "minutes" : "分钟" , # 分 "hours" : "小时" , # 小时 "days" : "天" , # 天 - "kelvin" : TEMP_KELVIN , # 开氏温标 + "kelvin" : UnitOfTemperature.KELVIN , # 开氏温标 "pascal" : "Pa" , # 帕斯卡(大气压强单位) "arcdegrees" : "rad" , # 弧度(角度单位) "rgb" : "RGB" , # RGB(颜色) - "watt" : POWER_WATT , # 瓦特(功率) - "litre" : VOLUME_LITERS , # 升 + "watt" : UnitOfPower.WATT , # 瓦特(功率) + "litre" : UnitOfVolume.LITERS , # 升 "ppm" : CONCENTRATION_PARTS_PER_MILLION , # ppm浓度 "lux" : LIGHT_LUX , # 勒克斯(照度) "mg/m3" : CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER , # 毫克每立方米 diff --git a/custom_components/xiaomi_miot_raw/deps/special_devices.py b/custom_components/xiaomi_miot_raw/deps/special_devices.py index e646e07..d6103a5 100644 --- a/custom_components/xiaomi_miot_raw/deps/special_devices.py +++ b/custom_components/xiaomi_miot_raw/deps/special_devices.py @@ -1,4 +1,9 @@ SPECIAL_DEVICES={ + "chuangmi.camera.xiaobai":{ + "device_type": ['camera'], + "mapping": {"camera": {"switch_status": {"siid": 1, "piid": 1}}}, + "params": {"camera": {"switch_status": {"power_on": True, "power_off": False}, "main": True}} + }, "chuangmi.plug.212a01":{ "device_type": ['switch','sensor'], "mapping": {"switch": {"switch_status": {"siid": 2, "piid": 1}, "temperature": {"siid": 2, "piid": 6}, "working_time": {"siid": 2, "piid": 7}}, "power_consumption": {"power_consumption": {"siid": 5, "piid": 1}, "electric_current": {"siid": 5, "piid": 2}, "voltage": {"siid": 5, "piid": 3}, "electric_power": {"siid": 5, "piid": 6}}}, diff --git a/custom_components/xiaomi_miot_raw/deps/xiaomi_cloud_new.py b/custom_components/xiaomi_miot_raw/deps/xiaomi_cloud_new.py index 7d54bba..594df0e 100644 --- a/custom_components/xiaomi_miot_raw/deps/xiaomi_cloud_new.py +++ b/custom_components/xiaomi_miot_raw/deps/xiaomi_cloud_new.py @@ -78,6 +78,7 @@ async def login(self, username: str, password: str): 'ssecurity': data['ssecurity'], 'service_token': token } + _LOGGER.info(f"user_id:{data['userId']},service_token: {token}") return (0, None) except Exception as e: @@ -160,6 +161,7 @@ async def get_devices(self, server: str): except Exception: loc = "en_US" try: + _LOGGER.info(f"user_id:{self.auth['user_id']},token:{self.auth['service_token']}") r = await self.session.post(baseurl + url, cookies={ 'userId': self.auth['user_id'], 'serviceToken': self.auth['service_token'], @@ -171,7 +173,7 @@ async def get_devices(self, server: str): 'signature': signature, '_nonce': nonce, 'data': data - }, timeout=5) + }, timeout=30) resp = await r.json(content_type=None) assert resp['code'] == 0, resp @@ -203,6 +205,7 @@ async def request_miot_api(self, api, data = None, server: str = None): 'cache-control': "no-cache", } try: + _LOGGER.info(f"user_id:{self.auth['user_id']},token:{self.auth['service_token']}") r = await self.session.post(url, cookies={ 'userId': self.auth['user_id'], 'serviceToken': self.auth['service_token'], @@ -213,7 +216,7 @@ async def request_miot_api(self, api, data = None, server: str = None): 'signature': signature, '_nonce': nonce, 'data': data - }, timeout=5) + }, timeout=30) self._fail_count = 0 resp = await r.json(content_type=None) @@ -230,6 +233,7 @@ async def request_miot_api(self, api, data = None, server: str = None): return resp except (asyncio.TimeoutError, ClientConnectorError) as ex: + _LOGGER.exception("request_miot_api:") if self._fail_count < 3 and api == "/miotspec/prop/get": self._fail_count += 1 _LOGGER.info(f"Error while requesting MIoT api {api} : {ex} ({self._fail_count})") diff --git a/custom_components/xiaomi_miot_raw/fan.py b/custom_components/xiaomi_miot_raw/fan.py index 00da7f8..4025d43 100644 --- a/custom_components/xiaomi_miot_raw/fan.py +++ b/custom_components/xiaomi_miot_raw/fan.py @@ -64,7 +64,6 @@ SUPPORT_PRESET_MODE = 8 # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): hass.data[DOMAIN]['add_handler'].setdefault(TYPE, {}) if 'config_entry' in config: @@ -148,9 +147,9 @@ def preset_mode(self): """Return the current speed.""" return self._mode - # @property - # def percentage(self): - # return None + @property + def percentage(self): + return self.speed @property def speed_count(self): @@ -226,9 +225,9 @@ async def async_set_direction(self, direction: str) -> None: raise TypeError(f"Your fan does not support {direction}.") await self.set_property_new(self._did_prefix + "motor_control", self._ctrl_params['motor_control'][d]) - # async def async_set_percentage(self, percentage: int) -> None: - # """Set the speed percentage of the fan.""" - # pass + async def async_set_percentage(self, percentage: int) -> None: + """Set the speed percentage of the fan.""" + await self.async_set_speed(percentage) def _handle_platform_specific_attrs(self): super()._handle_platform_specific_attrs() diff --git a/custom_components/xiaomi_miot_raw/humidifier.py b/custom_components/xiaomi_miot_raw/humidifier.py index 6d9874f..0ebe4e8 100644 --- a/custom_components/xiaomi_miot_raw/humidifier.py +++ b/custom_components/xiaomi_miot_raw/humidifier.py @@ -58,7 +58,6 @@ SCAN_INTERVAL = timedelta(seconds=10) # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): await async_generic_setup_platform( hass, diff --git a/custom_components/xiaomi_miot_raw/light.py b/custom_components/xiaomi_miot_raw/light.py index 2c0a85e..c9df4c9 100644 --- a/custom_components/xiaomi_miot_raw/light.py +++ b/custom_components/xiaomi_miot_raw/light.py @@ -59,7 +59,6 @@ ) # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): await async_generic_setup_platform( hass, diff --git a/custom_components/xiaomi_miot_raw/lock.py b/custom_components/xiaomi_miot_raw/lock.py index 3cf8c9a..4b8c5bc 100644 --- a/custom_components/xiaomi_miot_raw/lock.py +++ b/custom_components/xiaomi_miot_raw/lock.py @@ -52,7 +52,6 @@ ) # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the light from config.""" if DATA_KEY not in hass.data: diff --git a/custom_components/xiaomi_miot_raw/media_player.py b/custom_components/xiaomi_miot_raw/media_player.py index 5d37fcb..07c3065 100644 --- a/custom_components/xiaomi_miot_raw/media_player.py +++ b/custom_components/xiaomi_miot_raw/media_player.py @@ -6,6 +6,7 @@ import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util import voluptuous as vol +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.components import media_player from homeassistant.components.media_player import * from homeassistant.components.media_player.const import * @@ -40,6 +41,8 @@ ) SCAN_INTERVAL = timedelta(seconds=10) +VOLUME_LEVEL_VALUE = "volume_level_value" +IS_VOLUME_MUTED = "is_volume_muted" @dataclass @@ -49,7 +52,6 @@ class dev_info: firmware_version : str hardware_version : str -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): await async_generic_setup_platform( hass, @@ -199,7 +201,8 @@ def _handle_platform_specific_attrs(self): 'volume', False, self._ctrl_params['volume']['value_range'] ) -class MiotIRTV(MiotIRDevice, MediaPlayerEntity): + +class MiotIRTV(MiotIRDevice, MediaPlayerEntity, RestoreEntity): def __init__(self, config, hass, did_prefix): super().__init__(config, hass, did_prefix) self._player_state = STATE_PLAYING @@ -212,6 +215,7 @@ def supported_features(self): """Return the supported features.""" return ( SUPPORT_VOLUME_SET + | SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF @@ -243,31 +247,57 @@ async def async_turn_off(self): result = await self.async_send_ir_command('turn_off') if result: self._player_state = STATE_OFF + self._attr_is_volume_muted = False self.async_write_ha_state() async def async_volume_up(self): - await self.async_send_ir_command('volume_up') + result = await self.async_send_ir_command('volume_up') + if result: + self._volume_level = min(self._volume_level + 0.01, 1) + if self._attr_is_volume_muted: + self._attr_is_volume_muted = False + self.async_write_ha_state() + async def async_volume_down(self): - await self.async_send_ir_command('volume_down') + result = await self.async_send_ir_command('volume_down') + if result: + self._volume_level = max(self._volume_level - 0.01, 0) + self.async_write_ha_state() async def async_set_volume_level(self, volume): - if volume < 0.5: - result = await self.async_volume_down() - elif volume > 0.5: - result = await self.async_volume_up() + if volume < self._volume_level: + await self.async_volume_down() + elif volume > self.volume_level: + await self.async_volume_up() else: return - if result: - self._volume_level = 0.5 - self.async_write_ha_state() - - async def async_mute_volume(self): + async def async_mute_volume(self, mute): await self.async_send_ir_command('mute_on') + self._attr_is_volume_muted = mute + self.async_write_ha_state() async def async_media_next_track(self): await self.async_send_ir_command('channel_up') async def async_media_previous_track(self): await self.async_send_ir_command('channel_down') + + async def async_added_to_hass(self): + await super().async_added_to_hass() + last_state = await self.async_get_last_state() + if not last_state: + return + self._player_state = last_state.state + if VOLUME_LEVEL_VALUE in last_state.attributes: + self._volume_level = last_state.attributes[VOLUME_LEVEL_VALUE] + if IS_VOLUME_MUTED in last_state.attributes: + self._attr_is_volume_muted = last_state.attributes[VOLUME_LEVEL_VALUE] + + @property + def extra_state_attributes(self): + attributes = {} + attributes[VOLUME_LEVEL_VALUE] = self._volume_level + attributes[IS_VOLUME_MUTED] = self._attr_is_volume_muted + return attributes diff --git a/custom_components/xiaomi_miot_raw/number.py b/custom_components/xiaomi_miot_raw/number.py index 657d6fc..81657f3 100644 --- a/custom_components/xiaomi_miot_raw/number.py +++ b/custom_components/xiaomi_miot_raw/number.py @@ -48,7 +48,6 @@ SCHEMA ) # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): hass.data[DOMAIN]['add_handler'].setdefault(TYPE, {}) if 'config_entry' in config: @@ -85,14 +84,14 @@ def state(self): return None @property - def value(self): + def native_value(self): if self.state is not None: try: return float(self.state) except Exception as ex: _LOGGER.error(ex) - async def async_set_value(self, value): + async def async_set_native_value(self, value): result = await self._parent_device.set_property_new(self._full_did, value) if result: self._state_attrs[self._full_did] = value @@ -100,16 +99,16 @@ async def async_set_value(self, value): self._skip_update = True @property - def min_value(self): + def native_min_value(self): """Return the minimum value.""" return self._value_range[0] @property - def max_value(self): + def native_max_value(self): """Return the maximum value.""" return self._value_range[1] @property - def step(self): + def native_step(self): """Return the increment/decrement step.""" - return self._value_range[2] + return self._value_range[2] \ No newline at end of file diff --git a/custom_components/xiaomi_miot_raw/select.py b/custom_components/xiaomi_miot_raw/select.py index d4a3f2e..fb23124 100644 --- a/custom_components/xiaomi_miot_raw/select.py +++ b/custom_components/xiaomi_miot_raw/select.py @@ -57,7 +57,6 @@ SCAN_INTERVAL = timedelta(seconds=10) # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): hass.data[DOMAIN]['add_handler'].setdefault(TYPE, {}) if 'config_entry' in config: diff --git a/custom_components/xiaomi_miot_raw/sensor.py b/custom_components/xiaomi_miot_raw/sensor.py index a9b482f..6378b0e 100644 --- a/custom_components/xiaomi_miot_raw/sensor.py +++ b/custom_components/xiaomi_miot_raw/sensor.py @@ -87,7 +87,6 @@ "carbon_dioxide" : ["co2_density"], } # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the sensor from config.""" hass.data.setdefault(DATA_KEY, {}) diff --git a/custom_components/xiaomi_miot_raw/switch.py b/custom_components/xiaomi_miot_raw/switch.py index 4425f4a..05c4712 100644 --- a/custom_components/xiaomi_miot_raw/switch.py +++ b/custom_components/xiaomi_miot_raw/switch.py @@ -49,7 +49,6 @@ SCHEMA ) # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): hass.data[DOMAIN]['add_handler'].setdefault(TYPE, {}) if 'config_entry' in config: diff --git a/custom_components/xiaomi_miot_raw/translations/ca.json b/custom_components/xiaomi_miot_raw/translations/ca.json new file mode 100644 index 0000000..84ee287 --- /dev/null +++ b/custom_components/xiaomi_miot_raw/translations/ca.json @@ -0,0 +1,184 @@ +{ + "config": { + "abort": { + "already_configured": "Ja has configurat aquest dispositiu!", + "success": "Inici de sessió correcte! Espereu un moment i actualitzeu per veure els dispositius afegits. Per afegir-ne més, feu clic a `CONFIGURACIÓ` al vostre compte o a `AFEGEIX INTEGRACIÓ - Xiaomi MIoT`." + }, + "create_entry": { + "default": "per defecte" + }, + "error": { + "unexpected_error": "Error inesperat. Consulteu els registres per obtenir més informació", + "cannot_connect": "No es pot connectar", + "connection_failed": "Xarxa inaccessible", + "value_error": "Valor incorrecte", + "bad_params": "Paràmetres erronis", + "no_local_access": "Aquest dispositiu no admet l'accés local. S'ha habilitat l'accés al núvol. Feu clic a \"ENVIA\" de nou", + "no_connect_warning": "No s'ha pogut descobrir el teu dispositiu. Si es tracta d'un altaveu Xiaomi, és normal i cal introduir el model manualment a dalt. Si no, si us plau, comproveu la vostra entrada.", + "wrong_pwd": "Contrasenya incorrecta", + "need_auth": "Es requereix autenticació de dos factors. Obriu l'enllaç anterior per finalitzar l'autenticació i torneu a iniciar sessió.", + "account_tips": "Això és per facilitar l'addició de dispositius. Després d'iniciar la sessió, torneu aquí per seleccionar els dispositius que voleu afegir." + }, + "step": { + "user": { + "data": { + "name": "Nom", + "host": "IP", + "token": "testimoni" + }, + "description": "descripció", + "title": "Seleccioneu Operació" + }, + "xiaomi_account": { + "title": "Inicieu sessió al compte Xiaomi", + "description": "{hint}Introduïu les vostres credencials de Xiaomi.", + "data": { + "username": "Correu electrònic/identificador de Xiaomi", + "password": "Contrasenya", + "server_location": "Ubicació del servidor" + } + }, + "localinfo": { + "data": { + "name": "Nom", + "host": "IP", + "token": "testimoni" + }, + "description": "L'accés local requereix la informació anterior", + "title": "Configura" + }, + "devinfo": { + "title": "Informació del dispositiu", + "data": { + "devtype": "Tipus de dispositiu", + "mapping": "mapatge", + "params": "paràmetres", + "cloud_read": "Llegeix l'estat des del núvol", + "cloud_write": "Controla el dispositiu des del núvol" + }, + "description": "{device_info}" + }, + "cloudinfo": { + "title": "Credencials del compte", + "data": { + "did": "DID", + "userId": "ID d'usuari", + "serviceToken": "Testimoni de servei", + "ssecurity": "seguretat" + }, + "description": "L'accés al núvol requereix la informació anterior" + }, + "select_devices": { + "data": { + "devices": "Llista de dispositius" + }, + "description": "Tens {dev_count} dispositius! Seleccioneu els dispositius que voleu afegir a continuació. Encara en podeu seleccionar més en el futur.", + "title": "Seleccioneu Dispositius" + } + }, + "title": "MIoT" + }, + "system_health": { + "info": { + "logged_in": "Inicieu sessió al compte Xiaomi", + "can_reach_micloud_server": "Troba un servidor Xiaomi Cloud", + "account_devices_count": "Dispositius al compte", + "added_devices": "Dispositius afegits", + "accounts_count": "Comptes Xiaomi" + } + }, + "options": { + "abort": { + "no_configurable_options": "No hi ha opcions disponibles." + }, + "error": { + "plz_agree": "Si us plau, llegiu atentament", + "cannot_connect": "No es pot connectar", + "connection_failed": "La connexió a la xarxa ha fallat", + "wrong_pwd": "Contrasenya incorrecta", + "need_auth": "Es requereix autenticació de dos factors. Obriu l'enllaç anterior per finalitzar l'autenticació i torneu a iniciar sessió.", + "dev_readapt_failed": "No s'ha pogut reconfigurar el dispositiu. Els paràmetres no s'han modificat.", + "invalid_json": "JSON no vàlid" + }, + "step": { + "init": { + "data": { + "async_step_update_xiaomi_account": "Actualitzeu la credencial del compte Xiaomi i la ubicació del servidor", + "async_step_select_devices": "Afegir dispositius per lots (no seleccioneu amb el de dalt)", + "async_step_light_and_lock": "Mostra o amaga l'interruptor per a la llum indicadora i el bloqueig infantil", + "async_step_climate": "Connecteu un sensor al climatització per a la temperatura actual", + "async_step_cover": "Posició inversa de la coberta", + "async_step_re_adapt": "Reconfigura el dispositiu", + "async_step_edit_mpprm": "Edita mapes i paràmetres", + "async_step_edit_iptoken": "Actualitza la IP i el testimoni" + }, + "description": "descripció", + "title": "" + }, + "update_xiaomi_account": { + "title": "Actualitza les credencials del compte Xiaomi", + "description": "{hint}Actualitza la contrasenya per a \"{username}\".", + "data": { + "username": "Correu electrònic/identificador de Xiaomi\n", + "password": "Contrasenya", + "server_location": "Ubicació del servidor" + } + }, + "sensor": { + "description": "Si està marcat, es crearà una entitat sensor per a cada atribut.", + "title": "Opcions del sensor" + }, + "cover": { + "data": { + "reverse_position_percentage": "Percentatge de posició inversa" + }, + "description": "Invertir el percentatge de posició pot solucionar alguns problemes.", + "title": "Opcions de coberta" + }, + "binary_sensor": { + "data": { + "reverse": "Estat del sensor binari invers" + }, + "description": "Activeu aquesta funció per invertir els estats de tots els sensors binaris que pertanyen a aquest dispositiu. També podeu editar els mapes i els paràmetres per revertir només alguns d'ells.", + "title": "Opcions del sensor binari" + }, + "climate": { + "data": { + "current_temp_source": "Identificador d'entitat del sensor de temperatura ambiental" + }, + "description": "Introduïu aquí un ID d'entitat del sensor de temperatura per utilitzar-lo com a sensor de temperatura actual.", + "title": "Opcions de climatització" + }, + "light_and_lock": { + "data": { + "show_indicator_light": "Activa l'interruptor de la llum indicadora", + "show_physical_controls_locked": "Activa l'interruptor per al bloqueig infantil" + }, + "description": "descripció", + "title": "Llum indicador / Opcions de bloqueig per a nens" + }, + "select_devices": { + "data": { + "devices": "Llistat de dispositius" + }, + "description": "Seleccioneu els dispositius que voleu afegir. Després de l'enviament, els dispositius s'afegiran en segon pla. Actualitza per veure'ls.", + "title": "Dispositius d'addició per lots" + }, + "edit_mpprm": { + "data": { + "mapping": "mapatge", + "params": "paràmetres" + }, + "description": "Si no sabeu què són, manteniu els predeterminats. Si el dispositiu s'ha reconfigurat, a continuació es mostra la darrera configuració.", + "title": "Edita mapes i paràmetres" + }, + "edit_iptoken": { + "data": { + "host": "IP", + "token": "testimoni" + }, + "title": "Actualitza la IP i el testimoni" + } + } + } +} diff --git a/custom_components/xiaomi_miot_raw/translations/pt-BR.json b/custom_components/xiaomi_miot_raw/translations/pt-BR.json new file mode 100644 index 0000000..0f02c64 --- /dev/null +++ b/custom_components/xiaomi_miot_raw/translations/pt-BR.json @@ -0,0 +1,186 @@ +{ + "config": { + "abort": { + "already_configured": "Você já configurou este dispositivo!", + "success": "Login bem sucedido! Aguarde um momento e atualize para ver os dispositivos adicionados.\nPara adicionar mais, clique em 'CONFIGURAÇÃO' em sua conta ou em 'ADICIONAR INTEGRAÇÃO' - `Xiaomi MIoT`." + }, + "create_entry": { + "default": "" + }, + "error": { + "unexpected_error": "Erro inesperado. Veja os registros para detalhes", + "cannot_connect": "Não pode conectar", + "connection_failed": "Rede inacessível", + "value_error": "Valor errado", + "bad_params": "Parâmetros ruins", + "no_local_access": "O acesso local não é compatível com este dispositivo. O acesso à nuvem foi ativado. Clique em 'ENVIAR' novamente.", + "no_connect_warning": "Falha ao descobrir seu dispositivo. Se for um Xiaomi Speaker, é normal e você precisa inserir seu modelo manualmente acima. Caso contrário, verifique sua entrada.", + "wrong_pwd": "Senha incorreta", + "need_auth": "Autenticação de dois fatores necessária. Abra o link acima para concluir a autenticação e faça login novamente.", + "account_tips": "Isso é para facilitar a adição de dispositivos. \nDepois de fazer login, volte aqui para selecionar os dispositivos a serem adicionados." + }, + "step": { + "user": { + "data": { + "name": "Nome", + "host": "IP", + "token": "token" + }, + "description": "", + "title": "Selecionar Operação" + }, + "xiaomi_account": { + "title": "Login da conta Xiaomi", + "description": "{hint}Insira suas credenciais da Xiaomi.", + "data": { + "username": "E-mail/Xiaomi ID", + "password": "Senha", + "server_location": "Localização do servidor" + } + }, + "localinfo": { + "data": { + "name": "Nome", + "host": "IP", + "token": "token" + }, + "description": "O acesso local requer as informações acima", + "title": "Configurar" + }, + "devinfo": { + "title": "Informação do dispositivo", + "data": { + "devtype": "Tipos de dispositivo(s)", + "mapping": "mapeamento", + "params": "parâmetros", + "cloud_read": "Leia o status da nuvem", + "cloud_write": "Controle o dispositivo da nuvem" + + }, + "description": "{device_info}" + }, + "cloudinfo": { + "title": "Credenciais da conta", + "data": { + "did": "fez", + "userId": "Id de usuário", + "serviceToken": "Token do serviço", + "ssecurity": "segurança" + }, + "description": "O acesso à nuvem requer as informações acima" + }, + "select_devices": { + "data": { + "devices": "Lista de dispositivos" + }, + "description": "Você tem {dev_count} dispositivos! Selecione os dispositivos a serem adicionados abaixo.\nVocê ainda pode selecionar mais no futuro.", + "title": "Selecionar dispositivos" + } + }, + "title": "MIoT" + }, + "system_health": { + "info": { + "logged_in": "Logado na conta Xiaomi", + "can_reach_micloud_server": "Alcance o Xiaomi Cloud Server", + "added_devices": "Dispositivos adicionados", + "accounts_count": "Contas Xiaomi" + } + }, + "options": { + "abort": { + "no_configurable_options": "Nenhuma opção disponível." + }, + "error": { + "plz_agree": "Por favor, leia atentamente", + "cannot_connect": "Não pode se conectar", + "connection_failed": "Falha na conexão de rede", + "wrong_pwd": "Senha incorreta", + "need_auth": "Autenticação de dois fatores necessária. Abra o link acima para concluir a autenticação e faça login novamente.", + "dev_readapt_failed": "Falha ao readaptar o dispositivo. Os parâmetros não foram alterados.", + "invalid_json": "JSON inválido" + }, + "step": { + "init": { + "data": { + "async_step_update_xiaomi_account": "Atualizar a credencial da conta Xiaomi e a localização do servidor", + "async_step_select_devices": "Adição de lote de dispositivos (não selecione com o acima)", + "async_step_light_and_lock": "Mostrar ou ocultar o interruptor para luz indicadora e bloqueio para crianças", + "async_step_climate": "Anexar um sensor ao climate para temperatura atual", + "async_step_cover": "Posição reversa da cortina", + "async_step_re_adapt": "Readaptar dispositivo", + "async_step_edit_mpprm": "Editar mapeamento e parâmetros", + "async_step_edit_iptoken": "Atualizar IP e Token" + }, + "description": "", + "title": "Você gostaria de..." + }, + "update_xiaomi_account": { + "title": "Atualizar a credencial da conta Xiaomi", + "description": "{hint}Atualizar senha para “{username}”.", + "data": { + "username": "E-mail/Xiaomi ID", + "password": "Senha", + "server_location": "Localização do servidor" + } + }, + "sensor": { + "data": { + }, + "description": "Se marcado, haverá uma entidade de sensor para cada atributo.", + "title": "Opções de sensor" + }, + "cover": { + "data": { + "reverse_position_percentage": "Reverter posição da porcentagem" + }, + "description": "A reversão da posição da porcentagem pode corrigir alguns problemas.", + "title": "Opções de cortina" + }, + "binary_sensor": { + "data": { + "reverse": "Estado do sensor binário reverso" + }, + "description": "Ative este recurso para reverter os estados de todos os sensores binários que pertencem a este dispositivo.\nVocê também pode editar mapeamentos e parâmetros para reverter apenas alguns deles.", + "title": "Opções de sensores binários" + }, + "climate": { + "data": { + "current_temp_source": "ID da entidade do sensor de temperatura ambiente" + }, + "description": "Insira um ID de entidade do sensor de temperatura aqui para usá-lo como o sensor de temperatura atual.", + "title": "Opções de climate" + }, + "light_and_lock": { + "data": { + "show_indicator_light": "Ativar interruptor para luz indicadora", + "show_physical_controls_locked": "Ativar interruptor para bloqueio infantil" + }, + "description": "", + "title": "Luz Indicadora / Opções de Bloqueio Infantil" + }, + "select_devices": { + "data": { + "devices": "Lista de dispositivos" + }, + "description": "Selecione os dispositivos a serem adicionados.\n\nApós o envio, os dispositivos serão adicionados em segundo plano. Atualize para vê-los.", + "title": "Adição de lote de dispositivos" + }, + "edit_mpprm": { + "data": { + "mapping": "mapeamento", + "params": "parâmetros" + }, + "description": "Se você não sabe quais são, mantenha-os como padrão.\nSe o dispositivo foi readaptado, a configuração mais recente é mostrada abaixo.", + "title": "Editar mapeamento e parâmetros" + }, + "edit_iptoken": { + "data": { + "host": "IP", + "token": "token" + }, + "title": "Atualizar IP e Token" + } + } + } +} \ No newline at end of file diff --git a/custom_components/xiaomi_miot_raw/translations/zh-Hant.json b/custom_components/xiaomi_miot_raw/translations/zh-Hant.json index 82751ca..240e225 100644 --- a/custom_components/xiaomi_miot_raw/translations/zh-Hant.json +++ b/custom_components/xiaomi_miot_raw/translations/zh-Hant.json @@ -34,7 +34,8 @@ "description": "{hint}請輸入小米帳號與密碼。", "data": { "username": "電子郵件/小米 ID", - "password": "密碼" + "password": "密碼", + "server_location": "伺服器位置" } }, "localinfo": { @@ -89,21 +90,41 @@ }, "options": { "abort": { - "no_configurable_options": "沒有可設定的選項", - "no_configurable_account": "小米帳號沒有可進行設定的選項。\n如需退出登入,請刪除此設定項即可。刪除後不影響已添加的裝置。\n如需更新裝置列表,請點選「重新載入」。" + "no_configurable_options": "沒有可設定的選項" }, "error": { - "plz_agree": "請仔細閱讀說明" + "plz_agree": "請仔細閱讀說明", + "cannot_connect": "無法連線", + "connection_failed": "網路連線失敗", + "wrong_pwd": "密碼錯誤", + "need_auth": "需要進行兩步驟認證。請開啟上方連結、跟隨頁面指示完成後、重新登入。", + "dev_readapt_failed": "裝置重新設定失敗、參數未變更。", + "invalid_json": "JSON 格式無效" }, "step": { - "account": { + "init": { "data": { - "server_location": "裝置所在伺服器", - "batch_add": "單次新增多個裝置" + "async_step_update_xiaomi_account": "更新小米帳號憑證與伺服器位置", + "async_step_select_devices": "單次增加多個裝置(請不要與上一項一起選擇)", + "async_step_light_and_lock": "顯示/隱藏指示燈或兒童鎖開關", + "async_step_climate": "為溫控器指定外部溫濕度感測器", + "async_step_cover": "反轉窗簾上下捲動方向", + "async_step_re_adapt": "重新設定裝置", + "async_step_edit_mpprm": "修改 mapping 與設定參數", + "async_step_edit_iptoken": "更新裝置 IP 與權杖 Token" }, - "description": "可選擇裝置所在的伺服器。\n如需退出登入,請點選「刪除」。刪除後不影響已添加的裝置。\n如需更新裝置列表,請點選「重新載入」。", - "title": "帳號設定" + "description": "", + "title": "欲進行……" }, + "update_xiaomi_account": { + "title": "更新小米帳號資訊", + "description": "{hint}为小米帳號“{username}”更新密碼與伺服器位置。", + "data": { + "username": "電子郵件/小米 ID/手機號碼", + "password": "密碼", + "server_location": "伺服器位置" + } + }, "sensor": { "data": { }, @@ -139,12 +160,20 @@ "description": "請選擇所要新增的裝置。\n\n點選送出後,裝置將於背景進行新增。請重新更新以查看新增的裝置。", "title": "單次新增多個裝置" }, - "batch_agreement": { + "edit_mpprm": { "data": { - "iagree": "我已詳細閱讀並了解" + "mapping": "mapping", + "params": "參數" + }, + "description": "如果不確定以下參數設定,請維持預設值即可。\n如果剛剛點選過“重新設定”、下方顯示之參數即為最新設定內容。", + "title": "編輯 Mapping 與參數" + }, + "edit_iptoken": { + "data": { + "host": "IP", + "token": "Token" }, - "description": "接下来,可以一次同時選擇多個裝置進行新增。\n於點選送出後,裝置將於背景進行新增。請稍候一分鐘後、更新頁面即可查看新增裝置。\n假如新增的某個裝置無法正常工作。請先進行刪除、然後透過手動新增整合的方式進行新增。\n透過此方式新增的裝置,都為 **雲端接入**,需要網際網路連線才能正常工作。\n假如裝置支援本地端,請勿透過此方式進行新增。請以手動新增整合的方式進行。", - "title": "請詳細閱讀以下說明" + "title": "更新裝置 IP 與權杖 Token" } } } diff --git a/custom_components/xiaomi_miot_raw/vacuum.py b/custom_components/xiaomi_miot_raw/vacuum.py index 9524d3b..545f5a7 100644 --- a/custom_components/xiaomi_miot_raw/vacuum.py +++ b/custom_components/xiaomi_miot_raw/vacuum.py @@ -87,7 +87,6 @@ SCAN_INTERVAL = timedelta(seconds=10) # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): await async_generic_setup_platform( hass, diff --git a/custom_components/xiaomi_miot_raw/water_heater.py b/custom_components/xiaomi_miot_raw/water_heater.py index a9e7581..2252a4a 100644 --- a/custom_components/xiaomi_miot_raw/water_heater.py +++ b/custom_components/xiaomi_miot_raw/water_heater.py @@ -62,7 +62,6 @@ SCAN_INTERVAL = timedelta(seconds=10) # pylint: disable=unused-argument -@asyncio.coroutine async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): await async_generic_setup_platform( hass,