Fixed gpylint warnings.

TBR=noamsml
NOTRY=true

Review URL: https://codereview.chromium.org/292103002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@271596 0039d316-1c4b-4281-b951-d872f2087c98
parent 938f37f7
......@@ -3,18 +3,20 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This prototype has tons of flaws, not the least of which being that it
# occasionally will block while waiting for commands to finish. However, this is
# a quick sketch.
# Script requires following components:
# sudo apt-get install python-tornado
# sudo apt-get install python-pip
# sudo pip install google-api-python-client
"""Prototype of cloud device with support of local API.
This prototype has tons of flaws, not the least of which being that it
occasionally will block while waiting for commands to finish. However, this is
a quick sketch.
Script requires following components:
sudo apt-get install python-tornado
sudo apt-get install python-pip
sudo pip install google-api-python-client
"""
import atexit
import base64
import datetime
import httplib2
import json
import os
import subprocess
......@@ -23,6 +25,7 @@ import traceback
from apiclient.discovery import build_from_document
from apiclient.errors import HttpError
import httplib2
from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.file import Storage
......@@ -33,25 +36,26 @@ _OAUTH_SCOPE = 'https://www.googleapis.com/auth/clouddevices'
_API_CLIENT_FILE = 'config.json'
_API_DISCOVERY_FILE = 'discovery.json'
_DEVICE_STATE_FILE = 'device_state.json'
DEVICE_DRAFT = {
'systemName': 'LEDFlasher',
'deviceKind': 'vendor',
'displayName': 'LED Flasher',
'channel': {
'supportedType': 'xmpp'
},
'commands': {
'base': {
'vendorCommands': [{
'name': 'flashLED',
'parameter' : [{
'name': 'times',
'type': 'string'
}]
}]
'systemName': 'LEDFlasher',
'deviceKind': 'vendor',
'displayName': 'LED Flasher',
'channel': {
'supportedType': 'xmpp'
},
'commands': {
'base': {
'vendorCommands': [{
'name': 'flashLED',
'parameter': [{
'name': 'times',
'type': 'string'
}]
}]
}
}
}
}
wpa_supplicant_cmd = 'wpa_supplicant -Dwext -iwlan0 -cwpa_supplicant.conf'
......@@ -65,843 +69,927 @@ wpa_supplicant_conf = 'wpa_supplicant.conf'
wpa_supplicant_template = """
network={
ssid="%s"
scan_ssid=1
proto=WPA RSN
key_mgmt=WPA-PSK
pairwise=CCMP TKIP
group=CCMP TKIP
psk="%s"
ssid="%s"
scan_ssid=1
proto=WPA RSN
key_mgmt=WPA-PSK
pairwise=CCMP TKIP
group=CCMP TKIP
psk="%s"
}"""
led_path = '/sys/class/leds/ath9k_htc-phy0/'
class DeviceUnregisteredError(Exception):
pass
pass
def ignore_errors(func):
def inner(*args, **kwargs):
try:
func(*args, **kwargs)
except:
print 'Got error in unsafe function:'
traceback.print_exc()
return inner
def inner(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception: # pylint: disable=broad-except
print 'Got error in unsafe function:'
traceback.print_exc()
return inner
class CommandWrapperReal(object):
def __init__(self, cmd):
if type(cmd) == str:
cmd = cmd.split()
self.cmd = cmd
self.process = None
def start(self):
if self.process:
end()
self.process = subprocess.Popen(self.cmd)
def wait(self):
self.process.wait()
def end(self):
if self.process:
self.process.terminate()
"""Command wrapper that executs shell commands."""
def __init__(self, cmd):
if type(cmd) == str:
cmd = cmd.split()
self.cmd = cmd
self.process = None
def start(self):
if self.process:
self.end()
self.process = subprocess.Popen(self.cmd)
def wait(self):
self.process.wait()
def end(self):
if self.process:
self.process.terminate()
class CommandWrapperFake(object):
def __init__(self, cmd):
self.cmd = cmd
def start(self):
print 'Start: ', self.cmd
def wait(self):
print 'Wait: ', self.cmd
def end(self):
print 'End: ', self.cmd
"""Command wrapper that just prints shell commands."""
def __init__(self, cmd):
self.cmd = cmd
def start(self):
print 'Start: ', self.cmd
def wait(self):
print 'Wait: ', self.cmd
def end(self):
print 'End: ', self.cmd
class CloudCommandHandlerFake(object):
def __init__(self, ioloop):
pass
def handle_command(self, command_name, args):
if command_name == 'flashLED':
times = 1
if 'times' in args:
times = int(args['times'])
print 'Flashing LED %d times' % times
"""Prints devices commands without execution."""
def __init__(self, ioloop):
pass
def handle_command(self, command_name, args):
if command_name == 'flashLED':
times = 1
if 'times' in args:
times = int(args['times'])
print 'Flashing LED %d times' % times
class CloudCommandHandlerReal(object):
def __init__(self, ioloop):
self.ioloop = ioloop
def handle_command(self, command_name, args):
if command_name == 'flashLED':
times = 1
if 'times' in args:
times = int(args['times'])
print 'Really flashing LED %d times' % times
self.flash_led(times)
@ignore_errors
def flash_led(self, times):
self.set_led(times*2, True)
def set_led(self, times, value):
if not times:
return
file_trigger = open(os.path.join(led_path, 'brightness'), 'w')
if value:
file_trigger.write('1')
else:
file_trigger.write('0')
file_trigger.close()
self.ioloop.add_timeout(datetime.timedelta(milliseconds = 500),
lambda: self.set_led(times - 1, not value))
"""Executes device commands."""
class WifiHandler(object):
class Delegate:
# Token is optional, and all delegates should support it being None
def on_wifi_connected(self, token):
raise Exception('Unhandled condition: WiFi connected')
def __init__(self, ioloop, state, delegate):
self.ioloop = ioloop
self.state = state
self.delegate = delegate
def start(self):
raise Exception('Start not implemented!')
def get_ssid(self):
raise Exception('Get SSID not implemented!')
# Note that by using CommandWrapperFake, you can run WifiHandlerReal on fake
# devices for testing the wifi-specific logic
class WifiHandlerReal(WifiHandler):
def __init__(self, ioloop, state, delegate):
super(WifiHandlerReal, self).__init__(ioloop, state, delegate)
self.hostapd = CommandWrapper(hostapd_cmd)
self.wpa_supplicant = CommandWrapper(wpa_supplicant_cmd)
self.dhcpd = CommandWrapper(dhcpd_cmd)
def start(self):
if self.state.has_wifi():
self.switch_to_wifi(self.state.ssid(),
self.state.password(),
None)
else:
self.start_hostapd()
def start_hostapd(self):
self.hostapd.start()
time.sleep(3)
run_command(ifconfig_cmd)
self.dhcpd.start()
def switch_to_wifi(self, ssid, passwd, token):
try:
wpa_config = open(wpa_supplicant_conf, 'w')
wpa_config.write(wpa_supplicant_template % (ssid, passwd))
wpa_config.close()
self.hostapd.end()
self.dhcpd.end()
self.wpa_supplicant.start()
run_command(dhclient_release)
run_command(dhclient_renew)
self.state.set_wifi(ssid,passwd)
self.delegate.on_wifi_connected(token)
except DeviceUnregisteredError:
self.state.reset()
self.wpa_supplicant.end()
self.start_hostapd()
def stop(self):
self.hostapd.end()
self.wpa_supplicant.end()
self.dhcpd.end()
def get_ssid(self):
return self.state.get_ssid()
def __init__(self, ioloop):
self.ioloop = ioloop
def handle_command(self, command_name, args):
if command_name == 'flashLED':
times = 1
if 'times' in args:
times = int(args['times'])
print 'Really flashing LED %d times' % times
self.flash_led(times)
class WifiHandlerPassthrough(WifiHandler):
def __init__(self, ioloop, state, delegate):
super(WifiHandlerPassthrough, self).__init__(ioloop, state, delegate)
def start(self):
self.delegate.on_wifi_connected(None)
def switch_to_wifi(self, ssid, passwd, token):
raise Exception('Should not be reached')
def get_ssid(self):
return 'dummy'
@ignore_errors
def flash_led(self, times):
self.set_led(times*2, True)
def set_led(self, times, value):
"""Set led value."""
if not times:
return
def setup_fake():
print 'Called setup'
file_trigger = open(os.path.join(led_path, 'brightness'), 'w')
if value:
file_trigger.write('1')
else:
file_trigger.write('0')
def setup_real():
file_trigger = open(os.path.join(led_path, 'trigger'), 'w')
file_trigger.write('none')
file_trigger.close()
if os.path.exists('on_real_device'):
CommandWrapper = CommandWrapperReal
CommandWrapperMDns = CommandWrapperReal
CloudCommandHandler = CloudCommandHandlerReal
WifiHandler = WifiHandlerReal
setup_real()
else:
CommandWrapper = CommandWrapperFake
CommandWrapperMDns = CommandWrapperReal
CloudCommandHandler = CloudCommandHandlerFake
WifiHandler = WifiHandlerPassthrough
setup_fake()
class State:
def __init__(self):
self.oauth_storage_ = Storage('oauth_creds')
self.clear()
def clear(self):
self.credentials_ = None
self.has_credentials_ = False
self.has_wifi_ = False
self.ssid_ = ''
self.password_ = ''
self.device_id_ = ''
def reset(self):
self.clear()
self.dump()
def dump(self):
json_obj = {
'has_credentials': self.has_credentials_,
'has_wifi': self.has_wifi_,
'ssid': self.ssid_,
'password': self.password_,
'device_id': self.device_id_ }
statefile = open('device_state.json', 'w')
json.dump(json_obj, statefile)
statefile.close()
if self.has_credentials_:
self.oauth_storage_.put(self.credentials_)
def load(self):
if os.path.exists('device_state.json'):
statefile = open('device_state.json', 'r')
json_obj = json.load(statefile)
statefile.close()
self.has_credentials_ = json_obj['has_credentials']
self.has_wifi_ = json_obj['has_wifi']
self.ssid_ = json_obj['ssid']
self.password_ = json_obj['password']
self.device_id_ = json_obj['device_id']
if self.has_credentials_:
self.credentials_ = self.oauth_storage_.get()
def set_credentials(self, credentials, device_id):
self.device_id_ = device_id
self.credentials_ = credentials
self.has_credentials_ = True
self.dump()
def set_wifi(self, ssid, password):
self.ssid_ = ssid
self.password_ = password
self.has_wifi_ = True
self.dump()
def has_wifi(self):
return self.has_wifi_
def has_credentials(self):
return self.has_credentials_
def credentials(self):
return self.credentials_
def ssid(self):
return self.ssid_
def password(self):
return self.password_
def device_id(self):
return self.device_id_
def run_command(cmd):
wrapper = CommandWrapper(cmd)
self.ioloop.add_timeout(datetime.timedelta(milliseconds=500),
lambda: self.set_led(times - 1, not value))
class WifiHandler(object):
"""Base class for wifi handlers."""
class Delegate(object):
def on_wifi_connected(self, unused_token):
"""Token is optional, and all delegates should support it being None."""
raise Exception('Unhandled condition: WiFi connected')
def __init__(self, ioloop, state, delegate):
self.ioloop = ioloop
self.state = state
self.delegate = delegate
def start(self):
raise Exception('Start not implemented!')
def get_ssid(self):
raise Exception('Get SSID not implemented!')
class WifiHandlerReal(WifiHandler):
"""Real wifi handler.
Note that by using CommandWrapperFake, you can run WifiHandlerReal on fake
devices for testing the wifi-specific logic.
"""
def __init__(self, ioloop, state, delegate):
super(WifiHandlerReal, self).__init__(ioloop, state, delegate)
self.command_wrapper = CommandWrapperReal
self.hostapd = self.CommandWrapper(hostapd_cmd)
self.wpa_supplicant = self.CommandWrapper(wpa_supplicant_cmd)
self.dhcpd = self.CommandWrapper(dhcpd_cmd)
def start(self):
if self.state.has_wifi():
self.switch_to_wifi(self.state.ssid(), self.state.password(), None)
else:
self.start_hostapd()
def start_hostapd(self):
self.hostapd.start()
time.sleep(3)
self.run_command(ifconfig_cmd)
self.dhcpd.start()
def switch_to_wifi(self, ssid, passwd, token):
try:
wpa_config = open(wpa_supplicant_conf, 'w')
wpa_config.write(wpa_supplicant_template % (ssid, passwd))
wpa_config.close()
self.hostapd.end()
self.dhcpd.end()
self.wpa_supplicant.start()
self.run_command(dhclient_release)
self.run_command(dhclient_renew)
self.state.set_wifi(ssid, passwd)
self.delegate.on_wifi_connected(token)
except DeviceUnregisteredError:
self.state.reset()
self.wpa_supplicant.end()
self.start_hostapd()
def stop(self):
self.hostapd.end()
self.wpa_supplicant.end()
self.dhcpd.end()
def get_ssid(self):
return self.state.get_ssid()
def run_command(self, cmd):
wrapper = self.command_wrapper(cmd)
wrapper.start()
wrapper.wait()
class MDnsWrapper:
def __init__(self):
self.avahi_wrapper = None
self.setup_name = None
self.device_id = ''
self.started = False
def start(self):
self.started = True
self.run_command()
def get_command(self):
cmd = ['avahi-publish', '-s', 'Raspberry Pi' , '_privet._tcp', '8080',
'txtvers=2', 'type=wifi', 'ty=Raspberry Pi',
'id=' + self.device_id]
if self.setup_name:
cmd.append('setup=' + self.setup_name)
return cmd
def run_command(self):
if self.avahi_wrapper:
self.avahi_wrapper.end()
self.avahi_wrapper.wait()
self.avahi_wrapper = CommandWrapperMDns(self.get_command())
self.avahi_wrapper.start()
def set_id(self, device_id):
self.device_id = device_id
if self.started:
self.run_command()
def set_setup_name(self, setup_name):
self.setup_name = setup_name
if self.started:
self.run_command()
class CloudDevice:
class Delegate:
def on_device_started(self):
raise Exception('Not implemented: Device started')
def on_device_stopped(self):
raise Exception('Not implemented: Device stopped')
def __init__(self, ioloop, state, delegate):
self.state = state
self.http = httplib2.Http()
if not os.path.isfile(_API_CLIENT_FILE):
credentials = {
'oauth_client_id' : '',
'oauth_secret' : '',
'api_key' : ''
}
credentials_f = open(_API_CLIENT_FILE + '.samlpe', 'w')
credentials_f.write(json.dumps(credentials));
credentials_f.close()
raise Exception('Missing ' + _API_CLIENT_FILE);
credentials_f = open(_API_CLIENT_FILE)
credentials = json.load(credentials_f)
credentials_f.close()
self.oauth_client_id = credentials['oauth_client_id']
self.oauth_secret = credentials['oauth_secret']
self.api_key = credentials['api_key']
if not os.path.isfile(_API_DISCOVERY_FILE):
raise Exception('Download https://developers.google.com/'
'cloud-devices/v1/discovery.json');
f = open('discovery.json')
discovery = f.read()
f.close()
self.gcd = build_from_document(
discovery, developerKey=self.api_key, http=self.http)
self.ioloop = ioloop
self.active = True
self.device_id = None
self.credentials = None
self.delegate = delegate
self.command_handler = CloudCommandHandler(ioloop)
def try_start(self, token): # Token may be null
if self.state.has_credentials():
self.credentials = self.state.credentials()
self.device_id = self.state.device_id()
self.run_device()
elif token:
self.register(token)
else:
print 'Device not registered and has no credentials.'
print 'Waiting for registration.'
def register(self, token):
resource = {
'deviceDraft': DEVICE_DRAFT,
'oauthClientId': self.oauth_client_id
}
self.gcd.registrationTickets().patch(registrationTicketId=token,
body=resource).execute()
class WifiHandlerPassthrough(WifiHandler):
"""Passthrough wifi handler."""
def __init__(self, ioloop, state, delegate):
super(WifiHandlerPassthrough, self).__init__(ioloop, state, delegate)
def start(self):
self.delegate.on_wifi_connected(None)
def switch_to_wifi(self, unused_ssid, unused_passwd, unused_token):
raise Exception('Should not be reached')
def get_ssid(self):
return 'dummy'
class State(object):
"""Device state."""
def __init__(self):
self.oauth_storage_ = Storage('oauth_creds')
self.clear()
def clear(self):
self.credentials_ = None
self.has_credentials_ = False
self.has_wifi_ = False
self.ssid_ = ''
self.password_ = ''
self.device_id_ = ''
def reset(self):
self.clear()
self.dump()
def dump(self):
"""Saves device state to file."""
json_obj = {
'has_credentials': self.has_credentials_,
'has_wifi': self.has_wifi_,
'ssid': self.ssid_,
'password': self.password_,
'device_id': self.device_id_
}
statefile = open(_DEVICE_STATE_FILE, 'w')
json.dump(json_obj, statefile)
statefile.close()
if self.has_credentials_:
self.oauth_storage_.put(self.credentials_)
def load(self):
if os.path.exists(_DEVICE_STATE_FILE):
statefile = open(_DEVICE_STATE_FILE, 'r')
json_obj = json.load(statefile)
statefile.close()
self.has_credentials_ = json_obj['has_credentials']
self.has_wifi_ = json_obj['has_wifi']
self.ssid_ = json_obj['ssid']
self.password_ = json_obj['password']
self.device_id_ = json_obj['device_id']
if self.has_credentials_:
self.credentials_ = self.oauth_storage_.get()
def set_credentials(self, credentials, device_id):
self.device_id_ = device_id
self.credentials_ = credentials
self.has_credentials_ = True
self.dump()
def set_wifi(self, ssid, password):
self.ssid_ = ssid
self.password_ = password
self.has_wifi_ = True
self.dump()
def has_wifi(self):
return self.has_wifi_
def has_credentials(self):
return self.has_credentials_
def credentials(self):
return self.credentials_
def ssid(self):
return self.ssid_
def password(self):
return self.password_
def device_id(self):
return self.device_id_
class MDnsWrapper(object):
"""Handles mDNS requests to device."""
def __init__(self, command_wrapper):
self.command_wrapper = command_wrapper
self.avahi_wrapper = None
self.setup_name = None
self.device_id = ''
self.started = False
def start(self):
self.started = True
self.run_command()
finalTicket = self.gcd.registrationTickets().finalize(
registrationTicketId=token).execute()
def get_command(self):
cmd = ['avahi-publish', '-s', 'Raspberry Pi', '_privet._tcp', '8080',
'txtvers=2', 'type=wifi', 'ty=Raspberry Pi', 'id=' + self.device_id]
if self.setup_name:
cmd.append('setup=' + self.setup_name)
return cmd
authorization_code = finalTicket['robotAccountAuthorizationCode']
flow = OAuth2WebServerFlow(
self.oauth_client_id, self.oauth_secret, _OAUTH_SCOPE,
redirect_uri='oob')
self.credentials = flow.step2_exchange(authorization_code)
self.device_id = finalTicket['deviceDraft']['id']
self.state.set_credentials(self.credentials, self.device_id)
print 'Registered with device_id ', self.device_id
def run_command(self):
if self.avahi_wrapper:
self.avahi_wrapper.end()
self.avahi_wrapper.wait()
self.run_device()
def run_device(self):
self.credentials.authorize(self.http)
self.avahi_wrapper = self.command_wrapper(self.get_command())
self.avahi_wrapper.start()
def set_id(self, device_id):
self.device_id = device_id
if self.started:
self.run_command()
def set_setup_name(self, setup_name):
self.setup_name = setup_name
if self.started:
self.run_command()
class CloudDevice(object):
"""Handles device registration and commands."""
class Delegate(object):
def on_device_started(self):
raise Exception('Not implemented: Device started')
def on_device_stopped(self):
raise Exception('Not implemented: Device stopped')
def __init__(self, ioloop, state, command_wrapper, delegate):
self.state = state
self.http = httplib2.Http()
if not os.path.isfile(_API_CLIENT_FILE):
credentials = {
'oauth_client_id': '',
'oauth_secret': '',
'api_key': ''
}
credentials_f = open(_API_CLIENT_FILE + '.samlpe', 'w')
credentials_f.write(json.dumps(credentials))
credentials_f.close()
raise Exception('Missing ' + _API_CLIENT_FILE)
credentials_f = open(_API_CLIENT_FILE)
credentials = json.load(credentials_f)
credentials_f.close()
self.oauth_client_id = credentials['oauth_client_id']
self.oauth_secret = credentials['oauth_secret']
self.api_key = credentials['api_key']
if not os.path.isfile(_API_DISCOVERY_FILE):
raise Exception('Download https://developers.google.com/'
'cloud-devices/v1/discovery.json')
f = open(_API_DISCOVERY_FILE)
discovery = f.read()
f.close()
self.gcd = build_from_document(discovery, developerKey=self.api_key,
http=self.http)
self.ioloop = ioloop
self.active = True
self.device_id = None
self.credentials = None
self.delegate = delegate
self.command_handler = command_wrapper(ioloop)
def try_start(self, token):
"""Tries start or register device."""
if self.state.has_credentials():
self.credentials = self.state.credentials()
self.device_id = self.state.device_id()
self.run_device()
elif token:
self.register(token)
else:
print 'Device not registered and has no credentials.'
print 'Waiting for registration.'
def register(self, token):
"""Register device."""
resource = {
'deviceDraft': DEVICE_DRAFT,
'oauthClientId': self.oauth_client_id
}
self.gcd.registrationTickets().patch(registration_ticke_id=token,
body=resource).execute()
final_ticket = self.gcd.registrationTickets().finalize(
registration_ticke_id=token).execute()
authorization_code = final_ticket['robotAccountAuthorizationCode']
flow = OAuth2WebServerFlow(self.oauth_client_id, self.oauth_secret,
_OAUTH_SCOPE, redirect_uri='oob')
self.credentials = flow.step2_exchange(authorization_code)
self.device_id = final_ticket['deviceDraft']['id']
self.state.set_credentials(self.credentials, self.device_id)
print 'Registered with device_id ', self.device_id
self.run_device()
def run_device(self):
"""Runs device."""
self.credentials.authorize(self.http)
try:
self.gcd.devices().get(deviceId=self.device_id).execute()
except HttpError, e:
# Pretty good indication the device was deleted
if e.resp.status == 404:
raise DeviceUnregisteredError()
except AccessTokenRefreshError:
raise DeviceUnregisteredError()
self.check_commands()
self.delegate.on_device_started()
def check_commands(self):
"""Checks device commands."""
if not self.active:
return
print 'Checking commands...'
commands = self.gcd.commands().list(deviceId=self.device_id,
state='queued').execute()
if 'commands' in commands:
print 'Found ', len(commands['commands']), ' commands'
vendor_command_name = None
for command in commands['commands']:
try:
dev=self.gcd.devices().get(deviceId=self.device_id).execute()
except HttpError, e:
# Pretty good indication the device was deleted
if e.resp.status == 404:
raise DeviceUnregisteredError()
except AccessTokenRefreshError:
raise DeviceUnregisteredError()
self.check_commands()
self.delegate.on_device_started()
def check_commands(self):
if not self.active:
return
print 'Checking commands...'
commands = self.gcd.commands().list(deviceId=self.device_id,
state='queued').execute()
if 'commands' in commands:
print 'Found ', len(commands['commands']), ' commands'
args = {}
vendorCommandName = None
for command in commands['commands']:
try:
if command['name'].startswith('base._'):
vendorCommandName = command['name'][
len('base._'):]
if 'parameters' in command:
parameters = command['parameters']
else:
parameters = {}
else:
vendorCommandName = None
except KeyError:
print 'Could not parse vendor command ',
print repr(command)
vendorCommandName = None
if vendorCommandName:
self.command_handler.handle_command(
vendorCommandName,
parameters)
self.gcd.commands().patch(commandId = command['id'],
body={'state': 'done'}).execute()
else:
print 'Found no commands'
self.ioloop.add_timeout(datetime.timedelta(milliseconds=1000),
self.check_commands)
def stop(self):
self.active = False
def get_device_id(self):
return self.device_id
if command['name'].startswith('base._'):
vendor_command_name = command['name'][len('base._'):]
if 'parameters' in command:
parameters = command['parameters']
else:
parameters = {}
else:
vendor_command_name = None
except KeyError:
print 'Could not parse vendor command ',
print repr(command)
vendor_command_name = None
if vendor_command_name:
self.command_handler.handle_command(vendor_command_name, parameters)
self.gcd.commands().patch(commandId=command['id'],
body={'state': 'done'}).execute()
else:
print 'Found no commands'
self.ioloop.add_timeout(datetime.timedelta(milliseconds=1000),
self.check_commands)
def stop(self):
self.active = False
def get_device_id(self):
return self.device_id
def get_only(f):
def inner(self, request, response_func, *args):
if request.method != 'GET':
return False
return f(self, request, response_func, *args)
return inner
def inner(self, request, response_func, *args):
if request.method != 'GET':
return False
return f(self, request, response_func, *args)
return inner
def post_only(f):
def inner(self, request, response_func, *args):
if request.method != 'POST':
return False
return f(self, request, response_func, *args)
return inner
def inner(self, request, response_func, *args):
# if request.method != 'POST':
# return False
return f(self, request, response_func, *args)
return inner
def wifi_provisioning(f):
def inner(self, request, response_func, *args):
if self.on_wifi:
return False
return f(self, request, response_func, *args)
return inner
def inner(self, request, response_func, *args):
if self.on_wifi:
return False
return f(self, request, response_func, *args)
return inner
def post_provisioning(f):
def inner(self, request, response_func, *args):
if not self.on_wifi:
return False
return f(self, request, response_func, *args)
return inner
def inner(self, request, response_func, *args):
if not self.on_wifi:
return False
return f(self, request, response_func, *args)
return inner
def extract_encryption_params(f):
def inner(self, request, response_func, *args):
try:
client_id = request.headers['X-Privet-Client-ID']
if 'X-Privet-Encrypted' in request.headers:
encrypted = (request.headers['X-Privet-Encrypted'].lower()
== 'true')
else:
encrypted = False
except (KeyError, TypeError):
print 'Missing client parameters in headers'
response_func(400, { 'error': 'missing_client_parameters' })
return True
"""Extracts privet encription header and pass as parameter into function."""
def inner(self, request, response_func, *args):
"""Extracts privet encription header."""
try:
client_id = request.headers['X-Privet-Client-ID']
if 'X-Privet-Encrypted' in request.headers:
encrypted = (request.headers['X-Privet-Encrypted'].lower() == 'true')
else:
encrypted = False
except (KeyError, TypeError):
print 'Missing client parameters in headers'
response_func(400, {'error': 'missing_client_parameters'})
return True
return f(self, request, response_func, client_id, encrypted, *args)
return inner
return f(self, request, response_func, client_id, encrypted, *args)
return inner
def merge_dictionary(a, b):
result = {}
for k in a:
result[k] = a[k]
for k in b:
result[k] = b[k]
return result
class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate):
"""Handles HTTP requests."""
class InvalidStepError(Exception):
class InvalidStepError(Exception):
pass
class InvalidPackageError(Exception):
class InvalidPackageError(Exception):
pass
class EncryptionError(Exception):
class EncryptionError(Exception):
pass
class CancelableClosure:
class CancelableClosure(object):
"""Allows to cancel callbacks."""
def __init__(self, function):
self.function = function
self.function = function
def __call__(self):
if self.function:
return self.function
return None
if self.function:
return self.function
return None
def cancel(self):
self.function = None
self.function = None
class DummySession(object):
"""Handles sessions."""
class DummySession:
def __init__(self, client_id):
self.client_id = client_id
self.key = None
def do_step(self, step, package):
if step != 0:
raise InvalidStepError()
self.client_id = client_id
self.key = None
def do_step(self, step, package):
if step != 0:
raise self.InvalidStepError()
self.key = package
return self.key
self.key = package
return self.key
def decrypt(self, cyphertext):
return json.loads(cyphertext[len(self.key):])
return json.loads(cyphertext[len(self.key):])
def encrypt(self, plain_data):
return self.key + json.dumps(plain_data)
return self.key + json.dumps(plain_data)
def get_client_id(self):
return client_id
return self.client_id
def get_stype(self):
return 'dummy'
return 'dummy'
def __init__(self, ioloop, state):
if os.path.exists('on_real_device'):
mdns_wrappers = CommandWrapperReal
cloud_wrapper = CloudCommandHandlerReal
wifi_handler = WifiHandlerReal
self.setup_real()
else:
mdns_wrappers = CommandWrapperReal
cloud_wrapper = CloudCommandHandlerFake
wifi_handler = WifiHandlerPassthrough
self.setup_fake()
self.cloud_device = CloudDevice(ioloop, state, cloud_wrapper, self)
self.wifi_handler = wifi_handler(ioloop, state, self)
self.mdns_wrapper = MDnsWrapper(mdns_wrappers)
self.on_wifi = False
self.registered = False
self.in_session = False
self.ioloop = ioloop
self.handlers = {
'/internal/ping': self.do_ping,
'/privet/info': self.do_info,
'/deprecated/wifi/switch': self.do_wifi_switch,
'/privet/v2/session/handshake': self.do_session_handshake,
'/privet/v2/session/cancel': self.do_session_cancel,
'/privet/v2/session/api': self.do_session_api,
'/privet/v2/setup/start':
self.get_insecure_api_handler(self.do_secure_setup_start),
'/privet/v2/setup/cancel':
self.get_insecure_api_handler(self.do_secure_setup_cancel),
'/privet/v2/setup/status':
self.get_insecure_api_handler(self.do_secure_status),
}
class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate):
def __init__(self, ioloop, state):
self.cloud_device = CloudDevice(ioloop, state, self)
self.wifi_handler = WifiHandler(ioloop, state, self)
self.mdns_wrapper = MDnsWrapper()
self.on_wifi = False
self.registered = False
self.in_session = False
self.ioloop = ioloop
self.handlers = {
'/internal/ping': self.do_ping,
'/privet/info': self.do_info,
'/deprecated/wifi/switch': self.do_wifi_switch,
'/privet/v2/session/handshake': self.do_session_handshake,
'/privet/v2/session/cancel': self.do_session_cancel,
'/privet/v2/session/api': self.do_session_api,
'/privet/v2/setup/start': self.get_insecure_api_handler(
self.do_secure_setup),
'/privet/v2/setup/status': self.get_insecure_api_handler(
self.do_secure_status),
self.current_session = None
self.session_cancel_callback = None
self.session_handlers = {
'dummy': self.DummySession
}
}
self.secure_handlers = {
'/privet/v2/setup/start': self.do_secure_setup_start,
'/privet/v2/setup/cancel': self.do_secure_setup_cancel,
'/privet/v2/setup/status': self.do_secure_status
}
self.current_session = None
self.session_cancel_callback = None
self.session_handlers = {
'dummy' : DummySession
}
@staticmethod
def setup_fake():
print 'Called setup'
self.secure_handlers = {
'/privet/v2/setup/start' : self.do_secure_setup,
'/privet/v2/setup/status' : self.do_secure_status
}
def start(self):
self.wifi_handler.start()
self.mdns_wrapper.set_setup_name('RaspberryPi.camera.privet')
self.mdns_wrapper.start()
@get_only
def do_ping(self, request, response_func):
response_func(200, '{ "pong": true }')
return True
@get_only
def do_public_info(self, request, response_func):
info = merge_dictionary(
self.get_common_info(),
{
'stype' : self.session_handlers.keys()
})
self.real_send_response(
request, 200, json.dumps(info))
@post_provisioning
@get_only
def do_info(self, request, response_func):
specific_info = {
'x-privet-token': 'sample',
}
@staticmethod
def setup_real():
file_trigger = open(os.path.join(led_path, 'trigger'), 'w')
file_trigger.write('none')
file_trigger.close()
info = merge_dictionary(
self.get_common_info(),
specific_info
)
self.real_send_response(
request, 200, json.dumps(info))
@post_only
@wifi_provisioning
def do_wifi_switch(self, request, response_func):
data = json.loads(request.body)
try:
ssid = data['ssid']
passw = data['passw']
except KeyError:
print 'Malformed content: ' + repr(data)
self.real_send_response(
request, 400, { 'error': 'invalid_params' })
traceback.print_exc()
return True
response_func(200, { 'ssid': ssid } )
self.wifi_handler.switch_to_wifi(ssid, passw, None)
# TODO: Return to normal wifi after timeout (cancelable)
def start(self):
self.wifi_handler.start()
self.mdns_wrapper.set_setup_name('RaspberryPi.camera.privet')
self.mdns_wrapper.start()
@get_only
def do_ping(self, unused_request, response_func):
response_func(200, '{ "pong": true }')
return True
@get_only
def do_public_info(self, request, unused_response_func):
info = self.get_common_info().items() + {
'stype': self.session_handlers.keys()}.items()
self.real_send_response(request, 200, json.dumps(info))
@post_provisioning
@get_only
def do_info(self, request, unused_response_func):
specific_info = {'x-privet-token': 'sample'}
info = self.get_common_info().items() + specific_info.items()
self.real_send_response(request, 200, json.dumps(info))
@post_only
@wifi_provisioning
def do_wifi_switch(self, request, response_func):
"""Handles /deprecated/wifi/switch requests."""
data = json.loads(request.body)
try:
ssid = data['ssid']
passw = data['passw']
except KeyError:
print 'Malformed content: ' + repr(data)
self.real_send_response(request, 400, {'error': 'invalid_params'})
traceback.print_exc()
return True
response_func(200, {'ssid': ssid})
self.wifi_handler.switch_to_wifi(ssid, passw, None)
# TODO(noamsml): Return to normal wifi after timeout (cancelable)
return True
@extract_encryption_params
@post_only
@wifi_provisioning
def do_session_handshake(self, request, unused_response_func, client_id,
unused_encrypted):
"""Handles /privet/v2/session/handshake requests."""
data = json.loads(request.body)
try:
stype = data['stype']
step = data['step']
package = base64.b64decode(data['package'])
except (KeyError, TypeError):
traceback.print_exc()
print 'Malformed content: ' + repr(data)
self.real_send_response(request, 400, {'error': 'invalid_params'})
return True
if self.current_session:
if client_id != self.current_session.get_client_id():
self.real_send_response(request, 500, {'error': 'in_session'})
return True
@extract_encryption_params
@post_only
@wifi_provisioning
def do_session_handshake(self, request, response_func, client_id,
encrypted):
data = json.loads(request.body)
try:
stype = data['stype']
step = data['step']
package = base64.b64decode(data['package'])
except (KeyError, TypeError):
traceback.print_exc()
print 'Malformed content: ' + repr(data)
self.real_send_response(
request, 400, { 'error': 'invalid_params' })
return True
if self.current_session:
if client_id != self.current_session.get_client_id():
self.real_send_response(
request, 500, { 'error': 'in_session' })
return True
if stype != self.current_session.get_stype():
self.real_send_response(
request, 500, { 'error': 'invalid_stype' })
return True
else:
if stype not in self.session_handlers:
self.real_send_response(
request, 500, { 'error': 'invalid_stype' })
return True
self.current_session = self.session_handlers[stype](client_id)
try:
output_package = self.current_session.do_step(step, package)
except InvalidStepError:
self.real_send_response(
request, 500, { 'error': 'invalid_step' })
return True
except InvalidPackageError:
self.real_send_response(
request, 500, { 'error': 'invalid_step' })
return True
return_obj = {
'stype' : stype,
'step' : step,
'package': base64.b64encode(output_package)}
self.real_send_response(
request, 200, json.dumps(return_obj))
self.post_session_cancel()
if stype != self.current_session.get_stype():
self.real_send_response(request, 500, {'error': 'invalid_stype'})
return True
@extract_encryption_params
@post_only
@wifi_provisioning
def do_session_cancel(self, request, response_func, client_id,
encrypted):
if client_id == self.current_session.client_id:
self.current_session = None
if self.session_cancel_callback:
self.session_cancel_callback.cancel()
else:
self.real_send_response(
request, 400, { 'error': 'invalid_client_id' })
else:
if stype not in self.session_handlers:
self.real_send_response(request, 500, {'error': 'invalid_stype'})
return True
@extract_encryption_params
@post_only
@wifi_provisioning
def do_session_api(self, request, response_func, client_id, encrypted):
if not encrypted:
response_func(400, { 'error': 'encryption_required' })
return True
if (not self.current_session or
client_id != self.current_session.client_id):
response_func(405, { 'error': 'invalid_client_id' })
return True
self.current_session = self.session_handlers[stype](client_id)
try:
output_package = self.current_session.do_step(step, package)
except self.InvalidStepError:
self.real_send_response(request, 500, {'error': 'invalid_step'})
return True
except self.InvalidPackageError:
self.real_send_response(request, 500, {'error': 'invalid_step'})
return True
return_obj = {
'stype': stype,
'step': step,
'package': base64.b64encode(output_package)
}
self.real_send_response(request, 200, json.dumps(return_obj))
self.post_session_cancel()
return True
@extract_encryption_params
@post_only
@wifi_provisioning
def do_session_cancel(self, request, unused_response_func, client_id,
unused_encrypted):
if client_id == self.current_session.client_id:
self.current_session = None
if self.session_cancel_callback:
self.session_cancel_callback.cancel()
else:
self.real_send_response(request, 400, {'error': 'invalid_client_id'})
return True
@extract_encryption_params
@post_only
@wifi_provisioning
def do_session_api(self, request, response_func, client_id, encrypted):
"""Handles /privet/v2/session/api requests."""
if not encrypted:
response_func(400, {'error': 'encryption_required'})
return True
if not self.current_session or client_id != self.current_session.client_id:
response_func(405, {'error': 'invalid_client_id'})
return True
try:
decrypted = self.current_session.decrypt(request.body)
except self.EncryptionError:
response_func(415, {'error': 'decryption_failed'})
return True
def encrypted_response_func(code, data):
if 'error' in data:
self.encrypted_send_response(request, code, data)
else:
self.encrypted_send_response(request, code, {
'api': decrypted['api'],
'response': data
})
if ('api' not in decrypted or 'request' not in decrypted or
type(decrypted['request']) != dict):
print 'Invalid params in API stage'
encrypted_response_func(400, {'error': 'invalid_params'})
return True
if decrypted['api'] in self.secure_handlers:
self.secure_handlers[decrypted['api']](request,
encrypted_response_func,
decrypted['request'])
else:
encrypted_response_func(400, {'error': 'unknown_api'})
self.post_session_cancel()
return True
def get_insecure_api_handler(self, handler):
def inner(request, func):
return self.insecure_api_handler(request, func, handler)
return inner
try:
decrypted = self.current_session.decrypt(request.body)
except EncryptionError:
response_func(415, { 'error': 'decryption_failed' })
return True
def encrypted_response_func(code, data):
if 'error' in data:
self.encrypted_send_response(request, code, data)
else:
self.encrypted_send_response(request, code, {
'api': decrypted['api'],
'response': data})
@post_only
def insecure_api_handler(self, request, response_func, handler):
real_params = json.loads(request.body) if request.body else {}
handler(request, response_func, real_params)
return True
def do_secure_status(self, unused_request, response_func, unused_params):
"""Handles /privet/v2/setup/status requests."""
setup = {
'registration': {
'required': True
},
'wifi': {
'required': True
}
}
if self.on_wifi:
setup['wifi']['status'] = 'complete'
setup['wifi']['ssid'] = '' # TODO(noamsml): Add SSID to status
else:
setup['wifi']['status'] = 'available'
if self.cloud_device.get_device_id():
setup['registration']['status'] = 'complete'
setup['registration']['id'] = self.cloud_device.get_device_id()
else:
setup['registration']['status'] = 'available'
response_func(200, setup)
def do_secure_setup_start(self, unused_request, response_func, params):
"""Handles /privet/v2/setup/start requests."""
has_wifi = False
token = None
try:
if 'wifi' in params:
has_wifi = True
ssid = params['wifi']['ssid']
passw = params['wifi']['passphrase']
if 'registration' in params:
token = params['registration']['ticketID']
except KeyError:
print 'Invalid params in bootstrap stage'
response_func(400, {'error': 'invalid_params'})
return
if has_wifi:
self.wifi_handler.switch_to_wifi(ssid, passw, token)
elif token:
self.cloud_device.register(token)
else:
response_func(400, {'error': 'invalid_params'})
return
self.do_secure_status(unused_request, response_func, params)
def do_secure_setup_cancel(self, request, response_func, params):
pass
if ('api' not in decrypted or 'request' not in decrypted
or type(decrypted['request']) != dict):
print 'Invalid params in API stage'
encrypted_response_func(400, { 'error': 'invalid_params' })
return True
def handle_request(self, request):
def response_func(code, data):
self.real_send_response(request, code, json.dumps(data))
handled = False
if request.path in self.handlers:
handled = self.handlers[request.path](request, response_func)
if not handled:
self.real_send_response(request, 404, {'error': 'Not found'})
def encrypted_send_response(self, request, code, data):
self.real_send_response(request, code,
self.current_session.encrypt(data))
def real_send_response(self, request, code, data):
request.write('HTTP/1.1 %d Maybe OK\n' % code)
request.write('Content-Type: application/json\n')
request.write('Content-Length: %d\n' % len(data))
write_data = '\n%s' % data
request.write(str(write_data))
request.finish()
def device_state(self):
return 'idle'
def get_common_info(self):
return {
'version': '2.0',
'name': 'Sample Device',
'device_state': self.device_state()
}
def post_session_cancel(self):
if self.session_cancel_callback:
self.session_cancel_callback.cancel()
self.session_cancel_callback = self.CancelableClosure(self.session_cancel)
self.ioloop.add_timeout(datetime.timedelta(minutes=2),
self.session_cancel_callback)
if decrypted['api'] in self.secure_handlers:
self.secure_handlers[decrypted['api']](request,
encrypted_response_func,
decrypted['request'])
else:
encrypted_response_func(400, { 'error': 'unknown_api' })
def session_cancel(self):
self.current_session = None
self.post_session_cancel()
return True
def get_insecure_api_handler(self, handler):
return lambda request, response_func: self.insecure_api_handler(
request, response_func, handler)
@post_only
def insecure_api_handler(self, request, response_func, handler):
real_params = json.loads(request.body)
handler(request, response_func, real_params)
return True
def do_secure_setup(self, request, response_func, params):
setup_handlers = {
'start': self.do_setup_start,
'cancel': self.do_setup_cancel }
if not 'action' in params:
response_func(400, { 'error': 'invalid_params' })
return
if params['action'] not in setup:
response_func(400, { 'error': 'invalid_action' })
return
setup[params['action']](request, response_func, params)
def do_secure_status(self, request, response_func, params):
setup = {
'registration' : {
'required' : True
},
'wifi' : {
'required' : True
}
}
if self.on_wifi:
setup['wifi']['status'] = 'complete'
setup['wifi']['ssid'] = '' # TODO(noamsml): Add SSID to status
else:
setup['wifi']['status'] = 'available'
if self.cloud_device.get_device_id():
setup['registration']['status'] = 'complete'
setup['registration']['id'] = self.cloud_device.get_device_id()
else:
specific_info['setup']['registration'] = 'available'
def do_setup_start(self, request, response_func, params):
has_wifi = False
token = None
# WifiHandler.Delegate implementation
def on_wifi_connected(self, token):
self.mdns_wrapper.set_setup_name(None)
self.cloud_device.try_start(token)
self.on_wifi = True
try:
if 'wifi' in params:
has_wifi = True
ssid = params['wifi']['ssid']
passw = params['wifi']['passphrase']
def on_device_started(self):
self.mdns_wrapper.set_id(self.cloud_device.get_device_id())
if 'registration' in params:
token = params['registration']['ticketID']
except KeyError:
print 'Invalid params in bootstrap stage'
response_func(400, { 'error': 'invalid_params' })
return
response_func(200, { 'ssid' : ssid })
if has_wifi:
self.wifi_handler.switch_to_wifi(ssid, passw, token)
elif token:
self.cloud_device.register(token)
else:
response_func(400, { 'error': 'invalid_params' })
def do_setup_cancel(self, request, response_func, params):
pass
def handle_request(self, request):
def response_func(code, data):
self.real_send_response(request, code, json.dumps(data))
handled = False
if request.path in self.handlers:
handled = self.handlers[request.path](request, response_func)
if not handled:
self.real_send_response(request, 404,
{ 'error': 'Not found' })
def encrypted_send_response(self, request, code, json):
self.real_send_response(request, code,
self.current_session.encrypt(json))
def real_send_response(self, request, code, data):
request.write('HTTP/1.1 %d Maybe OK\n' % code)
request.write('Content-Type: application/json\n')
request.write('Content-Length: %d\n' % len(data))
write_data = '\n%s' % data
request.write(str(write_data));
request.finish()
def device_state(self):
return 'idle'
def get_common_info(self):
return { 'version' : '2.0',
'name' : 'Sample Device',
'device_state' : self.device_state() }
def post_session_cancel(self):
if self.session_cancel_callback:
self.session_cancel_callback.cancel()
self.session_cancel_callback = CancelableClosure(self.session_cancel)
self.ioloop.add_timeout(datetime.timedelta(minutes=2),
self.session_cancel_callback)
def session_cancel(self):
self.current_session = None
# WifiHandler.Delegate implementation
def on_wifi_connected(self, token):
self.mdns_wrapper.set_setup_name(None)
self.cloud_device.try_start(token)
self.on_wifi = True
def on_device_started(self):
self.mdns_wrapper.set_id(self.cloud_device.get_device_id())
def on_device_stopped(self):
pass
def stop(self):
self.wifi_handler.stop()
self.cloud_device.stop()
def on_device_stopped(self):
pass
state = State()
state.load()
def stop(self):
self.wifi_handler.stop()
self.cloud_device.stop()
ioloop = IOLoop.instance()
def main():
state = State()
state.load()
handler = WebRequestHandler(ioloop, state)
handler.start()
ioloop = IOLoop.instance()
def logic_stop():
handler = WebRequestHandler(ioloop, state)
handler.start()
def logic_stop():
handler.stop()
atexit.register(logic_stop)
server = HTTPServer(handler.handle_request)
server.listen(8080)
atexit.register(logic_stop)
ioloop.start()
server = HTTPServer(handler.handle_request)
server.listen(8080)
ioloop.start()
if __name__ == '__main__':
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment