From 012d65c149b88178f3992a1fe30cccd1eb52bfbd Mon Sep 17 00:00:00 2001 From: Kaew Date: Wed, 8 May 2024 21:57:04 +0700 Subject: [PATCH] Add UserId/Passwod as authentication configuration (#16) --- custom_components/lifesmart/__init__.py | 81 ++++--- custom_components/lifesmart/binary_sensor.py | 17 +- custom_components/lifesmart/climate.py | 54 ++--- custom_components/lifesmart/config_flow.py | 207 ++++++++++++------ custom_components/lifesmart/const.py | 7 + custom_components/lifesmart/cover.py | 6 +- .../lifesmart/lifesmart_client.py | 77 +++++-- custom_components/lifesmart/light.py | 18 +- custom_components/lifesmart/sensor.py | 24 +- .../lifesmart/translations/en.json | 24 +- 10 files changed, 326 insertions(+), 189 deletions(-) diff --git a/custom_components/lifesmart/__init__.py b/custom_components/lifesmart/__init__.py index 207f26a..7b63b42 100644 --- a/custom_components/lifesmart/__init__.py +++ b/custom_components/lifesmart/__init__.py @@ -1,34 +1,24 @@ -"""lifesmart by @ikaew""" -from typing import Final -from homeassistant.components.climate import FAN_HIGH, FAN_LOW, FAN_MEDIUM -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_URL, STATE_OFF, STATE_ON, Platform -from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.helpers.typing import ConfigType -from homeassistant.util.dt import utcnow -from homeassistant.helpers.event import async_track_point_in_utc_time -from homeassistant.helpers.entity import DeviceInfo, Entity -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers import discovery -from homeassistant.core import HomeAssistant, callback -from homeassistant.components import climate -from homeassistant.const import ( - CONF_FRIENDLY_NAME, -) -import subprocess -from unittest import case -import urllib.request -import json -import time +"""lifesmart by @ikaew.""" +import asyncio import datetime import hashlib +import json import logging -import threading -from .lifesmart_client import LifeSmartClient -import websocket -import asyncio import struct +import subprocess +import sys +import threading +import time +from typing import Final +from unittest import case +import urllib.request + import aiohttp +import voluptuous as vol +import websocket + +from homeassistant.components import climate +from homeassistant.components.climate import FAN_HIGH, FAN_LOW, FAN_MEDIUM from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, @@ -38,7 +28,25 @@ ATTR_RGB_COLOR, ATTR_RGBW_COLOR, ) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_FRIENDLY_NAME, + CONF_REGION, + CONF_URL, + STATE_OFF, + STATE_ON, + Platform, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.event import async_track_point_in_utc_time +from homeassistant.helpers.typing import ConfigType import homeassistant.util.color as color_util +from homeassistant.util.dt import utcnow + from .const import ( AI_TYPES, BINARY_SENSOR_TYPES, @@ -47,8 +55,10 @@ CONF_AI_INCLUDE_ITEMS, CONF_EXCLUDE_AGTS, CONF_EXCLUDE_ITEMS, + CONF_LIFESMART_APPKEY, CONF_LIFESMART_APPTOKEN, CONF_LIFESMART_USERID, + CONF_LIFESMART_USERPASSWORD, CONF_LIFESMART_USERTOKEN, COVER_TYPES, DEVICE_ID_KEY, @@ -56,7 +66,6 @@ DEVICE_TYPE_KEY, DEVICE_WITHOUT_IDXNAME, DOMAIN, - CONF_LIFESMART_APPKEY, EV_SENSOR_TYPES, GAS_SENSOR_TYPES, GENERIC_CONTROLLER_TYPES, @@ -78,9 +87,7 @@ SUPPORTED_SWTICH_TYPES, UPDATE_LISTENER, ) - -import voluptuous as vol -import sys +from .lifesmart_client import LifeSmartClient sys.setrecursionlimit(100000) @@ -93,9 +100,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): app_key = config_entry.data.get(CONF_LIFESMART_APPKEY) app_token = config_entry.data.get(CONF_LIFESMART_APPTOKEN) - user_token = config_entry.data.get(CONF_LIFESMART_USERTOKEN) user_id = config_entry.data.get(CONF_LIFESMART_USERID) - baseurl = config_entry.data.get(CONF_URL) + user_password = config_entry.data.get(CONF_LIFESMART_USERPASSWORD) + region = config_entry.data.get(CONF_REGION) exclude_devices = config_entry.data.get(CONF_EXCLUDE_ITEMS) exclude_hubs = config_entry.data.get(CONF_EXCLUDE_AGTS) ai_include_hubs = config_entry.data.get(CONF_AI_INCLUDE_AGTS) @@ -115,13 +122,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): update_listener = config_entry.add_update_listener(_async_update_listener) lifesmart_client = LifeSmartClient( - baseurl, + region, app_key, app_token, - user_token, user_id, + user_password, ) + await lifesmart_client.login_async() + devices = await lifesmart_client.get_all_device_async() hass.data[DOMAIN][config_entry.entry_id] = { @@ -195,9 +204,9 @@ async def data_update_handler(msg): attrs = hass.states.get(entity_id).attributes hass.states.set(entity_id, data["val"], attrs) elif device_type in SPOT_TYPES or device_type in LIGHT_SWITCH_TYPES: - #attrs = dict(hass.states.get(entity_id).attributes) + # attrs = dict(hass.states.get(entity_id).attributes) _LOGGER.debug("websocket_light_msg: %s ", str(msg)) - #_LOGGER.debug("websocket_light_attrs: %s", str(attrs)) + # _LOGGER.debug("websocket_light_attrs: %s", str(attrs)) value = data["val"] idx = sub_device_key diff --git a/custom_components/lifesmart/binary_sensor.py b/custom_components/lifesmart/binary_sensor.py index 08a919c..f3f6eed 100644 --- a/custom_components/lifesmart/binary_sensor.py +++ b/custom_components/lifesmart/binary_sensor.py @@ -1,10 +1,16 @@ """Support for LifeSmart binary sensors.""" -import datetime import logging + +from homeassistant.components.binary_sensor import ( + ENTITY_ID_FORMAT, + BinarySensorDeviceClass, + BinarySensorEntity, +) from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers.dispatcher import async_dispatcher_connect - from homeassistant.helpers.entity import DeviceInfo + +from . import LifeSmartDevice, generate_entity_id from .const import ( BINARY_SENSOR_TYPES, DEVICE_DATA_KEY, @@ -20,13 +26,6 @@ MANUFACTURER, MOTION_SENSOR_TYPES, ) -from homeassistant.components.binary_sensor import ( - BinarySensorDeviceClass, - BinarySensorEntity, - ENTITY_ID_FORMAT, -) - -from . import LifeSmartDevice, generate_entity_id _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/lifesmart/climate.py b/custom_components/lifesmart/climate.py index 3c3e52f..ae2a739 100644 --- a/custom_components/lifesmart/climate.py +++ b/custom_components/lifesmart/climate.py @@ -1,35 +1,35 @@ """Support for the LifeSmart climate devices.""" import logging import time + from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateEntity from homeassistant.components.climate.const import ( - ClimateEntityFeature, - HVACMode, FAN_HIGH, FAN_LOW, FAN_MEDIUM, + ClimateEntityFeature, + HVACMode, ) - -from homeassistant.const import ( - UnitOfTemperature, - PRECISION_WHOLE, -) +from homeassistant.const import PRECISION_WHOLE, UnitOfTemperature from . import LifeSmartDevice _LOGGER = logging.getLogger(__name__) DEVICE_TYPE = "climate" -LIFESMART_STATE_LIST = [HVACMode.OFF, - HVACMode.AUTO, - HVACMode.FAN_ONLY, - HVACMode.COOL, - HVACMode.HEAT, - HVACMode.DRY, - ] +LIFESMART_STATE_LIST = [ + HVACMode.OFF, + HVACMode.AUTO, + HVACMode.FAN_ONLY, + HVACMode.COOL, + HVACMode.HEAT, + HVACMode.DRY, +] -LIFESMART_STATE_LIST2 = [HVACMode.OFF, - HVACMode.HEAT,] +LIFESMART_STATE_LIST2 = [ + HVACMode.OFF, + HVACMode.HEAT, +] FAN_MODES = [FAN_LOW, FAN_MEDIUM, FAN_HIGH] GET_FAN_SPEED = {FAN_LOW: 15, FAN_MEDIUM: 45, FAN_HIGH: 76} @@ -162,22 +162,22 @@ async def async_set_hvac_mode(self, hvac_mode): await super().async_lifesmart_epset( "0xCE", LIFESMART_STATE_LIST.index(hvac_mode), "MODE" ) + elif hvac_mode == HVACMode.OFF: + await super().async_lifesmart_epset("0x80", 0, "P1") + time.sleep(1) + await super().async_lifesmart_epset("0x80", 0, "P2") + return + elif await super().async_lifesmart_epset("0x81", 1, "P1") == 0: + time.sleep(2) else: - if hvac_mode == HVACMode.OFF: - await super().async_lifesmart_epset("0x80", 0, "P1") - time.sleep(1) - await super().async_lifesmart_epset("0x80", 0, "P2") - return - else: - if await super().async_lifesmart_epset("0x81", 1, "P1") == 0: - time.sleep(2) - else: - return + return @property def supported_features(self): """Return the list of supported features.""" if self._devtype in AIR_TYPES: - return ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE + return ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE + ) else: return ClimateEntityFeature.TARGET_TEMPERATURE diff --git a/custom_components/lifesmart/config_flow.py b/custom_components/lifesmart/config_flow.py index e8e609d..9c4023a 100644 --- a/custom_components/lifesmart/config_flow.py +++ b/custom_components/lifesmart/config_flow.py @@ -1,43 +1,44 @@ +import logging from typing import Any + import voluptuous as vol -from .lifesmart_client import LifeSmartClient -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant import config_entries, exceptions -from homeassistant.const import ( - CONF_NAME, - CONF_URL, -) - -import logging +from homeassistant.const import CONF_NAME, CONF_REGION +from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.selector import selector from .const import ( CONF_AI_INCLUDE_AGTS, CONF_AI_INCLUDE_ITEMS, CONF_EXCLUDE_AGTS, CONF_EXCLUDE_ITEMS, + CONF_LIFESMART_APPKEY, CONF_LIFESMART_APPTOKEN, CONF_LIFESMART_USERID, - CONF_LIFESMART_USERTOKEN, + CONF_LIFESMART_USERPASSWORD, DOMAIN, - CONF_LIFESMART_APPKEY, + LIFESMART_REGION_OPTIONS, ) +from .lifesmart_client import LifeSmartClient _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema( - { - vol.Required(CONF_LIFESMART_APPKEY): str, - vol.Required(CONF_LIFESMART_APPTOKEN): str, - vol.Required(CONF_LIFESMART_USERTOKEN): str, - vol.Required(CONF_LIFESMART_USERID): str, - vol.Required(CONF_URL): str, - vol.Optional(CONF_EXCLUDE_ITEMS): str, - vol.Optional(CONF_EXCLUDE_AGTS): str, - vol.Optional(CONF_AI_INCLUDE_AGTS): str, - vol.Optional(CONF_AI_INCLUDE_ITEMS): str, - } -) +DATA_SCHEMA = { + vol.Required(CONF_LIFESMART_APPKEY): str, + vol.Required(CONF_LIFESMART_APPTOKEN): str, + vol.Required(CONF_LIFESMART_USERID): str, + vol.Required(CONF_LIFESMART_USERPASSWORD): str, + # vol.Required(CONF_REGION): str, + vol.Optional(CONF_EXCLUDE_ITEMS): str, + vol.Optional(CONF_EXCLUDE_AGTS): str, + vol.Optional(CONF_AI_INCLUDE_AGTS): str, + vol.Optional(CONF_AI_INCLUDE_ITEMS): str, +} + +DATA_SCHEMA[CONF_REGION] = selector(LIFESMART_REGION_OPTIONS) async def validate_input(hass, data): @@ -45,36 +46,38 @@ async def validate_input(hass, data): app_key = data[CONF_LIFESMART_APPKEY] app_token = data[CONF_LIFESMART_APPTOKEN] - user_token = data[CONF_LIFESMART_USERTOKEN] + # user_token = data[CONF_LIFESMART_USERTOKEN] user_id = data[CONF_LIFESMART_USERID] - baseurl = data[CONF_URL] + user_password = data[CONF_LIFESMART_USERPASSWORD] + region = data[CONF_REGION] # exclude_devices = data[CONF_EXCLUDE_ITEMS] # exclude_hubs = data[CONF_EXCLUDE_AGTS] # ai_include_hubs = data[CONF_AI_INCLUDE_AGTS] # ai_include_items = data[CONF_AI_INCLUDE_ITEMS] lifesmart_client = LifeSmartClient( - baseurl, + region, app_key, app_token, - user_token, user_id, + user_password, ) - devices = await lifesmart_client.get_all_device_async() + await lifesmart_client.login_async() + + await lifesmart_client.get_all_device_async() return {"title": f"User Id {user_id}", "unique_id": app_key} def get_unique_id(wiser_id: str): + """Generate Unique ID for Hub.""" return str(f"{DOMAIN}-{wiser_id}") @config_entries.HANDLERS.register(DOMAIN) class LifeSmartConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): - """ - LifeSmartConfigFlowHandler configuration method. - """ + """LifeSmartConfigFlowHandler configuration method.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH @@ -83,10 +86,16 @@ def __init__(self) -> None: """Initialize the config flow.""" self.discovery_info = {} - async def async_step_user(self, user_input=None): - """ - Handle a config flow. - """ + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> config_entries.OptionsFlow: + """Create the options flow.""" + return LifeSmartOptionsFlowHandler(config_entry) + + async def async_step_user(self, user_input=None) -> FlowResult: + """Handle a config flow.""" errors = {} if user_input is not None: try: @@ -107,7 +116,7 @@ async def async_step_user(self, user_input=None): return self.async_show_form( step_id="user", - data_schema=self.discovery_info or DATA_SCHEMA, + data_schema=self.discovery_info or vol.Schema(DATA_SCHEMA), errors=errors, ) @@ -119,39 +128,109 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry - async def async_step_main_params(self, user_input=None): - """Handle options flow.""" - """ + async def async_step_user(self, user_input=None) -> FlowResult: + """Handle a config flow.""" + errors = {} if user_input is not None: - if user_input[CONF_HOST]: - data = { - CONF_HOST: user_input[CONF_HOST], - CONF_PASSWORD: self.config_entry.data[CONF_PASSWORD], - CONF_NAME: self.config_entry.data[CONF_NAME], - } - user_input.pop(CONF_HOST) - self.hass.config_entries.async_update_entry( - self.config_entry, data=data + try: + validated = await validate_input(self.hass, user_input) + except: + _LOGGER.warning("Input validation error") + + if "base" not in errors: + # Add hub name to config + user_input[CONF_NAME] = validated["title"] + + return self.async_create_entry( + title=validated["title"], data=user_input ) - options = self.config_entry.options | user_input - return self.async_create_entry(title="", data=options) - """ - - data_schema = { - vol.Required(CONF_LIFESMART_APPKEY): str, - vol.Required(CONF_LIFESMART_APPTOKEN): str, - vol.Required(CONF_LIFESMART_USERTOKEN): str, - vol.Required(CONF_LIFESMART_USERID): str, - vol.Required(CONF_URL): str, - vol.Optional(CONF_EXCLUDE_ITEMS): str, - vol.Optional(CONF_EXCLUDE_AGTS): str, - vol.Optional(CONF_AI_INCLUDE_AGTS): str, - vol.Optional(CONF_AI_INCLUDE_ITEMS): str, + schema = { + vol.Required( + CONF_LIFESMART_APPKEY, + default=self.config_entry.data.get(CONF_LIFESMART_APPKEY), + ): str, + vol.Required( + CONF_LIFESMART_APPTOKEN, + default=self.config_entry.data.get(CONF_LIFESMART_APPTOKEN), + ): str, + vol.Required( + CONF_LIFESMART_USERID, + default=self.config_entry.data.get(CONF_LIFESMART_USERID), + ): str, + vol.Required( + CONF_LIFESMART_USERPASSWORD, + default=self.config_entry.data.get(CONF_LIFESMART_USERPASSWORD), + ): str, + vol.Optional( + CONF_EXCLUDE_ITEMS, + default=self.config_entry.data.get(CONF_EXCLUDE_ITEMS, ""), + ): str, + vol.Optional( + CONF_EXCLUDE_AGTS, + default=self.config_entry.data.get(CONF_EXCLUDE_AGTS, ""), + ): str, + vol.Optional( + CONF_AI_INCLUDE_AGTS, + default=self.config_entry.data.get(CONF_AI_INCLUDE_AGTS, ""), + ): str, + vol.Optional( + CONF_AI_INCLUDE_ITEMS, + default=self.config_entry.data.get(CONF_AI_INCLUDE_ITEMS, ""), + ): str, } + + schema[CONF_REGION] = selector(LIFESMART_REGION_OPTIONS) + return self.async_show_form( - step_id="main_params", data_schema=vol.Schema(data_schema) + step_id="user", + data_schema=vol.Schema(schema), + errors=errors, ) async def async_step_init(self, user_input=None): """Handle options flow.""" - return self.async_show_menu(step_id="init", menu_options=["main_params"]) + if user_input is not None: + options = self.config_entry.options | user_input + return self.async_create_entry(title="", data=options) + + schema = { + vol.Required( + CONF_LIFESMART_APPKEY, + default=self.config_entry.data.get(CONF_LIFESMART_APPKEY), + ): str, + vol.Required( + CONF_LIFESMART_APPTOKEN, + default=self.config_entry.data.get(CONF_LIFESMART_APPTOKEN), + ): str, + vol.Required( + CONF_LIFESMART_USERID, + default=self.config_entry.data.get(CONF_LIFESMART_USERID), + ): str, + vol.Required( + CONF_LIFESMART_USERPASSWORD, + default=self.config_entry.data.get(CONF_LIFESMART_USERPASSWORD), + ): str, + vol.Optional( + CONF_EXCLUDE_ITEMS, + default=self.config_entry.data.get(CONF_EXCLUDE_ITEMS, ""), + ): str, + vol.Optional( + CONF_EXCLUDE_AGTS, + default=self.config_entry.data.get(CONF_EXCLUDE_AGTS, ""), + ): str, + vol.Optional( + CONF_AI_INCLUDE_AGTS, + default=self.config_entry.data.get(CONF_AI_INCLUDE_AGTS, ""), + ): str, + vol.Optional( + CONF_AI_INCLUDE_ITEMS, + default=self.config_entry.data.get(CONF_AI_INCLUDE_ITEMS, ""), + ): str, + } + + schema[CONF_REGION] = selector(LIFESMART_REGION_OPTIONS).CONFIG_SCHEMA + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema(schema), + ) diff --git a/custom_components/lifesmart/const.py b/custom_components/lifesmart/const.py index 5b89b29..2d838bb 100644 --- a/custom_components/lifesmart/const.py +++ b/custom_components/lifesmart/const.py @@ -5,6 +5,7 @@ CONF_LIFESMART_APPTOKEN = "apptoken" CONF_LIFESMART_USERTOKEN = "usertoken" CONF_LIFESMART_USERID = "userid" +CONF_LIFESMART_USERPASSWORD = "userpassword" CONF_EXCLUDE_ITEMS = "exclude" CONF_EXCLUDE_AGTS = "exclude_agt" CONF_AI_INCLUDE_AGTS = "ai_include_agt" @@ -162,3 +163,9 @@ UPDATE_LISTENER = "update_listener" LIFESMART_SIGNAL_UPDATE_ENTITY = "lifesmart_updated" + +LIFESMART_REGION_OPTIONS = { + "select": { + "options": ["", "cn0", "cn1", "cn2", "us", "eur", "jp", "apz"], + } +} diff --git a/custom_components/lifesmart/cover.py b/custom_components/lifesmart/cover.py index fe7a1e8..5491f9e 100644 --- a/custom_components/lifesmart/cover.py +++ b/custom_components/lifesmart/cover.py @@ -1,9 +1,5 @@ """Support for LifeSmart covers.""" -from homeassistant.components.cover import ( - ENTITY_ID_FORMAT, - ATTR_POSITION, - CoverEntity, -) +from homeassistant.components.cover import ATTR_POSITION, ENTITY_ID_FORMAT, CoverEntity from . import LifeSmartDevice diff --git a/custom_components/lifesmart/lifesmart_client.py b/custom_components/lifesmart/lifesmart_client.py index 28f89d9..4e1d8bc 100644 --- a/custom_components/lifesmart/lifesmart_client.py +++ b/custom_components/lifesmart/lifesmart_client.py @@ -1,8 +1,9 @@ import asyncio -import logging -import time import hashlib import json +import logging +import time + import aiohttp _LOGGER = logging.getLogger(__name__) @@ -13,17 +14,18 @@ class LifeSmartClient: def __init__( self, - baseurl, + region, appkey, apptoken, - usertoken, userid, + userpassword, ) -> None: - self._baseurl = baseurl + """Initialize LifeSmart client.""" + self._region = region self._appkey = appkey self._apptoken = apptoken - self._usertoken = usertoken self._userid = userid + self._userpassword = userpassword async def get_all_device_async(self): """Get all devices belong to current user.""" @@ -70,8 +72,38 @@ async def get_all_scene_async(self, agt): return response["message"] return False + async def login_async(self): + """Login to LifeSmart service to get user token.""" + # Get temporary token + url = self.get_api_url() + "/auth.login" + login_data = { + "uid": self._userid, + "pwd": self._userpassword, + "appkey": self._appkey, + } + header = self.generate_header() + send_data = json.dumps(login_data) + response = json.loads(await self.post_async(url, send_data, header)) + if response["code"] != "success": + return False + + # Use temporary token to get usertoken + url = self.get_api_url() + "/auth.do_auth" + auth_data = { + "userid": self._userid, + "token": response["token"], + "appkey": self._appkey, + "rgn": "apz", + } + send_data = json.dumps(auth_data) + response = json.loads(await self.post_async(url, send_data, header)) + if response["code"] == "success": + self._usertoken = response["usertoken"] + + return response + async def set_scene_async(self, agt, id): - """Set the scene by scene id to LifeSmart""" + """Set the scene by scene id to LifeSmart.""" url = self.get_api_url() + "/api.SceneSet" tick = int(time.time()) # keys = str(keys) @@ -210,9 +242,11 @@ async def send_ir_ackey_async( return response async def turn_on_light_swith_async(self, idx, agt, me): + """Turn on light async.""" return await self.send_epset_async("0x81", 1, idx, agt, me) async def turn_off_light_swith_async(self, idx, agt, me): + """Turn off light async.""" return await self.send_epset_async("0x80", 0, idx, agt, me) async def send_epset_async(self, type, val, idx, agt, me): @@ -274,7 +308,7 @@ async def get_epget_async(self, agt, me): return response["message"]["data"] async def get_ir_remote_list_async(self, agt): - """Get remote list for a specific station""" + """Get remote list for a specific station.""" url = self.get_api_url() + "/irapi.GetRemoteList" tick = int(time.time()) @@ -325,26 +359,33 @@ async def get_ir_remote_async(self, agt, ai): return response["message"]["codes"] async def post_async(self, url, data, headers): - """Async method to make a POST api call""" + """Async method to make a POST api call.""" async with aiohttp.ClientSession() as session: async with session.post(url, data=data, headers=headers) as response: response_text = await response.text() return response_text def get_signature(self, data): - """Generate signature required by LifeSmart API""" + """Generate signature required by LifeSmart API.""" return hashlib.md5(data.encode(encoding="UTF-8")).hexdigest() def get_api_url(self): - """Generate api URL""" - return "https://" + self._baseurl + "/app" + """Generate api URL.""" + if self._region == "": + return "https://api.ilifesmart.com/app" + else: + return "https://api." + self._region + ".ilifesmart.com/app" def get_wss_url(self): - """Generate websocket (wss) URL""" - return "wss://" + self._baseurl + ":8443/wsapp/" + """Generate websocket (wss) URL.""" + + if self._region == "": + return "wss://api.ilifesmart.com:8443/wsapp/" + else: + return "wss://api." + self._region + ".ilifesmart.com:8443/wsapp/" def generate_system_request_body(self, tick, data): - """Generate system node in request body which contain credential and signature""" + """Generate system node in request body which contain credential and signature.""" return { "ver": "1.0", "lang": "en", @@ -355,7 +396,7 @@ def generate_system_request_body(self, tick, data): } def generate_time_and_credential_data(self, tick): - """Generate default parameter required in body""" + """Generate default parameter required in body.""" return ( "time:" @@ -371,11 +412,11 @@ def generate_time_and_credential_data(self, tick): ) def generate_header(self): - """Generate default http header required by LifeSmart""" + """Generate default http header required by LifeSmart.""" return {"Content-Type": "application/json"} def generate_wss_auth(self): - """Generate authentication message with signature for wss connection""" + """Generate authentication message with signature for wss connection.""" tick = int(time.time()) sdata = "method:WbAuth," + self.generate_time_and_credential_data(tick) diff --git a/custom_components/lifesmart/light.py b/custom_components/lifesmart/light.py index 2ef840d..f649eba 100644 --- a/custom_components/lifesmart/light.py +++ b/custom_components/lifesmart/light.py @@ -1,25 +1,27 @@ """Support for LifeSmart Gateway Light.""" import binascii +import hashlib +import json import logging import struct -import urllib.request -import json import time -import hashlib +import urllib.request + import aiohttp + from homeassistant.components.light import ( - ColorMode, ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, ATTR_HS_COLOR, - ATTR_RGBW_COLOR, - ATTR_RGB_COLOR, ATTR_MAX_MIREDS, ATTR_MIN_MIREDS, - ATTR_COLOR_TEMP, + ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, + ENTITY_ID_FORMAT, + ColorMode, # SUPPORT_BRIGHTNESS, # SUPPORT_COLOR, LightEntity, - ENTITY_ID_FORMAT, ) import homeassistant.util.color as color_util diff --git a/custom_components/lifesmart/sensor.py b/custom_components/lifesmart/sensor.py index 002db57..e0ff800 100644 --- a/custom_components/lifesmart/sensor.py +++ b/custom_components/lifesmart/sensor.py @@ -1,20 +1,22 @@ """Support for lifesmart sensors.""" import logging -from homeassistant.components.sensor import SensorEntity -from homeassistant.components.sensor import SensorDeviceClass +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.const import ( - PERCENTAGE, - UnitOfTemperature, - UnitOfPower, - UnitOfEnergy, - UnitOfPrecipitationDepth, - CONCENTRATION_PARTS_PER_MILLION, CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + CONCENTRATION_PARTS_PER_MILLION, LIGHT_LUX, + PERCENTAGE, + UnitOfEnergy, + UnitOfPower, + UnitOfTemperature, ) from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo + +# DOMAIN = "sensor" +# ENTITY_ID_FORMAT = DOMAIN + ".{}" +from . import LifeSmartDevice, generate_entity_id from .const import ( DEVICE_DATA_KEY, DEVICE_ID_KEY, @@ -30,12 +32,6 @@ SMART_PLUG_TYPES, ) - -# DOMAIN = "sensor" -# ENTITY_ID_FORMAT = DOMAIN + ".{}" - -from . import LifeSmartDevice, generate_entity_id - _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/lifesmart/translations/en.json b/custom_components/lifesmart/translations/en.json index ac2cad3..6e17b83 100644 --- a/custom_components/lifesmart/translations/en.json +++ b/custom_components/lifesmart/translations/en.json @@ -8,9 +8,9 @@ "data": { "appkey": "App Key", "apptoken": "App Token", - "usertoken": "User Token ", + "userpassword": "User Password ", "userid": "User Id", - "url": "LifeSmart regional domain", + "region": "LifeSmart regional server", "exclude": "List of device to be excluded", "exclude_agt": "List of hub to be excluded", "ai_include_agt": "List of hub to be included in Scene", @@ -27,11 +27,19 @@ }, "options": { "step": { - "init": { + "user": { "title": "LifeSmart Integration Setup", - "description": "Select configurations to edit", - "menu_options": { - "main_params": "Main configurations" + "description": "Enter LifeSmart Platform credential and User details.", + "data": { + "appkey": "App Key", + "apptoken": "App Token", + "userpassword": "User Password ", + "userid": "User Id", + "region": "LifeSmart regional server", + "exclude": "List of device to be excluded", + "exclude_agt": "List of hub to be excluded", + "ai_include_agt": "List of hub to be included in Scene", + "ai_include_me": "List of device to be included in Scene" } }, "main_params": { @@ -40,9 +48,9 @@ "data": { "appkey": "App Key", "apptoken": "App Token", - "usertoken": "User Token ", + "userpassword": "User Password ", "userid": "User Id", - "url": "LifeSmart regional domain", + "region": "LifeSmart regional server", "exclude": "List of device to be excluded", "exclude_agt": "List of hub to be excluded", "ai_include_agt": "List of hub to be included in Scene",