summaryrefslogtreecommitdiffstats
path: root/components/cloud_devices
diff options
context:
space:
mode:
authorvitalybuka@chromium.org <vitalybuka@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-19 21:20:49 +0000
committervitalybuka@chromium.org <vitalybuka@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-19 21:20:49 +0000
commit2f3f1631ec40e6eddff1c31ef4daee9dedb73d11 (patch)
tree211e165803e8398d1143a1391b3016ff180eb905 /components/cloud_devices
parent9d9a1e68d5fd585125166605048355f7d54b3db5 (diff)
downloadchromium_src-2f3f1631ec40e6eddff1c31ef4daee9dedb73d11.zip
chromium_src-2f3f1631ec40e6eddff1c31ef4daee9dedb73d11.tar.gz
chromium_src-2f3f1631ec40e6eddff1c31ef4daee9dedb73d11.tar.bz2
Draft of Google Cloud Print prototype.
NOTRY=true Review URL: https://codereview.chromium.org/291893003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@271486 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components/cloud_devices')
-rwxr-xr-xcomponents/cloud_devices/tools/prototype/prototype.py877
1 files changed, 877 insertions, 0 deletions
diff --git a/components/cloud_devices/tools/prototype/prototype.py b/components/cloud_devices/tools/prototype/prototype.py
new file mode 100755
index 0000000..c678172
--- /dev/null
+++ b/components/cloud_devices/tools/prototype/prototype.py
@@ -0,0 +1,877 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# 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.
+
+import subprocess
+import json
+import tornado.httpserver
+import tornado.ioloop
+import time
+import atexit
+import apiclient.errors
+from apiclient.discovery import build_from_document
+from oauth2client.client import OAuth2WebServerFlow
+from oauth2client.file import Storage
+import oauth2client.client
+import datetime
+import httplib2
+import os
+import traceback
+import urlparse
+import base64
+
+# API Client ID and Secret for Device manufacturers.
+_OAUTH_CLIENT_ID = ''
+_OAUTH_SECRET = ''
+
+_OAUTH_SCOPE = 'https://www.googleapis.com/auth/clouddevices'
+_API_KEY = ''
+
+DEVICE_DRAFT = {
+ "systemName": "LEDFlasher",
+ "deviceKind": "vendor",
+ "displayName": "LED Flasher",
+ "channel":
+ {
+ "supportedType": "xmpp"
+ },
+ "commands":
+ {
+ "base":
+ {
+ "vendorCommands":
+ [
+ {
+ "name": "flashLED",
+ "parameter" : [{
+ "name": "times",
+ "type": "int"
+ }]
+ }
+ ]
+ }
+ }
+ }
+
+wpa_supplicant_cmd = "wpa_supplicant -Dwext -iwlan0 -cwpa_supplicant.conf"
+ifconfig_cmd = "ifconfig wlan0 192.168.0.3"
+hostapd_cmd = "hostapd hostapd-min.conf"
+dhclient_release = "dhclient -r wlan0"
+dhclient_renew = "dhclient wlan0"
+dhcpd_cmd = "udhcpd -f /etc/udhcpd.conf"
+
+
+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"
+}"""
+
+
+led_path = "/sys/class/leds/ath9k_htc-phy0/"
+
+class DeviceUnregisteredError(Exception):
+ 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
+
+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()
+
+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
+
+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 = 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 = 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))
+
+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()
+
+
+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"
+
+
+def setup_fake():
+ print "Called setup"
+
+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)
+ 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()
+ f = open("clouddevices.json")
+ discovery = f.read()
+ f.close()
+ self.gcd = build_from_document(
+ discovery, developerKey=_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. Shutting down."
+ def register(self, token):
+ resource = {
+ 'deviceDraft': DEVICE_DRAFT,
+ 'oauthClientId': _OAUTH_CLIENT_ID
+ }
+
+ self.gcd.registrationTickets().patch(registrationTicketId=token,
+ body=resource).execute()
+
+ finalTicket = self.gcd.registrationTickets().finalize(
+ registrationTicketId=token).execute()
+
+ authorization_code = finalTicket['robotAccountAuthorizationCode']
+ flow = OAuth2WebServerFlow(
+ _OAUTH_CLIENT_ID, _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
+
+ self.run_device()
+ def run_device(self):
+ self.credentials.authorize(self.http)
+
+ try:
+ dev=self.gcd.devices().get(deviceId=self.device_id).execute()
+ except apiclient.errors.HttpError, e:
+ # Pretty good indication the device was deleted
+ if e.resp.status == 404:
+ raise DeviceUnregisteredError()
+ except oauth2client.client.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:
+ vendorCommand = command["base"]["vendorCommand"]
+ vendorCommandName = vendorCommand["name"]
+
+ if "parameters" in vendorCommand:
+ for parameter in vendorCommand["parameters"]:
+ value = None
+ if "intValue" in parameter:
+ value = int(parameter["intValue"])
+ elif "stringValue" in parameter:
+ value = parameter["stringValue"]
+ else:
+ pass
+
+ args[parameter["name"]] = value
+ except KeyError:
+ print "Could not parse vendor command ",
+ print repr(command)
+ vendorCommandName = None
+
+ if vendorCommandName:
+ self.command_handler.handle_command(vendorCommandName, args)
+
+ 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 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 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 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 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
+
+ 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 InvalidStepError(Exception):
+ pass
+
+class InvalidPackageError(Exception):
+ pass
+
+class EncryptionError(Exception):
+ pass
+
+class CancelableClosure:
+ def __init__(self, function):
+ self.function = function
+ def __call__(self):
+ if self.function:
+ return self.function
+ return None
+ def cancel(self):
+ self.function = None
+
+
+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.key = package
+ return self.key
+ def decrypt(self, cyphertext):
+ return json.loads(cyphertext[len(self.key):])
+ def encrypt(self, plain_data):
+ return self.key + json.dumps(plain_data)
+ def get_client_id(self):
+ return client_id
+ def get_stype(self):
+ return "dummy"
+
+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 = {
+ "/privet/ping": self.do_ping,
+ "/privet/info": self.do_info,
+ "/privet/wifi/switch": self.do_wifi_switch,
+ "/privet/session/handshake": self.do_session_handshake,
+ "/privet/session/cancel": self.do_session_cancel,
+ "/privet/session/api": self.do_session_api,
+ "/privet/v2/setup": self.get_insecure_api_handler(
+ self.do_secure_setup)
+ }
+
+ self.current_session = None
+ self.session_cancel_callback = None
+ self.session_handlers = {
+ "dummy" : DummySession
+ }
+
+ self.secure_handlers = {
+ "/privet/v2/setup" : self.do_secure_setup
+ }
+ 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",
+ "setup" : {
+ }
+ }
+
+ if self.on_wifi:
+ specific_info["setup"]["wifi"] = "complete";
+ else:
+ specific_info["setup"]["wifi"] = "available";
+
+ if self.cloud_device.get_device_id():
+ specific_info["setup"]["registration"] = "complete"
+ else:
+ specific_info["setup"]["registration"] = "available"
+
+ 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)
+ 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()
+ 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" })
+ 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
+
+ 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})
+
+ 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):
+ 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_setup_start(self, request, response_func, params):
+ 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
+
+ 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()
+
+state = State()
+state.load()
+
+ioloop = tornado.ioloop.IOLoop.instance()
+
+
+handler = WebRequestHandler(ioloop, state)
+handler.start()
+
+def logic_stop():
+ handler.stop()
+
+atexit.register(logic_stop)
+
+server = tornado.httpserver.HTTPServer(handler.handle_request)
+server.listen(8080)
+ioloop.start()