Skip to content

Commit

Permalink
Implemented API v2 (experimental)
Browse files Browse the repository at this point in the history
No state detection available for the moment
  • Loading branch information
ollo69 committed Apr 6, 2020
1 parent 3411176 commit 150a6e1
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 154 deletions.
3 changes: 2 additions & 1 deletion custom_components/smartthinq_washer/.translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"data": {
"region": "Region (e.g. US)",
"language": "Language (e.g. en-US)",
"token": "Access Token"
"token": "Access Token (only for API v1)",
"use_api_v2": "Use API v2 (experimental)"
},
"description": "Fill in your SmartThinQ access information. If you don't have an access token, leave it empty to start the process for generating a new one.",
"title": "SmartThinQ LGE Washer"
Expand Down
65 changes: 44 additions & 21 deletions custom_components/smartthinq_washer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
import asyncio
import logging

from .wideq.core import(
Client,
parse_oauth_callback,
)
from .wideq.core import Client
from .wideq.core_v2 import ClientV2

from .wideq.core_exceptions import(
NotConnectedError,
Expand All @@ -28,12 +26,15 @@
from homeassistant.const import CONF_REGION, CONF_TOKEN

from .const import (
DOMAIN,
ATTR_CONFIG,
CLIENT,
CONF_LANGUAGE,
SMARTTHINQ_COMPONENTS,
CONF_OAUTH_URL,
CONF_OAUTH_USER_NUM,
CONF_USE_API_V2,
DOMAIN,
LGE_DEVICES,
SMARTTHINQ_COMPONENTS,
)

SMARTTHINQ_SCHEMA = vol.Schema({
Expand All @@ -51,44 +52,63 @@

_LOGGER = logging.getLogger(__name__)


class LGEAuthentication:
def __init__(self, region, language):

def __init__(self, region, language, use_api_v2=False):
self._region = region
self._language = language
self._use_api_v2 = use_api_v2

def _create_client(self):
if self._use_api_v2:
client = ClientV2(country=self._region, language=self._language)
else:
client = Client(country=self._region, language=self._language)

return client

def getLoginUrl(self) -> str:

login_url = None
client = self._create_client()

try:
client = Client(
country=self._region,
language=self._language,
)
login_url = client.gateway.oauth_url()
except:
pass

return login_url

def getTokenFromUrl(self, callback_url) -> str:
def getOAuthInfoFromUrl(self, callback_url) -> str:

refresh_token = None
client = self._create_client()

oauth_info = None
try:
access_token, refresh_token = parse_oauth_callback(callback_url)
except:
if self._use_api_v2:
oauth_info = ClientV2.oauthinfo_from_url(callback_url)
else:
oauth_info = Client.oauthinfo_from_url(callback_url)
except Exception as ex:
_LOGGER.error(ex)
pass

return refresh_token
return oauth_info

def createClientFromToken(self, token):
def createClientFromToken(self, token, oauth_url = None, oauth_user_num = None):

client = None
try:
client = Client.from_token(token, self._region, self._language)
if self._use_api_v2:
client = ClientV2.from_token(oauth_url, token, oauth_user_num, self._region, self._language)
else:
client = Client.from_token(token, self._region, self._language)
except Exception as ex:
_LOGGER.error(ex)
pass

return client


Expand All @@ -102,16 +122,19 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry):
refresh_token = config_entry.data.get(CONF_TOKEN)
region = config_entry.data.get(CONF_REGION)
language = config_entry.data.get(CONF_LANGUAGE)
use_apiv2 = config_entry.data.get(CONF_USE_API_V2, False)
oauth_url = config_entry.data.get(CONF_OAUTH_URL)
oauth_user_num = config_entry.data.get(CONF_OAUTH_USER_NUM)

_LOGGER.info("Initializing smartthinq platform with region: %s - language: %s", region, language)

hass.data.setdefault(DOMAIN, {})[LGE_DEVICES] = {}

# if network is not connected we can have some error
# raising ConfigEntryNotReady platform setup will be retried
lgeauth = LGEAuthentication(region, language)
lgeauth = LGEAuthentication(region, language, use_apiv2)
client = await hass.async_add_executor_job(
lgeauth.createClientFromToken, refresh_token
lgeauth.createClientFromToken, refresh_token, oauth_url, oauth_user_num
)
if not client:
_LOGGER.warning('Connection not available. SmartthinQ platform not ready.')
Expand Down
67 changes: 49 additions & 18 deletions custom_components/smartthinq_washer/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@

import voluptuous as vol
from aiohttp import client, web
from yarl import URL
from typing import Any, Awaitable, Callable, Dict, Optional, cast

from homeassistant import config_entries
from homeassistant.core import HomeAssistant, callback
from homeassistant.const import CONF_REGION, CONF_TOKEN
from homeassistant.components.http import HomeAssistantView
from homeassistant.helpers import config_entry_oauth2_flow as oauth2_flow
from yarl import URL
from typing import Any, Awaitable, Callable, Dict, Optional, cast

from .const import DOMAIN, CONF_LANGUAGE
from homeassistant.const import CONF_REGION, CONF_TOKEN

from .const import DOMAIN, CONF_LANGUAGE, CONF_OAUTH_URL, CONF_OAUTH_USER_NUM, CONF_USE_API_V2
from . import LGEAuthentication

CONF_LOGIN = "login_url"
Expand All @@ -23,6 +25,7 @@
vol.Required(CONF_REGION): str,
vol.Required(CONF_LANGUAGE): str,
vol.Optional(CONF_TOKEN): str,
vol.Required(CONF_USE_API_V2, default=False): bool,
})

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -103,6 +106,10 @@ def __init__(self):
self._region = None
self._language = None
self._token = None
self._oauth_url = None
self._oauth_user_num = None
self._use_api_v2 = False

self._loginurl = None

@property
Expand All @@ -112,6 +119,7 @@ def logger(self) -> logging.Logger:

async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user interface"""

if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")

Expand All @@ -121,6 +129,10 @@ async def async_step_user(self, user_input=None):
region = user_input[CONF_REGION]
language = user_input[CONF_LANGUAGE]
refresh_token = user_input.get(CONF_TOKEN, "")
self._use_api_v2 = user_input[CONF_USE_API_V2]

if self._use_api_v2:
refresh_token = ""

region_regex = re.compile(r"^[A-Z]{2,3}$")
if not region_regex.match(region):
Expand All @@ -135,7 +147,7 @@ async def async_step_user(self, user_input=None):
self._token = refresh_token

if not self._token:
lgauth = LGEAuthentication(self._region, self._language)
lgauth = LGEAuthentication(self._region, self._language, self._use_api_v2)
self._loginurl = await self.hass.async_add_executor_job(
lgauth.getLoginUrl
)
Expand All @@ -152,6 +164,8 @@ async def async_step_user(self, user_input=None):
return await self._save_config_entry()

async def async_oauth_create_entry(self, data: dict) -> dict:
"""Create new entry from oauth2 callback data."""

_LOGGER.info(data)
token = data.get("refresh_token", None)
if not token:
Expand All @@ -161,27 +175,36 @@ async def async_oauth_create_entry(self, data: dict) -> dict:
return self._show_form(errors=None, step_id="token")

async def async_step_url(self, user_input=None):
"""Parse the response url for oauth data and submit for save."""

lgauth = LGEAuthentication(self._region, self._language)
lgauth = LGEAuthentication(self._region, self._language, self._use_api_v2)
url = user_input[CONF_URL]
token = await self.hass.async_add_executor_job(
lgauth.getTokenFromUrl, url
oauth_info = await self.hass.async_add_executor_job(
lgauth.getOAuthInfoFromUrl, url
)
if not token:
if not oauth_info:
return self._show_form(errors={"base": "invalid_url"}, step_id="url")

self._token = token
self._token = oauth_info["refresh_token"]
self._oauth_url = oauth_info.get("oauth_url")
self._oauth_user_num = oauth_info.get("user_number")

if self._use_api_v2:
return await self._save_config_entry()

return self._show_form(errors=None, step_id="token")

async def async_step_token(self, user_input=None):
"""Show result token and submit for save."""
self._token = user_input[CONF_TOKEN]
return await self._save_config_entry()

async def _save_config_entry(self):
# Test the connection to the SmartThinQ.
lgauth = LGEAuthentication(self._region, self._language)
"""Test the connection to the SmartThinQ and save the entry."""

lgauth = LGEAuthentication(self._region, self._language, self._use_api_v2)
client = await self.hass.async_add_executor_job(
lgauth.createClientFromToken, self._token
lgauth.createClientFromToken, self._token, self._oauth_url, self._oauth_user_num
)

if not client:
Expand All @@ -192,13 +215,21 @@ async def _save_config_entry(self):
_LOGGER.error("No SmartThinQ devices found. Component setup aborted.")
return self.async_abort(reason="no_smartthinq_devices")

data={
CONF_TOKEN: self._token,
CONF_REGION: self._region,
CONF_LANGUAGE: self._language,
CONF_USE_API_V2: self._use_api_v2,
}
if self._use_api_v2:
data.update({
CONF_OAUTH_URL: self._oauth_url,
CONF_OAUTH_USER_NUM: self._oauth_user_num,
})

return self.async_create_entry(
title="LGE Washers",
data={
CONF_TOKEN: self._token,
CONF_REGION: self._region,
CONF_LANGUAGE: self._language
}
data=data,
)

@callback
Expand Down
13 changes: 9 additions & 4 deletions custom_components/smartthinq_washer/const.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
DOMAIN = 'smartthinq_washer'
DOMAIN = "smartthinq_washer"

CONF_LANGUAGE = "language"
CONF_OAUTH_URL = "outh_url"
CONF_OAUTH_USER_NUM = "outh_user_num"
CONF_USE_API_V2 = "use_api_v2"

ATTR_CONFIG = "config"
CLIENT = "client"
CONF_LANGUAGE = 'language'
LGE_DEVICES = 'lge_devices'
LGE_DEVICES = "lge_devices"

SMARTTHINQ_COMPONENTS = [
'sensor',
"sensor",
]

4 changes: 2 additions & 2 deletions custom_components/smartthinq_washer/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,8 @@ def _restart_monitor(self):
#self._client.refresh()
self._notlogged = True

except:
LOGGER.warn('Generic Wideq Error. Exiting.')
except Exception as ex:
LOGGER.warn('Generic Wideq Error - [%s]. Exiting', str(ex))
self._notlogged = True

def update(self):
Expand Down
17 changes: 17 additions & 0 deletions custom_components/smartthinq_washer/wideq/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
"""
Support for LG Smartthinq device.
"""
import uuid

def as_list(obj):
"""Wrap non-lists in lists.
If `obj` is a list, return it unchanged. Otherwise, return a
single-element list containing it.
"""

if isinstance(obj, list):
return obj
else:
return [obj]

def gen_uuid():
return str(uuid.uuid4())

Loading

0 comments on commit 150a6e1

Please sign in to comment.