| Index: components/cloud_devices/tools/prototype/prototype.py
|
| diff --git a/components/cloud_devices/tools/prototype/prototype.py b/components/cloud_devices/tools/prototype/prototype.py
|
| index 2e2d92330545759d14f6a623078be9a74557b131..4b3d074536855b3af9625811cbd28d04f457df88 100755
|
| --- a/components/cloud_devices/tools/prototype/prototype.py
|
| +++ b/components/cloud_devices/tools/prototype/prototype.py
|
| @@ -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()
|
|
|