Skip to content

Commit

Permalink
Fix the discovery code, reported in #46.
Browse files Browse the repository at this point in the history
  • Loading branch information
deiger committed Dec 28, 2020
1 parent 8156fcd commit 3b6627e
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 102 deletions.
33 changes: 18 additions & 15 deletions aircon/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import signal
import socket
import sys
import textwrap
import threading
import time
import _thread
Expand Down Expand Up @@ -95,8 +96,10 @@ def ParseArguments() -> argparse.Namespace:
return arg_parser.parse_args()


def setup_logger(log_level):
if sys.platform == 'linux':
def setup_logger(log_level, use_stderr=False):
if use_stderr:
logging_handler = logging.StreamHandler(sys.stderr)
elif sys.platform == 'linux':
logging_handler = logging.handlers.SysLogHandler(address='/dev/log')
elif sys.platform == 'darwin':
logging_handler = logging.handlers.SysLogHandler(address='/var/run/syslog')
Expand Down Expand Up @@ -239,24 +242,23 @@ def _escape_name(name: str):
async def discovery(parsed_args):
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(connect=5.0)) as session:
try:
all_configs = await perform_discovery(parsed_args.app, parsed_args.user,
parsed_args.passwd, parsed_args.prefix,
parsed_args.device,
all_configs = await perform_discovery(session, parsed_args.app, parsed_args.user,
parsed_args.passwd, parsed_args.device,
parsed_args.properties)
except:
print('Error occurred.')
except Exception as e:
print(f'Error occurred:\n{e!r}')
sys.exit(1)

for config in all_configs:
properties_text = ''
if 'properties' in config.keys():
properties_text = 'Properties:\n{}'.format(
json.dumps(config['properties'], indent=2)
)
print('Device {} has:\nIP address: {}\nlanip_key: {}\nlanip_key_id: {}\n{}\n'
.format(config['product_name'], config['lan_ip'],
config['lanip_key'], config['lanip_key_id'],
properties_text))
properties_text = f'Properties:\n{json.dumps(config["properties"], indent=2)}'
print(textwrap.dedent(f"""Device {config['product_name']} has:
IP address: {config['lan_ip']}
lanip_key: {config['lanip_key']}
lanip_key_id: {config['lanip_key_id']}
{properties_text}
"""))

file_content = {
'name': config['product_name'],
Expand All @@ -274,9 +276,10 @@ async def discovery(parsed_args):

if __name__ == '__main__':
parsed_args = ParseArguments() # type: argparse.Namespace
setup_logger(parsed_args.log_level)

if parsed_args.cmd == 'run':
setup_logger(parsed_args.log_level)
asyncio.run(run(parsed_args))
elif parsed_args.cmd == 'discovery':
setup_logger(parsed_args.log_level, use_stderr=True)
asyncio.run(discovery(parsed_args))
152 changes: 65 additions & 87 deletions aircon/discovery.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import aiohttp
import base64
import gzip
from http.client import HTTPSConnection
from http import HTTPStatus
import json
import logging
import ssl
Expand All @@ -10,9 +10,8 @@

_USER_AGENT = 'Dalvik/2.1.0 (Linux; U; Android 9.0; SM-G850F Build/LRX22G)'

def _sign_in(user: str, passwd: str, user_server: str, app_id: str, app_secret: str,
ssl_context: ssl.SSLContext):
conn = HTTPSConnection(user_server, context=ssl_context)
async def _sign_in(user: str, passwd: str, user_server: str, app_id: str, app_secret: str,
session: aiohttp.ClientSession, ssl_context: ssl.SSLContext):
query = {
'user': {
'email': user,
Expand All @@ -32,81 +31,67 @@ def _sign_in(user: str, passwd: str, user_server: str, app_id: str, app_secret:
'Host': user_server,
'Accept-Encoding': 'gzip'
}
logging.debug('POST /users/sign_in.json, body=%r, headers=%r' % (json.dumps(query), headers))
conn.request('POST', '/users/sign_in.json', body=json.dumps(query), headers=headers)
resp = conn.getresponse()
if resp.status != 200:
logging.error('Failed to login to Hisense server:\nStatus %d: %r',
resp.status, resp.reason)
sys.exit(1)
resp_data = resp.read()
try:
resp_data = gzip.decompress(resp_data)
except OSError:
pass # Not gzipped.
try:
tokens = json.loads(resp_data)
except UnicodeDecodeError:
logging.exception('Failed to parse login tokens to Hisense server:\nData: %r',
resp_data)
sys.exit(1)
conn.close()
return tokens['access_token']
logging.debug('POST /users/sign_in.json, body=%r, headers=%r', json.dumps(query), headers)
async with session.request('POST', f'https://{user_server}/users/sign_in.json',
json=query, headers=headers, ssl=ssl_context) as resp:
if resp.status != HTTPStatus.OK.value:
logging.error('Failed to login to Hisense server:\nStatus %d: %r',
resp.status, resp.reason)
sys.exit(1)
resp_data = await resp.text()
try:
tokens = json.loads(resp_data)
except UnicodeDecodeError:
logging.exception('Failed to parse login tokens to Hisense server:\nData: %r',
resp_data)
sys.exit(1)
return tokens['access_token']

def _get_devices(devices_server: str, access_token: str, headers: dict, conn: HTTPSConnection):
logging.debug('GET /apiv1/devices.json, headers=%r' % headers)
conn.request('GET', '/apiv1/devices.json', headers=headers)
resp = conn.getresponse()
if resp.status != 200:
logging.error('Failed to get devices data from Hisense server:\nStatus %d: %r',
resp.status, resp.reason)
sys.exit(1)
resp_data = resp.read()
try:
resp_data = gzip.decompress(resp_data)
except OSError:
pass # Not gzipped.
try:
devices = json.loads(resp_data)
except UnicodeDecodeError:
logging.exception('Failed to parse devices data from Hisense server:\nData: %r',
resp_data)
sys.exit(1)
if not devices:
logging.error('No device is configured! Please configure a device first.')
sys.exit(1)
return devices
async def _get_devices(devices_server: str, access_token: str, headers: dict,
session: aiohttp.ClientSession, ssl_context: ssl.SSLContext):
logging.debug('GET /apiv1/devices.json, headers=%r', headers)
async with session.get(f'https://{devices_server}/apiv1/devices.json',
headers=headers, ssl=ssl_context) as resp:
if resp.status != HTTPStatus.OK.value:
logging.error('Failed to get devices data from Hisense server:\nStatus %d: %r',
resp.status, resp.reason)
sys.exit(1)
resp_data = await resp.text()
try:
devices = json.loads(resp_data)
except UnicodeDecodeError:
logging.exception('Failed to parse devices data from Hisense server:\nData: %r',
resp_data)
sys.exit(1)
if not devices:
logging.error('No device is configured! Please configure a device first.')
sys.exit(1)
return devices

def _get_lanip(dsn: str, headers: dict, conn: HTTPSConnection):
conn.request('GET', '/apiv1/dsns/{}/lan.json'.format(dsn), headers=headers)
resp = conn.getresponse()
if resp.status != 200:
logging.error('Failed to get device data from Hisense server: %r', resp)
sys.exit(1)
resp_data = resp.read()
try:
resp_data = gzip.decompress(resp_data)
except OSError:
pass # Not gzipped.
lanip = json.loads(resp_data)['lanip']
return lanip
async def _get_lanip(devices_server: str, dsn: str, headers: dict,
session: aiohttp.ClientSession, ssl_context: ssl.SSLContext):
logging.debug(f'GET /apiv1/dsns/{dsn}/lan.json, headers=%r', headers)
async with session.get(f'https://{devices_server}/apiv1/dsns/{dsn}/lan.json',
headers=headers, ssl=ssl_context) as resp:
if resp.status != HTTPStatus.OK.value:
logging.error('Failed to get device data from Hisense server: %r', resp)
sys.exit(1)
resp_data = await resp.text()
return json.loads(resp_data)['lanip']

def _get_device_properties(dsn: str, headers: dict, conn: HTTPSConnection):
conn.request('GET', '/apiv1/dsns/{}/properties.json'.format(dsn), headers=headers)
resp = conn.getresponse()
if resp.status != 200:
logging.error('Failed to get properties data from Hisense server: %r', resp)
sys.exit(1)
resp_data = resp.read()
try:
resp_data = gzip.decompress(resp_data)
except OSError:
pass # Not gzipped.
return json.loads(resp_data)
async def _get_device_properties(devices_server: str, dsn: str, headers: dict,
session: aiohttp.ClientSession, ssl_context: ssl.SSLContext):
logging.debug(f'GET /apiv1/dsns/{dsn}/properties.json, headers=%r', headers)
async with session.get(f'https://{devices_server}/apiv1/dsns/{dsn}/properties.json',
headers=headers, ssl=ssl_context) as resp:
if resp.status != HTTPStatus.OK.value:
logging.error('Failed to get properties data from Hisense server: %r', resp)
sys.exit(1)
resp_data = await resp.text()
return json.loads(resp_data)

def perform_discovery(app: str, user: str, passwd: str,
prefix: str, device_filter: str,
properties_filter: bool) -> dict:
async def perform_discovery(session: aiohttp.ClientSession, app: str, user: str, passwd: str,
device_filter: str = None, properties_filter: bool = False) -> dict:
if app in SECRET_ID_MAP:
app_prefix = SECRET_ID_MAP[app]
else:
Expand All @@ -132,10 +117,9 @@ def perform_discovery(app: str, user: str, passwd: str,
ssl_context.check_hostname = False
ssl_context.load_default_certs()

access_token = _sign_in(user, passwd, user_server, app_id, app_secret, ssl_context)
access_token = await _sign_in(user, passwd, user_server, app_id, app_secret, session, ssl_context)

result = []
conn = HTTPSConnection(devices_server, context=ssl_context)
headers = {
'Accept': 'application/json',
'Connection': 'Keep-Alive',
Expand All @@ -144,26 +128,20 @@ def perform_discovery(app: str, user: str, passwd: str,
'Host': devices_server,
'Accept-Encoding': 'gzip'
}
devices = _get_devices(devices_server, access_token, headers, conn)
devices = await _get_devices(devices_server, access_token, headers, session, ssl_context)
logging.debug('Found devices: %r', devices)
for device in devices:
device_data = device['device']
if device_filter and device_filter != device_data['product_name']:
continue
dsn = device_data['dsn']
lanip = _get_lanip(dsn, headers, conn)
lanip = await _get_lanip(devices_server, dsn, headers, session, ssl_context)
properties_text = ''
if properties_filter:
props = _get_device_properties(dsn, headers, conn)
props = await _get_device_properties(devices_server, dsn, headers, session, ssl_context)
device_data['properties'] = props
properties_text = 'Properties:\n%s', json.dumps(props, indent=2)

print('Device {} has:\nIP address: {}\nlanip_key: {}\nlanip_key_id: {}\n{}\n'.format(
device_data['product_name'], device_data['lan_ip'],
lanip['lanip_key'], lanip['lanip_key_id'], properties_text))

device_data['lanip_key'] = lanip['lanip_key']
device_data['lanip_key_id'] = lanip['lanip_key_id']
result.append(device_data)
conn.close()
return result

0 comments on commit 3b6627e

Please sign in to comment.