diff options
author | vitalybuka@chromium.org <vitalybuka@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-20 06:42:42 +0000 |
---|---|---|
committer | vitalybuka@chromium.org <vitalybuka@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-20 06:42:42 +0000 |
commit | 7b6f5dd4a511e98a8201e8084cba52ba1af1469e (patch) | |
tree | a5e45552326af1b86a1c4339a3d907e25f4d288c /components/cloud_devices | |
parent | 938f37f79f5d74702420e48210afecfdb210f198 (diff) | |
download | chromium_src-7b6f5dd4a511e98a8201e8084cba52ba1af1469e.zip chromium_src-7b6f5dd4a511e98a8201e8084cba52ba1af1469e.tar.gz chromium_src-7b6f5dd4a511e98a8201e8084cba52ba1af1469e.tar.bz2 |
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
Diffstat (limited to 'components/cloud_devices')
-rwxr-xr-x | components/cloud_devices/tools/prototype/prototype.py | 1644 |
1 files changed, 866 insertions, 778 deletions
diff --git a/components/cloud_devices/tools/prototype/prototype.py b/components/cloud_devices/tools/prototype/prototype.py index 2e2d923..4b3d074 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() |