├── .gitignore ├── COPYING ├── README.md ├── doc └── services.md ├── jumpstart.py └── templates ├── app-with-tablet ├── .gitignore ├── app │ ├── .metadata │ ├── app-with-tablet.pml │ ├── behavior.xar │ ├── html │ │ ├── css │ │ │ └── style.css │ │ ├── index.html │ │ └── js │ │ │ ├── jquery-1.11.0.min.js │ │ │ ├── main.js │ │ │ └── robotutils.js │ ├── icon.png │ ├── manifest.xml │ ├── scripts │ │ ├── myservice.py │ │ └── stk │ │ │ ├── __init__.py │ │ │ ├── events.py │ │ │ ├── logging.py │ │ │ ├── runner.py │ │ │ └── services.py │ └── translations │ │ └── translation_en_US.ts ├── debug │ ├── index.html │ └── js │ │ ├── jquery-1.11.0.min.js │ │ └── jquery.cookie.js ├── serve.py └── testrun.py ├── dialog-service ├── .gitignore ├── app │ ├── .metadata │ ├── dialog-service.pml │ ├── dialog-service │ │ ├── dialog-service.dlg │ │ └── dialog-service_enu.top │ ├── icon.png │ ├── manifest.xml │ ├── scripts │ │ ├── myservice.py │ │ └── stk │ │ │ ├── __init__.py │ │ │ ├── events.py │ │ │ ├── logging.py │ │ │ ├── runner.py │ │ │ └── services.py │ └── testrun │ │ └── behavior.xar └── testrun.py ├── python-service ├── .gitignore ├── app │ ├── .metadata │ ├── icon.png │ ├── manifest.xml │ ├── python-service.pml │ └── scripts │ │ ├── myservice.py │ │ └── stk │ │ ├── __init__.py │ │ ├── events.py │ │ ├── logging.py │ │ ├── runner.py │ │ └── services.py └── testrun.py ├── pythonapp ├── .gitignore └── app │ ├── .metadata │ ├── behavior.xar │ ├── icon.png │ ├── manifest.xml │ ├── pythonapp.pml │ └── scripts │ ├── main.py │ └── stk │ ├── __init__.py │ ├── events.py │ ├── logging.py │ ├── runner.py │ └── services.py ├── service-tabletpage ├── .gitignore ├── app │ ├── .metadata │ ├── behavior.xar │ ├── html │ │ ├── css │ │ │ └── style.css │ │ ├── index.html │ │ └── js │ │ │ ├── jquery-1.11.0.min.js │ │ │ ├── main.js │ │ │ └── robotutils.js │ ├── icon.png │ ├── manifest.xml │ ├── scripts │ │ ├── myservice.py │ │ └── stk │ │ │ ├── __init__.py │ │ │ ├── events.py │ │ │ ├── logging.py │ │ │ ├── runner.py │ │ │ └── services.py │ ├── service-tabletpage.pml │ └── translations │ │ └── translation_en_US.ts ├── debug │ ├── index.html │ └── js │ │ ├── jquery-1.11.0.min.js │ │ └── jquery.cookie.js ├── serve.py └── testrun.py ├── service-webpage-nao ├── .gitignore ├── app │ ├── .metadata │ ├── html │ │ ├── css │ │ │ └── style.css │ │ ├── index.html │ │ └── js │ │ │ ├── jquery-1.11.0.min.js │ │ │ ├── main.js │ │ │ └── robotutils.qim1.js │ ├── icon.png │ ├── manifest.xml │ ├── scripts │ │ ├── myservice.py │ │ └── stk │ │ │ ├── __init__.py │ │ │ ├── events.py │ │ │ ├── logging.py │ │ │ ├── runner.py │ │ │ └── services.py │ └── service-webpage-nao.pml ├── debug │ ├── index.html │ └── js │ │ ├── jquery-1.11.0.min.js │ │ └── jquery.cookie.js ├── serve.py └── testrun.py ├── simple-tabletpage ├── app │ ├── .metadata │ ├── behavior.xar │ ├── html │ │ ├── css │ │ │ └── style.css │ │ ├── index.html │ │ └── js │ │ │ ├── jquery-1.11.0.min.js │ │ │ ├── main.js │ │ │ └── robotutils.js │ ├── icon.png │ ├── manifest.xml │ └── simple-tabletpage.pml ├── debug │ ├── index.html │ └── js │ │ ├── jquery-1.11.0.min.js │ │ └── jquery.cookie.js └── serve.py └── simple-webpage-nao ├── app ├── .metadata ├── html │ ├── css │ │ └── style.css │ ├── index.html │ └── js │ │ ├── jquery-1.11.0.min.js │ │ ├── main.js │ │ └── robotutils.qim1.js ├── icon.png ├── manifest.xml └── simple-webpage-nao.pml ├── debug ├── index.html └── js │ ├── jquery-1.11.0.min.js │ └── jquery.cookie.js └── serve.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | output/* 3 | 4 | *.pkg 5 | 6 | .DS_Store 7 | 8 | *.idea 9 | .floo 10 | .flooignore 11 | 12 | */bower_components/* 13 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017, SoftBank Robotics Europe SAS 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the SoftBank Robotics Europe SAS nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL SoftBank Robotics Europe SAS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /doc/services.md: -------------------------------------------------------------------------------- 1 | 2 | Clarifying the "service" terminology 3 | ======= 4 | 5 | There are two different entities that are called "services": 6 | 7 | * **NAOqi services** (also called "modules"), that expose an API and are registered to the ServiceDirectory. You can call them with qicli, subscribe to their signals, etc. 8 | 9 | * **systemd services**, that are standalone executables packaged in an Application Package, declared in it's manifest with a `` tag. These are managed by ALServiceManager, who can start and stop them (they will have their own process). For clarity's sake, these are called "**Executables**" in this doc. 10 | 11 | The confusion between the two is increased by the fact that a common pattern is to write an executable whose sole purpose is to run a NAOqi service, and sometimes to identify both with the same name (e.g. both are called “ALFuchsiaBallTracker”). 12 | 13 | Ways of using them in an app: 14 | 15 | * Run a standalone executable during your app, possibly as the only content. This is demonstrated in the `pythonapp` template. 16 | 17 | * Run a NAOqi service during your app (packaged in an executable), but only while the app is running -> this is how `service-tabletpage` is built. 18 | 19 | * Package a NAOqi service running all the time -> this wastes resources, and risks causing hard-to-find bugs, so should only be used for system apps that *really* need to do so. 20 | 21 | 22 | qi.Application 23 | ======= 24 | 25 | 26 | robot_runner**`.init()`** returns a QiApplication object, the same you would get by calling `qi.Application()̀`. 27 | 28 | When your python script is packaged in an application, it should be declared in the manifest with a --qi-url parameter: 29 | 30 | `` 31 | ` ` 32 | ` ` 33 | 34 | However, you can also execute that script directly, locally on your computer (python main.py), or from your favourite editor. 35 | -------------------------------------------------------------------------------- /jumpstart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # PYTHON_ARGCOMPLETE_OK 3 | """ 4 | jumpstart.py 5 | 6 | A script for generating a NAOqi project from a tamplate folder. 7 | """ 8 | 9 | __version__ = "0.1.1" 10 | 11 | __copyright__ = "Copyright 2015-2016, SBRE" 12 | __author__ = 'ekroeger' 13 | __email__ = 'ekroeger@aldebaran.com' 14 | 15 | import os 16 | import shutil 17 | import re 18 | 19 | import distutils.dir_util 20 | 21 | argcomplete = None 22 | try: 23 | import argcomplete 24 | except Exception as e: 25 | pass 26 | 27 | TEMPLATES = "templates" 28 | OUTPUT = "output" 29 | 30 | FILETYPES_TO_REPROCESS = [".xar", ".pml", ".manifest", ".py", ".js", ".json", 31 | ".xml", ".html", ".top", ".dlg"] 32 | 33 | 34 | def rename_in_file(filepath, sourceword, destword): 35 | sourcere = re.compile(r"\b" + sourceword + r"\b") 36 | # Does this pattern even occur in this file? (most of the time, it won't) 37 | with open(filepath) as f: 38 | if not any(sourcere.search(line) for line in f): 39 | return False# We're done here. 40 | # pattern is in the file, so perform replace operation. 41 | 42 | with open(filepath) as f: 43 | temp_filepath = filepath + ".tmp" 44 | out = open(temp_filepath, "w") 45 | for line in f: 46 | out.write(sourcere.sub(destword, line)) 47 | f.close() 48 | out.close() 49 | os.unlink(filepath) 50 | os.rename(temp_filepath, filepath) 51 | return True 52 | 53 | def rename_in_folder(folder, sourceword, destword): 54 | files = list(os.listdir(folder)) 55 | for filename in files: 56 | if filename.startswith("."): 57 | continue 58 | filepath = os.path.join(folder, filename) 59 | # 1) replace everything inside 60 | if os.path.isdir(filepath): 61 | rename_in_folder(filepath, sourceword, destword) 62 | else: 63 | extension = os.path.splitext(filename)[-1].lower() 64 | if extension in FILETYPES_TO_REPROCESS: 65 | if rename_in_file(filepath, sourceword, destword): 66 | print "Renamed inside", filename 67 | # 2) rename if needed 68 | if sourceword in filename: 69 | newfilename = filename.replace(sourceword, destword) 70 | os.rename(filepath, os.path.join(folder, newfilename)) 71 | print "Renamed", filename 72 | 73 | def generate(sourcename, destname, servicename=None): 74 | "Generate a folder based on a template, or add to an existing one." 75 | if not os.path.exists(OUTPUT): 76 | os.mkdir(OUTPUT) 77 | sourcepath = os.path.join(TEMPLATES, sourcename) 78 | destpath = os.path.join(OUTPUT, destname) 79 | if not os.path.exists(sourcepath): 80 | raise Exception, "Template not found: " + repr(sourcename) 81 | project_exists = os.path.exists(destpath) 82 | if project_exists: 83 | shutil.move(destpath, destpath + "_TEMP") 84 | print "Project already exists, only adding new files to it" 85 | shutil.copytree(sourcepath, destpath) 86 | rename_in_folder(destpath, sourcename, destname) 87 | 88 | if servicename: 89 | # script name (used by ALServiceManager) should be underscore 90 | scriptname = servicename.lower() 91 | # service name (used in ServiceDirectory) should be capitalized 92 | if not servicename[0].isupper(): 93 | servicename = servicename.capitalize() 94 | rename_in_folder(destpath, "ALMyService", servicename) 95 | rename_in_folder(destpath, "myservice", scriptname) 96 | 97 | if project_exists: 98 | distutils.dir_util.copy_tree(destpath + "_TEMP", destpath) 99 | shutil.rmtree(destpath + "_TEMP") 100 | print "Done adding to", destname, "from", sourcename, 101 | else: 102 | print "Done generating", destname, "from", sourcename, 103 | 104 | 105 | def test_run(): 106 | #generate("pythonapp", "mytestapp") 107 | generate("service-tabletpage", "servicetestapp", "ALSuperDuperService") 108 | 109 | # Used for argcomplete 110 | class TemplateCompleter(object): 111 | def __init__(self): 112 | self.choices = os.listdir("templates") 113 | 114 | def __call__(self, prefix, **kwargs): 115 | return (c for c in self.choices if c.startswith(prefix)) 116 | 117 | 118 | def run_with_sysargs(): 119 | import argparse 120 | parser = argparse.ArgumentParser(description='Generates a project.') 121 | parser.add_argument('sourcename', type=str, 122 | help='name of source recipe/template').completer = TemplateCompleter() 123 | parser.add_argument('destname', type=str, 124 | help='name of new project to create') 125 | parser.add_argument('servicename', type=str, 126 | help='optional, name of service to create', 127 | nargs='?') 128 | if argcomplete: 129 | argcomplete.autocomplete(parser) 130 | args = parser.parse_args() 131 | if (args.servicename): 132 | generate(args.sourcename, args.destname, args.servicename) 133 | else: 134 | generate(args.sourcename, args.destname) 135 | 136 | if __name__ == "__main__": 137 | #test_run() 138 | #rename_in_folder("./output/basicparams/", "basicparams", "volumeslider") 139 | run_with_sysargs() 140 | 141 | -------------------------------------------------------------------------------- /templates/app-with-tablet/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/.metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/app-with-tablet/app/.metadata -------------------------------------------------------------------------------- /templates/app-with-tablet/app/app-with-tablet.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/behavior.xar: -------------------------------------------------------------------------------- 1 | media/images/box/root.pngmedia/images/box/root.png -------------------------------------------------------------------------------- /templates/app-with-tablet/app/html/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 3 | } 4 | 5 | body{ 6 | margin: 0px; 7 | background-color: black; 8 | } 9 | 10 | .centered { 11 | position: absolute; 12 | top: 50%; 13 | left: 50%; 14 | margin-right: -50%; 15 | transform: translate(-50%, -50%); 16 | text-align: center; 17 | } 18 | 19 | .tabletsized { 20 | background-color: lightblue; 21 | width: 1280px; 22 | height: 800px; 23 | position: absolute; 24 | } 25 | 26 | #exit { 27 | font-size: 100px; 28 | position: absolute; 29 | top: 0px; 30 | right: 15px; 31 | } 32 | 33 | #biglabel { 34 | font-size: 300px; 35 | } 36 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/html/js/main.js: -------------------------------------------------------------------------------- 1 | var KEY_TABLETSETATE = "app-with-tablet/TabletState"; 2 | 3 | function updateTabletState(stateJson) { 4 | var state = JSON.parse(stateJson); 5 | if (state.title) { 6 | $("#biglabel").show(); 7 | $("#biglabel").html(state.title); 8 | } else { 9 | $("#biglabel").hide(); 10 | } 11 | } 12 | 13 | RobotUtils.onService(function (ALMemory) { 14 | ALMemory.getData(KEY_TABLETSETATE).then(updateTabletState); 15 | //updateTabletState('{"title": "TEST"}'); 16 | }); 17 | RobotUtils.subscribeToALMemoryEvent(KEY_TABLETSETATE, updateTabletState); 18 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/app-with-tablet/app/icon.png -------------------------------------------------------------------------------- /templates/app-with-tablet/app/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | app-with-tablet 5 | 6 | 7 | en_US 8 | 9 | 10 | en_US 11 | 12 | 13 | 14 | 15 | interactive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/scripts/myservice.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | A sample showing how to make a Python script as an app. 4 | """ 5 | 6 | __version__ = "0.0.8" 7 | 8 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 9 | __author__ = 'YOURNAME' 10 | __email__ = 'YOUREMAIL@aldebaran.com' 11 | 12 | import json 13 | import time 14 | 15 | import stk.runner 16 | import stk.events 17 | import stk.services 18 | import stk.logging 19 | 20 | KEY_TABLETSETATE = "app-with-tablet/TabletState" 21 | 22 | class ALMyService(object): 23 | "A sample standalone app, that demonstrates simple Python usage" 24 | APP_ID = "com.aldebaran.dx-team-presentation" 25 | def __init__(self, qiapp): 26 | self.qiapp = qiapp 27 | self.events = stk.events.EventHelper(qiapp.session) 28 | self.s = stk.services.ServiceCache(qiapp.session) 29 | self.logger = stk.logging.get_logger(qiapp.session, self.APP_ID) 30 | 31 | def show(self, command): 32 | self.events.set(KEY_TABLETSETATE, json.dumps(command)) 33 | 34 | def on_start(self): 35 | "Ask to be touched, waits, and exits." 36 | self.show({"title": "intro"}) 37 | self.s.ALTextToSpeech.say("This is the first page") 38 | time.sleep(1.0) 39 | self.show({"title": "other"}) 40 | self.s.ALTextToSpeech.say("This is another page") 41 | time.sleep(1.0) 42 | self.show({"title": "last"}) 43 | self.s.ALTextToSpeech.say("This is the last page, I'm done") 44 | self.stop() 45 | 46 | def stop(self): 47 | "Standard way of stopping the application." 48 | self.qiapp.stop() 49 | 50 | def on_stop(self): 51 | "Cleanup" 52 | self.show({}) 53 | self.logger.info("Application finished.") 54 | self.events.clear() 55 | 56 | if __name__ == "__main__": 57 | stk.runner.run_service(ALMyService) 58 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/scripts/stk/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | STK - A collection of libraries useful for making apps with NAOqi. 3 | """ 4 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/scripts/stk/events.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.events.py 3 | 4 | Provides misc. wrappers for ALMemory and Signals (using the same syntax for 5 | handling both). 6 | """ 7 | 8 | __version__ = "0.1.1" 9 | 10 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 11 | __author__ = 'ekroeger' 12 | __email__ = 'ekroeger@aldebaran.com' 13 | 14 | import qi 15 | 16 | 17 | def on(*keys): 18 | """Decorator for connecting a callback to one or several events. 19 | 20 | Usage: 21 | 22 | class O: 23 | @on("MyMemoryKey") 24 | def my_callback(self,value): 25 | print "I was called!", value 26 | 27 | o = O() 28 | events = EventsHelper() 29 | events.connect_decorators(o) 30 | 31 | After that, whenever MyMemoryKey is raised, o.my_callback will be called 32 | with the value. 33 | """ 34 | def decorator(func): 35 | func.__event_keys__ = keys 36 | return func 37 | return decorator 38 | 39 | 40 | class EventHelper(object): 41 | "Helper for ALMemory; takes care of event connections so you don't have to" 42 | 43 | def __init__(self, session=None): 44 | self.session = None 45 | self.almemory = None 46 | if session: 47 | self.init(session) 48 | self.handlers = {} # a handler is (subscriber, connections) 49 | self.subscriber_names = {} 50 | self.wait_value = None 51 | self.wait_promise = None 52 | 53 | def init(self, session): 54 | "Sets the NAOqi session, if it wasn't passed to the constructor" 55 | self.session = session 56 | self.almemory = session.service("ALMemory") 57 | 58 | def connect_decorators(self, obj): 59 | "Connects all decorated methods of target object." 60 | for membername in dir(obj): 61 | member = getattr(obj, membername) 62 | if hasattr(member, "__event_keys__"): 63 | for event in member.__event_keys__: 64 | self.connect(event, member) 65 | 66 | def connect(self, event, callback): 67 | """Connects an ALMemory event or signal to a callback. 68 | 69 | Note that some events trigger side effects in services when someone 70 | subscribes to them (such as WordRecognized). Those will *not* be 71 | triggered by this function, for those, use .subscribe(). 72 | """ 73 | if event not in self.handlers: 74 | if "." in event: 75 | # if we have more than one ".": 76 | service_name, signal_name = event.split(".") 77 | service = self.session.service(service_name) 78 | self.handlers[event] = (getattr(service, signal_name), []) 79 | else: 80 | # It's a "normal" ALMemory event. 81 | self.handlers[event] = ( 82 | self.almemory.subscriber(event).signal, []) 83 | signal, connections = self.handlers[event] 84 | connection_id = signal.connect(callback) 85 | connections.append(connection_id) 86 | return connection_id 87 | 88 | def subscribe(self, event, attachedname, callback): 89 | """Subscribes to an ALMemory event so as to notify providers. 90 | 91 | This is necessary for things like WordRecognized.""" 92 | connection_id = self.connect(event, callback) 93 | dummyname = "on_" + event.replace("/", "") 94 | self.almemory.subscribeToEvent(event, attachedname, dummyname) 95 | self.subscriber_names[event] = attachedname 96 | return connection_id 97 | 98 | def disconnect(self, event, connection_id=None): 99 | "Disconnects a connection, or all if no connection is specified." 100 | if event in self.handlers: 101 | signal, connections = self.handlers[event] 102 | if connection_id: 103 | if connection_id in connections: 104 | signal.disconnect(connection_id) 105 | connections.remove(connection_id) 106 | else: 107 | # Didn't specify a connection ID: remove all 108 | for connection_id in connections: 109 | signal.disconnect(connection_id) 110 | del connections[:] 111 | if event in self.subscriber_names: 112 | name = self.subscriber_names[event] 113 | self.almemory.unsubscribeToEvent(event, name) 114 | del self.subscriber_names[event] 115 | 116 | def clear(self): 117 | "Disconnect all connections" 118 | for event in list(self.handlers): 119 | self.disconnect(event) 120 | 121 | def get(self, key): 122 | "Gets ALMemory value." 123 | return self.almemory.getData(key) 124 | 125 | def get_int(self, key): 126 | "Gets ALMemory value, cast as int." 127 | try: 128 | return int(self.get(key)) 129 | except RuntimeError: 130 | # Key doesn't exist 131 | return 0 132 | except ValueError: 133 | # Key exists, but can't be parsed to int 134 | return 0 135 | 136 | def set(self, key, value): 137 | "Sets value of ALMemory key." 138 | return self.almemory.raiseEvent(key, value) 139 | 140 | def remove(self, key): 141 | "Remove key from ALMemory." 142 | try: 143 | self.almemory.removeData(key) 144 | except RuntimeError: 145 | pass 146 | 147 | def _on_wait_event(self, value): 148 | "Internal - callback for an event." 149 | if self.wait_promise: 150 | self.wait_promise.setValue(value) 151 | self.wait_promise = None 152 | 153 | def _on_wait_signal(self, *args): 154 | "Internal - callback for a signal." 155 | if self.wait_promise: 156 | self.wait_promise.setValue(args) 157 | self.wait_promise = None 158 | 159 | def cancel_wait(self): 160 | "Cancel the current wait (raises an exception in the waiting thread)" 161 | if self.wait_promise: 162 | self.wait_promise.setCanceled() 163 | self.wait_promise = None 164 | 165 | def wait_for(self, event, subscribe=False): 166 | """Block until a certain event is raised, and returns it's value. 167 | 168 | If you pass subscribe=True, ALMemory.subscribeToEvent will be called 169 | (sometimes necessary for side effects, i.e. WordRecognized). 170 | 171 | This will block a thread so you should avoid doing this too often! 172 | """ 173 | if self.wait_promise: 174 | # there was already a wait in progress, cancel it! 175 | self.wait_promise.setCanceled() 176 | self.wait_promise = qi.Promise() 177 | if subscribe: 178 | connection_id = self.subscribe(event, "EVENTHELPER", 179 | self._on_wait_event) 180 | elif "." in event: # it's a signal 181 | connection_id = self.connect(event, self._on_wait_signal) 182 | else: 183 | connection_id = self.connect(event, self._on_wait_event) 184 | try: 185 | result = self.wait_promise.future().value() 186 | finally: 187 | self.disconnect(event, connection_id) 188 | return result 189 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/scripts/stk/logging.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.logging.py 3 | 4 | Utility library for logging with qi. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | import functools 14 | import traceback 15 | 16 | import qi 17 | 18 | 19 | def get_logger(session, app_id): 20 | """Returns a qi logger object.""" 21 | logger = qi.logging.Logger(app_id) 22 | try: 23 | qicore = qi.module("qicore") 24 | log_manager = session.service("LogManager") 25 | provider = qicore.createObject("LogProvider", log_manager) 26 | log_manager.addProvider(provider) 27 | except RuntimeError: 28 | # no qicore, we're not running on a robot, it doesn't matter 29 | pass 30 | except AttributeError: 31 | # old version of NAOqi - logging will probably not work. 32 | pass 33 | return logger 34 | 35 | 36 | def log_exceptions(func): 37 | """Catches all exceptions in decorated method, and prints them. 38 | 39 | Attached function must be on an object with a "logger" member. 40 | """ 41 | @functools.wraps(func) 42 | def wrapped(self, *args): 43 | try: 44 | return func(self, *args) 45 | except Exception as exc: 46 | self.logger.error(traceback.format_exc()) 47 | raise exc 48 | return wrapped 49 | 50 | 51 | def log_exceptions_and_return(default_value): 52 | """If an exception occurs, print it and return default_value. 53 | 54 | Attached function must be on an object with a "logger" member. 55 | """ 56 | def decorator(func): 57 | @functools.wraps(func) 58 | def wrapped(self, *args): 59 | try: 60 | return func(self, *args) 61 | except Exception: 62 | self.logger.error(traceback.format_exc()) 63 | return default_value 64 | return wrapped 65 | return decorator 66 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/scripts/stk/runner.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.runner.py 3 | 4 | A helper library for making simple standalone python scripts as apps. 5 | 6 | Wraps some NAOqi and system stuff, you could do all this by directly using the 7 | Python SDK, these helper functions just isolate some frequently used/hairy 8 | bits so you don't have them mixed in your logic. 9 | """ 10 | 11 | __version__ = "0.1.3" 12 | 13 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 14 | __author__ = 'ekroeger' 15 | __email__ = 'ekroeger@aldebaran.com' 16 | 17 | import sys 18 | import qi 19 | from distutils.version import LooseVersion 20 | 21 | # 22 | # Helpers for making sure we have a robot to connect to 23 | # 24 | 25 | 26 | def check_commandline_args(description): 27 | "Checks whether command-line parameters are enough" 28 | import argparse 29 | parser = argparse.ArgumentParser(description=description) 30 | parser.add_argument('--qi-url', help='connect to specific NAOqi instance') 31 | 32 | args = parser.parse_args() 33 | return args 34 | 35 | 36 | def is_on_robot(): 37 | "Returns whether this is being executed on an Aldebaran robot." 38 | import platform 39 | return "aldebaran" in platform.platform() 40 | 41 | 42 | def get_debug_robot(): 43 | "Returns IP address of debug robot, complaining if not found" 44 | try: 45 | import qiq.config 46 | qiqrobot = qiq.config.defaultHost() 47 | if qiqrobot: 48 | robot = raw_input( 49 | "connect to which robot? (default is {0}) ".format(qiqrobot)) 50 | if robot: 51 | return robot 52 | else: 53 | return qiqrobot 54 | else: 55 | print "qiq found, but it has no default robot configured." 56 | except ImportError: 57 | # qiq not installed 58 | print "qiq not installed (you can use it to set a default robot)." 59 | return raw_input("connect to which robot? ") 60 | 61 | 62 | def init(qi_url=None): 63 | "Returns a QiApplication object, possibly with interactive input." 64 | if qi_url: 65 | sys.argv.extend(["--qi-url", qi_url]) 66 | else: 67 | args = check_commandline_args('Run the app.') 68 | if bool(args.qi_url): 69 | qi_url = args.qi_url 70 | elif not is_on_robot(): 71 | print "no --qi-url parameter given; interactively getting debug robot." 72 | debug_robot = get_debug_robot() 73 | if debug_robot: 74 | sys.argv.extend(["--qi-url", debug_robot]) 75 | qi_url = debug_robot 76 | else: 77 | raise RuntimeError("No robot, not running.") 78 | 79 | qiapp = None 80 | sys.argv[0] = str(sys.argv[0]) 81 | 82 | # In versions bellow 2.3, look for --qi-url in the arguemnts and call accordingly the Application 83 | if qi_url and hasattr(qi, "__version__") and LooseVersion(qi.__version__) < LooseVersion("2.3"): 84 | qiapp = qi.Application(url="tcp://"+qi_url+":9559") 85 | # In versions greater than 2.3 the ip can simply be passed through argv[0] 86 | else: 87 | # In some environments sys.argv[0] has unicode, which qi rejects 88 | qiapp = qi.Application() 89 | 90 | qiapp.start() 91 | return qiapp 92 | 93 | 94 | # Main runner 95 | 96 | def run_activity(activity_class, service_name=None): 97 | """Instantiate the given class, and runs it. 98 | 99 | The given class must take a qiapplication object as parameter, and may also 100 | have on_start and on_stop methods, that will be called before and after 101 | running it.""" 102 | qiapp = init() 103 | activity = activity_class(qiapp) 104 | service_id = None 105 | 106 | try: 107 | # if it's a service, register it 108 | if service_name: 109 | # Note: this will fail if there is already a service. Unregistering 110 | # it would not be a good practice, because it's process would still 111 | # be running. 112 | service_id = qiapp.session.registerService(service_name, activity) 113 | 114 | if hasattr(activity, "on_start"): 115 | def handle_on_start_done(on_start_future): 116 | "Custom callback, for checking errors" 117 | if on_start_future.hasError(): 118 | try: 119 | msg = "Error in on_start(), stopping application: %s" \ 120 | % on_start_future.error() 121 | if hasattr(activity, "logger"): 122 | activity.logger.error(msg) 123 | else: 124 | print msg 125 | finally: 126 | qiapp.stop() 127 | qi.async(activity.on_start).addCallback(handle_on_start_done) 128 | 129 | # Run the QiApplication, which runs until someone calls qiapp.stop() 130 | qiapp.run() 131 | 132 | finally: 133 | # Cleanup 134 | if hasattr(activity, "on_stop"): 135 | # We need a qi.async call so that if the class is single threaded, 136 | # it will wait for callbacks to be finished. 137 | qi.async(activity.on_stop).wait() 138 | if service_id: 139 | qiapp.session.unregisterService(service_id) 140 | 141 | 142 | def run_service(service_class, service_name=None): 143 | """Instantiate the given class, and registers it as a NAOqi service. 144 | 145 | The given class must take a qiapplication object as parameter, and may also 146 | have on_start and on_stop methods, that will be called before and after 147 | running it. 148 | 149 | If the service_name parameter is not given, the classes' name will be used. 150 | """ 151 | if not service_name: 152 | service_name = service_class.__name__ 153 | run_activity(service_class, service_name) 154 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/scripts/stk/services.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.services.py 3 | 4 | Syntactic sugar for accessing NAOqi services. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | 14 | class ServiceCache(object): 15 | "A helper for accessing NAOqi services." 16 | 17 | def __init__(self, session=None): 18 | self.session = None 19 | self.services = {} 20 | if session: 21 | self.init(session) 22 | 23 | def init(self, session): 24 | "Sets the session object, if it wasn't passed to constructor." 25 | self.session = session 26 | 27 | def __getattr__(self, servicename): 28 | "We overload this so (instance).ALMotion returns the service, or None." 29 | if (not servicename in self.services) or ( 30 | servicename == "ALTabletService"): 31 | # ugly hack: never cache ALtabletService, always ask for a new one 32 | if servicename.startswith("__"): 33 | # Behave like a normal python object for those 34 | raise AttributeError 35 | try: 36 | self.services[servicename] = self.session.service(servicename) 37 | except RuntimeError: # Cannot find service 38 | self.services[servicename] = None 39 | return self.services[servicename] 40 | -------------------------------------------------------------------------------- /templates/app-with-tablet/app/translations/translation_en_US.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /templates/app-with-tablet/debug/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

App debug interface!

7 |

Pick the robot to use

8 | Robot IP: 9 |
10 | 11 | 12 |
13 |

The app's page will be displayed as if it was on a tablet connected to that robot

14 | 15 | 16 | 17 | 18 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/app-with-tablet/debug/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.4.1 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2006, 2014 Klaus Hartl 6 | * Released under the MIT license 7 | */ 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD (Register as an anonymous module) 11 | define(['jquery'], factory); 12 | } else if (typeof exports === 'object') { 13 | // Node/CommonJS 14 | module.exports = factory(require('jquery')); 15 | } else { 16 | // Browser globals 17 | factory(jQuery); 18 | } 19 | }(function ($) { 20 | 21 | var pluses = /\+/g; 22 | 23 | function encode(s) { 24 | return config.raw ? s : encodeURIComponent(s); 25 | } 26 | 27 | function decode(s) { 28 | return config.raw ? s : decodeURIComponent(s); 29 | } 30 | 31 | function stringifyCookieValue(value) { 32 | return encode(config.json ? JSON.stringify(value) : String(value)); 33 | } 34 | 35 | function parseCookieValue(s) { 36 | if (s.indexOf('"') === 0) { 37 | // This is a quoted cookie as according to RFC2068, unescape... 38 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); 39 | } 40 | 41 | try { 42 | // Replace server-side written pluses with spaces. 43 | // If we can't decode the cookie, ignore it, it's unusable. 44 | // If we can't parse the cookie, ignore it, it's unusable. 45 | s = decodeURIComponent(s.replace(pluses, ' ')); 46 | return config.json ? JSON.parse(s) : s; 47 | } catch(e) {} 48 | } 49 | 50 | function read(s, converter) { 51 | var value = config.raw ? s : parseCookieValue(s); 52 | return $.isFunction(converter) ? converter(value) : value; 53 | } 54 | 55 | var config = $.cookie = function (key, value, options) { 56 | 57 | // Write 58 | 59 | if (arguments.length > 1 && !$.isFunction(value)) { 60 | options = $.extend({}, config.defaults, options); 61 | 62 | if (typeof options.expires === 'number') { 63 | var days = options.expires, t = options.expires = new Date(); 64 | t.setMilliseconds(t.getMilliseconds() + days * 864e+5); 65 | } 66 | 67 | return (document.cookie = [ 68 | encode(key), '=', stringifyCookieValue(value), 69 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 70 | options.path ? '; path=' + options.path : '', 71 | options.domain ? '; domain=' + options.domain : '', 72 | options.secure ? '; secure' : '' 73 | ].join('')); 74 | } 75 | 76 | // Read 77 | 78 | var result = key ? undefined : {}, 79 | // To prevent the for loop in the first place assign an empty array 80 | // in case there are no cookies at all. Also prevents odd result when 81 | // calling $.cookie(). 82 | cookies = document.cookie ? document.cookie.split('; ') : [], 83 | i = 0, 84 | l = cookies.length; 85 | 86 | for (; i < l; i++) { 87 | var parts = cookies[i].split('='), 88 | name = decode(parts.shift()), 89 | cookie = parts.join('='); 90 | 91 | if (key === name) { 92 | // If second argument (value) is a function it's a converter... 93 | result = read(cookie, value); 94 | break; 95 | } 96 | 97 | // Prevent storing a cookie that we couldn't decode. 98 | if (!key && (cookie = read(cookie)) !== undefined) { 99 | result[name] = cookie; 100 | } 101 | } 102 | 103 | return result; 104 | }; 105 | 106 | config.defaults = {}; 107 | 108 | $.removeCookie = function (key, options) { 109 | // Must not alter options, thus extending a fresh object... 110 | $.cookie(key, '', $.extend({}, options, { expires: -1 })); 111 | return !$.cookie(key); 112 | }; 113 | 114 | })); 115 | -------------------------------------------------------------------------------- /templates/app-with-tablet/serve.py: -------------------------------------------------------------------------------- 1 | """ 2 | A helper script for testing your app's local html files, with no robot install. 3 | 4 | Usage: 5 | Run serve.py, a browser will open on a debug page, enter your robot's IP address 6 | and your local pages will be served as if they were on the robot. 7 | 8 | Using a server like this (as opposed to just file:// etc.) is only necessary 9 | if you do ajax calls in your page. 10 | """ 11 | 12 | __version__ = "0.0.3" 13 | 14 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 15 | __author__ = 'ekroeger' 16 | __email__ = 'ekroeger@aldebaran.com' 17 | 18 | import os 19 | import threading 20 | import webbrowser 21 | import BaseHTTPServer 22 | import SimpleHTTPServer 23 | import urlparse 24 | import urllib 25 | 26 | USE_SERVER = False 27 | 28 | PORT = 8081 29 | 30 | def open_browser(): 31 | """Start a browser after waiting for half a second.""" 32 | if USE_SERVER: 33 | def _open_browser(): 34 | url = 'http://localhost:%s/debug/index.html' % PORT 35 | webbrowser.open(url) 36 | thread = threading.Timer(0.5, _open_browser) 37 | thread.start() 38 | else: 39 | indexpath = os.path.join(os.getcwd(), "debug/index.html") 40 | url = urlparse.urljoin('file:', urllib.pathname2url(indexpath)) 41 | webbrowser.open(url) 42 | 43 | def start_server(): 44 | """Start the server.""" 45 | if USE_SERVER: 46 | server_address = ("", PORT) 47 | handler_class = SimpleHTTPServer.SimpleHTTPRequestHandler 48 | handler_class.extensions_map['.png'] = 'image/png' 49 | server = BaseHTTPServer.HTTPServer(server_address, handler_class) 50 | server.serve_forever() 51 | 52 | def run(): 53 | open_browser() # Comment out this line if you don't want to open a browser 54 | start_server() 55 | 56 | if __name__ == "__main__": 57 | run() 58 | 59 | -------------------------------------------------------------------------------- /templates/app-with-tablet/testrun.py: -------------------------------------------------------------------------------- 1 | """ 2 | A test runner for a NAOqi service (to test the service packaged in the app) 3 | 4 | Edit it to set your robot's IP to run it (or use arv etc.) 5 | 6 | So far it's pretty hard-coded 7 | """ 8 | 9 | __version__ = "0.0.1" 10 | 11 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 12 | __author__ = 'ekroeger' 13 | __email__ = 'ekroeger@aldebaran.com' 14 | 15 | 16 | import subprocess 17 | import time 18 | import sys 19 | import traceback 20 | 21 | import qi 22 | 23 | ######################################### 24 | # Helper base class 25 | ######################################### 26 | 27 | class ServiceTester: 28 | _app = None 29 | _testresults = [] 30 | 31 | @classmethod 32 | def configure(cls, robot): 33 | sys.argv.extend(["--qi-url", robot]) 34 | cls.robot = robot 35 | cls._app = qi.Application() 36 | cls._app.start() 37 | 38 | @classmethod 39 | def recap(cls): 40 | succeeded = 0 41 | failed = [] 42 | for name, result in cls._testresults: 43 | if result: 44 | succeeded += 1 45 | else: 46 | failed.append(name) 47 | print "================================================" 48 | print "Successes: {0}/{1}".format(succeeded, len(cls._testresults)) 49 | if failed: 50 | print "Failed tests:", ", ".join(failed) 51 | else: 52 | print "All OK :)" 53 | 54 | def service(self, service_name): 55 | return self._app.session.service(service_name) 56 | 57 | def __init__(self): 58 | self.name = self.__class__.__name__ 59 | self.start() 60 | 61 | def start(self): 62 | command = ["/usr/bin/python", self.script, "--qi-url", self.robot] 63 | self.popen = subprocess.Popen(command, stdout=subprocess.PIPE, 64 | stderr=subprocess.STDOUT) 65 | self.running = True 66 | #print "Spawned", self.script, "with ID", self.popen.pid 67 | #print 68 | 69 | def finish_and_dump(self, verbose=True): 70 | if self.running: 71 | self.running = False 72 | self.popen.terminate() 73 | if verbose: 74 | print 75 | print "Killed. Output:" 76 | lines_iterator = iter(self.popen.stdout.readline, b"") 77 | for line in lines_iterator: 78 | print ">", line.strip("\n") # yield line 79 | print 80 | 81 | def run_wrapped(self): 82 | succeeded = False 83 | try: 84 | print 85 | print "--------------------" 86 | print "starting", self.name 87 | succeeded = self.run() 88 | print "finished", self.name 89 | except Exception as e: 90 | print "error in", self.name 91 | print traceback.format_exc() 92 | finally: 93 | self.finish_and_dump(verbose=(not succeeded)) 94 | self._testresults.append((self.name, succeeded)) 95 | 96 | ######################################### 97 | # Specific test classes 98 | ######################################### 99 | 100 | class SetGetTest(ServiceTester): 101 | script = "app/scripts/myservice.py" 102 | def run(self): 103 | time.sleep(1) 104 | 105 | ALMyService = self.service("ALMyService") 106 | ALMyService.set(0) 107 | assert ALMyService.get() == 0 108 | ALMyService.set(2) 109 | assert ALMyService.get() == 2 110 | return True 111 | 112 | 113 | class ResetTest(ServiceTester): 114 | script = "app/scripts/myservice.py" 115 | def run(self): 116 | time.sleep(1) 117 | 118 | ALMyService = self.service("ALMyService") 119 | ALMyService.reset() 120 | assert ALMyService.get() == 0 121 | return True 122 | 123 | 124 | class ExitTest(ServiceTester): 125 | script = "app/scripts/myservice.py" 126 | def run(self): 127 | time.sleep(1) 128 | 129 | ALMyService = self.service("ALMyService") 130 | goterror = False 131 | try: 132 | ALMyService.exit() 133 | ALMyService.set(1) 134 | except RuntimeError: 135 | print "Got error after exit, as expected" 136 | goterror = True 137 | assert goterror, "Expected an exception after exit!" 138 | return True 139 | 140 | def run_tests(robotname, *testclasses): 141 | ServiceTester.configure(robotname) 142 | for cls in testclasses: 143 | cls().run_wrapped() 144 | ServiceTester.recap() 145 | 146 | 147 | def test_all(robotname): 148 | run_tests(robotname, 149 | SetGetTest, 150 | ResetTest, 151 | ExitTest, 152 | ) 153 | 154 | if __name__ == "__main__": 155 | ROBOTNAME = "yourrobot.local" # Adapt this depending of your robot 156 | ROBOTNAME = "citadelle.local" 157 | test_all(ROBOTNAME) 158 | -------------------------------------------------------------------------------- /templates/dialog-service/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | -------------------------------------------------------------------------------- /templates/dialog-service/app/.metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/dialog-service/app/.metadata -------------------------------------------------------------------------------- /templates/dialog-service/app/dialog-service.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /templates/dialog-service/app/dialog-service/dialog-service.dlg: -------------------------------------------------------------------------------- 1 | multilanguage:dialog-service 2 | enu:dialog-service_enu.top 3 | -------------------------------------------------------------------------------- /templates/dialog-service/app/dialog-service/dialog-service_enu.top: -------------------------------------------------------------------------------- 1 | topic: ~dialog-service() 2 | language: enu 3 | 4 | concept:(numbers) [0 1 2 3 4 5 6 7 8 9 10] 5 | 6 | u:(set {the} counter [to at] _~numbers) 7 | setting counter to $1 8 | ^call(ALMyService.set($1)) 9 | 10 | u:(["check counter" "what is the counter?"]) 11 | So, ^call(ALMyService.get()) 12 | c1:(_*) the counter is $1 13 | -------------------------------------------------------------------------------- /templates/dialog-service/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/dialog-service/app/icon.png -------------------------------------------------------------------------------- /templates/dialog-service/app/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dialog-service 5 | 6 | 7 | A simple app consisting of a collaborative dialogue talking to a service. 8 | 9 | 10 | en_US 11 | 12 | 13 | en_US 14 | 15 | 16 | 17 | interactive 18 | 19 | 20 | Launch this to test your collaborative dialog in isolation (easier for debugging) 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /templates/dialog-service/app/scripts/myservice.py: -------------------------------------------------------------------------------- 1 | """ 2 | A sample showing how to have a NAOqi service as a Python app. 3 | """ 4 | 5 | __version__ = "0.0.3" 6 | 7 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 8 | __author__ = 'YOURNAME' 9 | __email__ = 'YOUREMAIL@aldebaran.com' 10 | 11 | 12 | import qi 13 | 14 | import stk.runner 15 | import stk.events 16 | import stk.services 17 | import stk.logging 18 | 19 | class ALMyService(object): 20 | "NAOqi service example (set/get on a simple value)." 21 | APP_ID = "com.aldebaran.ALMyService" 22 | def __init__(self, qiapp): 23 | # generic activity boilerplate 24 | self.qiapp = qiapp 25 | self.events = stk.events.EventHelper(qiapp.session) 26 | self.s = stk.services.ServiceCache(qiapp.session) 27 | self.logger = stk.logging.get_logger(qiapp.session, self.APP_ID) 28 | # Internal variables 29 | self.level = 0 30 | 31 | @qi.bind(returnType=qi.Void, paramsType=[qi.Int8]) 32 | def set(self, level): 33 | "Set level" 34 | self.level = level 35 | 36 | @qi.bind(returnType=qi.Int8, paramsType=[]) 37 | def get(self): 38 | "Get level" 39 | return self.level 40 | 41 | @qi.bind(returnType=qi.Void, paramsType=[]) 42 | def reset(self): 43 | "Reset level to default value" 44 | return self.set(0) 45 | 46 | @qi.bind(returnType=qi.Void, paramsType=[]) 47 | def stop(self): 48 | "Stop the service." 49 | self.logger.info("ALMyService stopped by user request.") 50 | self.qiapp.stop() 51 | 52 | @qi.nobind 53 | def on_stop(self): 54 | "Cleanup (add yours if needed)" 55 | self.logger.info("ALMyService finished.") 56 | 57 | #################### 58 | # Setup and Run 59 | #################### 60 | 61 | if __name__ == "__main__": 62 | stk.runner.run_service(ALMyService) 63 | 64 | -------------------------------------------------------------------------------- /templates/dialog-service/app/scripts/stk/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | STK - A collection of libraries useful for making apps with NAOqi. 3 | """ 4 | -------------------------------------------------------------------------------- /templates/dialog-service/app/scripts/stk/events.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.events.py 3 | 4 | Provides misc. wrappers for ALMemory and Signals (using the same syntax for 5 | handling both). 6 | """ 7 | 8 | __version__ = "0.1.1" 9 | 10 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 11 | __author__ = 'ekroeger' 12 | __email__ = 'ekroeger@aldebaran.com' 13 | 14 | import qi 15 | 16 | 17 | def on(*keys): 18 | """Decorator for connecting a callback to one or several events. 19 | 20 | Usage: 21 | 22 | class O: 23 | @on("MyMemoryKey") 24 | def my_callback(self,value): 25 | print "I was called!", value 26 | 27 | o = O() 28 | events = EventsHelper() 29 | events.connect_decorators(o) 30 | 31 | After that, whenever MyMemoryKey is raised, o.my_callback will be called 32 | with the value. 33 | """ 34 | def decorator(func): 35 | func.__event_keys__ = keys 36 | return func 37 | return decorator 38 | 39 | 40 | class EventHelper(object): 41 | "Helper for ALMemory; takes care of event connections so you don't have to" 42 | 43 | def __init__(self, session=None): 44 | self.session = None 45 | self.almemory = None 46 | if session: 47 | self.init(session) 48 | self.handlers = {} # a handler is (subscriber, connections) 49 | self.subscriber_names = {} 50 | self.wait_value = None 51 | self.wait_promise = None 52 | 53 | def init(self, session): 54 | "Sets the NAOqi session, if it wasn't passed to the constructor" 55 | self.session = session 56 | self.almemory = session.service("ALMemory") 57 | 58 | def connect_decorators(self, obj): 59 | "Connects all decorated methods of target object." 60 | for membername in dir(obj): 61 | member = getattr(obj, membername) 62 | if hasattr(member, "__event_keys__"): 63 | for event in member.__event_keys__: 64 | self.connect(event, member) 65 | 66 | def connect(self, event, callback): 67 | """Connects an ALMemory event or signal to a callback. 68 | 69 | Note that some events trigger side effects in services when someone 70 | subscribes to them (such as WordRecognized). Those will *not* be 71 | triggered by this function, for those, use .subscribe(). 72 | """ 73 | if event not in self.handlers: 74 | if "." in event: 75 | # if we have more than one ".": 76 | service_name, signal_name = event.split(".") 77 | service = self.session.service(service_name) 78 | self.handlers[event] = (getattr(service, signal_name), []) 79 | else: 80 | # It's a "normal" ALMemory event. 81 | self.handlers[event] = ( 82 | self.almemory.subscriber(event).signal, []) 83 | signal, connections = self.handlers[event] 84 | connection_id = signal.connect(callback) 85 | connections.append(connection_id) 86 | return connection_id 87 | 88 | def subscribe(self, event, attachedname, callback): 89 | """Subscribes to an ALMemory event so as to notify providers. 90 | 91 | This is necessary for things like WordRecognized.""" 92 | connection_id = self.connect(event, callback) 93 | dummyname = "on_" + event.replace("/", "") 94 | self.almemory.subscribeToEvent(event, attachedname, dummyname) 95 | self.subscriber_names[event] = attachedname 96 | return connection_id 97 | 98 | def disconnect(self, event, connection_id=None): 99 | "Disconnects a connection, or all if no connection is specified." 100 | if event in self.handlers: 101 | signal, connections = self.handlers[event] 102 | if connection_id: 103 | if connection_id in connections: 104 | signal.disconnect(connection_id) 105 | connections.remove(connection_id) 106 | else: 107 | # Didn't specify a connection ID: remove all 108 | for connection_id in connections: 109 | signal.disconnect(connection_id) 110 | del connections[:] 111 | if event in self.subscriber_names: 112 | name = self.subscriber_names[event] 113 | self.almemory.unsubscribeToEvent(event, name) 114 | del self.subscriber_names[event] 115 | 116 | def clear(self): 117 | "Disconnect all connections" 118 | for event in list(self.handlers): 119 | self.disconnect(event) 120 | 121 | def get(self, key): 122 | "Gets ALMemory value." 123 | return self.almemory.getData(key) 124 | 125 | def get_int(self, key): 126 | "Gets ALMemory value, cast as int." 127 | try: 128 | return int(self.get(key)) 129 | except RuntimeError: 130 | # Key doesn't exist 131 | return 0 132 | except ValueError: 133 | # Key exists, but can't be parsed to int 134 | return 0 135 | 136 | def set(self, key, value): 137 | "Sets value of ALMemory key." 138 | return self.almemory.raiseEvent(key, value) 139 | 140 | def remove(self, key): 141 | "Remove key from ALMemory." 142 | try: 143 | self.almemory.removeData(key) 144 | except RuntimeError: 145 | pass 146 | 147 | def _on_wait_event(self, value): 148 | "Internal - callback for an event." 149 | if self.wait_promise: 150 | self.wait_promise.setValue(value) 151 | self.wait_promise = None 152 | 153 | def _on_wait_signal(self, *args): 154 | "Internal - callback for a signal." 155 | if self.wait_promise: 156 | self.wait_promise.setValue(args) 157 | self.wait_promise = None 158 | 159 | def cancel_wait(self): 160 | "Cancel the current wait (raises an exception in the waiting thread)" 161 | if self.wait_promise: 162 | self.wait_promise.setCanceled() 163 | self.wait_promise = None 164 | 165 | def wait_for(self, event, subscribe=False): 166 | """Block until a certain event is raised, and returns it's value. 167 | 168 | If you pass subscribe=True, ALMemory.subscribeToEvent will be called 169 | (sometimes necessary for side effects, i.e. WordRecognized). 170 | 171 | This will block a thread so you should avoid doing this too often! 172 | """ 173 | if self.wait_promise: 174 | # there was already a wait in progress, cancel it! 175 | self.wait_promise.setCanceled() 176 | self.wait_promise = qi.Promise() 177 | if subscribe: 178 | connection_id = self.subscribe(event, "EVENTHELPER", 179 | self._on_wait_event) 180 | elif "." in event: # it's a signal 181 | connection_id = self.connect(event, self._on_wait_signal) 182 | else: 183 | connection_id = self.connect(event, self._on_wait_event) 184 | try: 185 | result = self.wait_promise.future().value() 186 | finally: 187 | self.disconnect(event, connection_id) 188 | return result 189 | -------------------------------------------------------------------------------- /templates/dialog-service/app/scripts/stk/logging.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.logging.py 3 | 4 | Utility library for logging with qi. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | import functools 14 | import traceback 15 | 16 | import qi 17 | 18 | 19 | def get_logger(session, app_id): 20 | """Returns a qi logger object.""" 21 | logger = qi.logging.Logger(app_id) 22 | try: 23 | qicore = qi.module("qicore") 24 | log_manager = session.service("LogManager") 25 | provider = qicore.createObject("LogProvider", log_manager) 26 | log_manager.addProvider(provider) 27 | except RuntimeError: 28 | # no qicore, we're not running on a robot, it doesn't matter 29 | pass 30 | except AttributeError: 31 | # old version of NAOqi - logging will probably not work. 32 | pass 33 | return logger 34 | 35 | 36 | def log_exceptions(func): 37 | """Catches all exceptions in decorated method, and prints them. 38 | 39 | Attached function must be on an object with a "logger" member. 40 | """ 41 | @functools.wraps(func) 42 | def wrapped(self, *args): 43 | try: 44 | return func(self, *args) 45 | except Exception as exc: 46 | self.logger.error(traceback.format_exc()) 47 | raise exc 48 | return wrapped 49 | 50 | 51 | def log_exceptions_and_return(default_value): 52 | """If an exception occurs, print it and return default_value. 53 | 54 | Attached function must be on an object with a "logger" member. 55 | """ 56 | def decorator(func): 57 | @functools.wraps(func) 58 | def wrapped(self, *args): 59 | try: 60 | return func(self, *args) 61 | except Exception: 62 | self.logger.error(traceback.format_exc()) 63 | return default_value 64 | return wrapped 65 | return decorator 66 | -------------------------------------------------------------------------------- /templates/dialog-service/app/scripts/stk/runner.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.runner.py 3 | 4 | A helper library for making simple standalone python scripts as apps. 5 | 6 | Wraps some NAOqi and system stuff, you could do all this by directly using the 7 | Python SDK, these helper functions just isolate some frequently used/hairy 8 | bits so you don't have them mixed in your logic. 9 | """ 10 | 11 | __version__ = "0.1.3" 12 | 13 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 14 | __author__ = 'ekroeger' 15 | __email__ = 'ekroeger@aldebaran.com' 16 | 17 | import sys 18 | import qi 19 | from distutils.version import LooseVersion 20 | 21 | # 22 | # Helpers for making sure we have a robot to connect to 23 | # 24 | 25 | 26 | def check_commandline_args(description): 27 | "Checks whether command-line parameters are enough" 28 | import argparse 29 | parser = argparse.ArgumentParser(description=description) 30 | parser.add_argument('--qi-url', help='connect to specific NAOqi instance') 31 | 32 | args = parser.parse_args() 33 | return args 34 | 35 | 36 | def is_on_robot(): 37 | "Returns whether this is being executed on an Aldebaran robot." 38 | import platform 39 | return "aldebaran" in platform.platform() 40 | 41 | 42 | def get_debug_robot(): 43 | "Returns IP address of debug robot, complaining if not found" 44 | try: 45 | import qiq.config 46 | qiqrobot = qiq.config.defaultHost() 47 | if qiqrobot: 48 | robot = raw_input( 49 | "connect to which robot? (default is {0}) ".format(qiqrobot)) 50 | if robot: 51 | return robot 52 | else: 53 | return qiqrobot 54 | else: 55 | print "qiq found, but it has no default robot configured." 56 | except ImportError: 57 | # qiq not installed 58 | print "qiq not installed (you can use it to set a default robot)." 59 | return raw_input("connect to which robot? ") 60 | 61 | 62 | def init(qi_url=None): 63 | "Returns a QiApplication object, possibly with interactive input." 64 | if qi_url: 65 | sys.argv.extend(["--qi-url", qi_url]) 66 | else: 67 | args = check_commandline_args('Run the app.') 68 | if bool(args.qi_url): 69 | qi_url = args.qi_url 70 | elif not is_on_robot(): 71 | print "no --qi-url parameter given; interactively getting debug robot." 72 | debug_robot = get_debug_robot() 73 | if debug_robot: 74 | sys.argv.extend(["--qi-url", debug_robot]) 75 | qi_url = debug_robot 76 | else: 77 | raise RuntimeError("No robot, not running.") 78 | 79 | qiapp = None 80 | sys.argv[0] = str(sys.argv[0]) 81 | 82 | # In versions bellow 2.3, look for --qi-url in the arguemnts and call accordingly the Application 83 | if qi_url and LooseVersion(qi.__version__) < LooseVersion("2.3"): 84 | position = 0 85 | qiapp = qi.Application(url="tcp://"+qi_url+":9559") 86 | # In versions greater than 2.3 the ip can simply be passed through argv[0] 87 | else: 88 | # In some environments sys.argv[0] has unicode, which qi rejects 89 | qiapp = qi.Application() 90 | 91 | qiapp.start() 92 | return qiapp 93 | 94 | 95 | # Main runner 96 | 97 | def run_activity(activity_class, service_name=None): 98 | """Instantiate the given class, and runs it. 99 | 100 | The given class must take a qiapplication object as parameter, and may also 101 | have on_start and on_stop methods, that will be called before and after 102 | running it.""" 103 | qiapp = init() 104 | activity = activity_class(qiapp) 105 | service_id = None 106 | 107 | try: 108 | # if it's a service, register it 109 | if service_name: 110 | # Note: this will fail if there is already a service. Unregistering 111 | # it would not be a good practice, because it's process would still 112 | # be running. 113 | service_id = qiapp.session.registerService(service_name, activity) 114 | 115 | if hasattr(activity, "on_start"): 116 | def handle_on_start_done(on_start_future): 117 | "Custom callback, for checking errors" 118 | if on_start_future.hasError(): 119 | try: 120 | msg = "Error in on_start(), stopping application: %s" \ 121 | % on_start_future.error() 122 | if hasattr(activity, "logger"): 123 | activity.logger.error(msg) 124 | else: 125 | print msg 126 | finally: 127 | qiapp.stop() 128 | qi.async(activity.on_start).addCallback(handle_on_start_done) 129 | 130 | # Run the QiApplication, which runs until someone calls qiapp.stop() 131 | qiapp.run() 132 | 133 | finally: 134 | # Cleanup 135 | if hasattr(activity, "on_stop"): 136 | # We need a qi.async call so that if the class is single threaded, 137 | # it will wait for callbacks to be finished. 138 | qi.async(activity.on_stop).wait() 139 | if service_id: 140 | qiapp.session.unregisterService(service_id) 141 | 142 | 143 | def run_service(service_class, service_name=None): 144 | """Instantiate the given class, and registers it as a NAOqi service. 145 | 146 | The given class must take a qiapplication object as parameter, and may also 147 | have on_start and on_stop methods, that will be called before and after 148 | running it. 149 | 150 | If the service_name parameter is not given, the classes' name will be used. 151 | """ 152 | if not service_name: 153 | service_name = service_class.__name__ 154 | run_activity(service_class, service_name) 155 | -------------------------------------------------------------------------------- /templates/dialog-service/app/scripts/stk/services.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.services.py 3 | 4 | Syntactic sugar for accessing NAOqi services. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | 14 | class ServiceCache(object): 15 | "A helper for accessing NAOqi services." 16 | 17 | def __init__(self, session=None): 18 | self.session = None 19 | self.services = {} 20 | if session: 21 | self.init(session) 22 | 23 | def init(self, session): 24 | "Sets the session object, if it wasn't passed to constructor." 25 | self.session = session 26 | 27 | def __getattr__(self, servicename): 28 | "We overload this so (instance).ALMotion returns the service, or None." 29 | if (not servicename in self.services) or ( 30 | servicename == "ALTabletService"): 31 | # ugly hack: never cache ALtabletService, always ask for a new one 32 | if servicename.startswith("__"): 33 | # Behave like a normal python object for those 34 | raise AttributeError 35 | try: 36 | self.services[servicename] = self.session.service(servicename) 37 | except RuntimeError: # Cannot find service 38 | self.services[servicename] = None 39 | return self.services[servicename] 40 | -------------------------------------------------------------------------------- /templates/dialog-service/app/testrun/behavior.xar: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | media/images/box/root.png 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ../dialog-service/dialog-service.dlg 20 | media/images/box/box-dialog.png 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /templates/dialog-service/testrun.py: -------------------------------------------------------------------------------- 1 | """ 2 | A test runner for a NAOqi service (to test the service packaged in the app) 3 | 4 | Edit it to set your robot's IP to run it (or use arv etc.) 5 | 6 | So far it's pretty hard-coded 7 | """ 8 | 9 | __version__ = "0.0.1" 10 | 11 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 12 | __author__ = 'ekroeger' 13 | __email__ = 'ekroeger@aldebaran.com' 14 | 15 | 16 | import subprocess 17 | import time 18 | import sys 19 | import traceback 20 | 21 | import qi 22 | 23 | ######################################### 24 | # Helper base class 25 | ######################################### 26 | 27 | class ServiceTester: 28 | _app = None 29 | _testresults = [] 30 | 31 | @classmethod 32 | def configure(cls, robot): 33 | sys.argv.extend(["--qi-url", robot]) 34 | cls.robot = robot 35 | cls._app = qi.Application() 36 | cls._app.start() 37 | 38 | @classmethod 39 | def recap(cls): 40 | succeeded = 0 41 | failed = [] 42 | for name, result in cls._testresults: 43 | if result: 44 | succeeded += 1 45 | else: 46 | failed.append(name) 47 | print "================================================" 48 | print "Successes: {0}/{1}".format(succeeded, len(cls._testresults)) 49 | if failed: 50 | print "Failed tests:", ", ".join(failed) 51 | else: 52 | print "All OK :)" 53 | 54 | def service(self, service_name): 55 | return self._app.session.service(service_name) 56 | 57 | def __init__(self): 58 | self.name = self.__class__.__name__ 59 | self.start() 60 | 61 | def start(self): 62 | command = ["/usr/bin/python", self.script, "--qi-url", self.robot] 63 | self.popen = subprocess.Popen(command, stdout=subprocess.PIPE, 64 | stderr=subprocess.STDOUT) 65 | self.running = True 66 | #print "Spawned", self.script, "with ID", self.popen.pid 67 | #print 68 | 69 | def finish_and_dump(self, verbose=True): 70 | if self.running: 71 | self.running = False 72 | self.popen.terminate() 73 | if verbose: 74 | print 75 | print "Killed. Output:" 76 | lines_iterator = iter(self.popen.stdout.readline, b"") 77 | for line in lines_iterator: 78 | print ">", line.strip("\n") # yield line 79 | print 80 | 81 | def run_wrapped(self): 82 | succeeded = False 83 | try: 84 | print 85 | print "--------------------" 86 | print "starting", self.name 87 | succeeded = self.run() 88 | print "finished", self.name 89 | except Exception as e: 90 | print "error in", self.name 91 | print traceback.format_exc() 92 | finally: 93 | self.finish_and_dump(verbose=(not succeeded)) 94 | self._testresults.append((self.name, succeeded)) 95 | 96 | ######################################### 97 | # Specific test classes 98 | ######################################### 99 | 100 | class SetGetTest(ServiceTester): 101 | script = "app/scripts/myservice.py" 102 | def run(self): 103 | time.sleep(1) 104 | 105 | ALMyService = self.service("ALMyService") 106 | ALMyService.set(0) 107 | assert ALMyService.get() == 0 108 | ALMyService.set(2) 109 | assert ALMyService.get() == 2 110 | return True 111 | 112 | 113 | class ResetTest(ServiceTester): 114 | script = "app/scripts/myservice.py" 115 | def run(self): 116 | time.sleep(1) 117 | 118 | ALMyService = self.service("ALMyService") 119 | ALMyService.reset() 120 | assert ALMyService.get() == 0 121 | return True 122 | 123 | 124 | class StopTest(ServiceTester): 125 | script = "app/scripts/myservice.py" 126 | def run(self): 127 | time.sleep(1) 128 | 129 | ALMyService = self.service("ALMyService") 130 | goterror = False 131 | try: 132 | ALMyService.stop() 133 | time.sleep(1) 134 | ALMyService.set(1) 135 | except RuntimeError: 136 | print "Got error after exit, as expected" 137 | goterror = True 138 | assert goterror, "Expected an exception after exit!" 139 | return True 140 | 141 | def run_tests(robotname, *testclasses): 142 | ServiceTester.configure(robotname) 143 | for cls in testclasses: 144 | cls().run_wrapped() 145 | ServiceTester.recap() 146 | 147 | 148 | def test_all(robotname): 149 | run_tests(robotname, 150 | SetGetTest, 151 | ResetTest, 152 | StopTest, 153 | ) 154 | 155 | if __name__ == "__main__": 156 | ROBOTNAME = "yourrobot.local" # Adapt this depending of your robot 157 | ROBOTNAME = "citadelle.local" 158 | test_all(ROBOTNAME) 159 | -------------------------------------------------------------------------------- /templates/python-service/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | -------------------------------------------------------------------------------- /templates/python-service/app/.metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/python-service/app/.metadata -------------------------------------------------------------------------------- /templates/python-service/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/python-service/app/icon.png -------------------------------------------------------------------------------- /templates/python-service/app/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | python-service 5 | 6 | 7 | en_US 8 | 9 | 10 | en_US 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /templates/python-service/app/python-service.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /templates/python-service/app/scripts/myservice.py: -------------------------------------------------------------------------------- 1 | """ 2 | A sample showing how to have a NAOqi service as a Python app. 3 | """ 4 | 5 | __version__ = "0.0.3" 6 | 7 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 8 | __author__ = 'YOURNAME' 9 | __email__ = 'YOUREMAIL@aldebaran.com' 10 | 11 | 12 | import qi 13 | 14 | import stk.runner 15 | import stk.events 16 | import stk.services 17 | import stk.logging 18 | 19 | class ALMyService(object): 20 | "NAOqi service example (set/get on a simple value)." 21 | APP_ID = "com.aldebaran.ALMyService" 22 | def __init__(self, qiapp): 23 | # generic activity boilerplate 24 | self.qiapp = qiapp 25 | self.events = stk.events.EventHelper(qiapp.session) 26 | self.s = stk.services.ServiceCache(qiapp.session) 27 | self.logger = stk.logging.get_logger(qiapp.session, self.APP_ID) 28 | # Internal variables 29 | self.level = 0 30 | 31 | @qi.bind(returnType=qi.Void, paramsType=[qi.Int8]) 32 | def set(self, level): 33 | "Set level" 34 | self.level = level 35 | 36 | @qi.bind(returnType=qi.Int8, paramsType=[]) 37 | def get(self): 38 | "Get level" 39 | return self.level 40 | 41 | @qi.bind(returnType=qi.Void, paramsType=[]) 42 | def reset(self): 43 | "Reset level to default value" 44 | return self.set(0) 45 | 46 | @qi.bind(returnType=qi.Void, paramsType=[]) 47 | def stop(self): 48 | "Stop the service." 49 | self.logger.info("ALMyService stopped by user request.") 50 | self.qiapp.stop() 51 | 52 | @qi.nobind 53 | def on_stop(self): 54 | "Cleanup (add yours if needed)" 55 | self.logger.info("ALMyService finished.") 56 | 57 | #################### 58 | # Setup and Run 59 | #################### 60 | 61 | if __name__ == "__main__": 62 | stk.runner.run_service(ALMyService) 63 | 64 | -------------------------------------------------------------------------------- /templates/python-service/app/scripts/stk/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | STK - A collection of libraries useful for making apps with NAOqi. 3 | """ 4 | -------------------------------------------------------------------------------- /templates/python-service/app/scripts/stk/logging.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.logging.py 3 | 4 | Utility library for logging with qi. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | import functools 14 | import traceback 15 | 16 | import qi 17 | 18 | 19 | def get_logger(session, app_id): 20 | """Returns a qi logger object.""" 21 | logger = qi.logging.Logger(app_id) 22 | try: 23 | qicore = qi.module("qicore") 24 | log_manager = session.service("LogManager") 25 | provider = qicore.createObject("LogProvider", log_manager) 26 | log_manager.addProvider(provider) 27 | except RuntimeError: 28 | # no qicore, we're not running on a robot, it doesn't matter 29 | pass 30 | except AttributeError: 31 | # old version of NAOqi - logging will probably not work. 32 | pass 33 | return logger 34 | 35 | 36 | def log_exceptions(func): 37 | """Catches all exceptions in decorated method, and prints them. 38 | 39 | Attached function must be on an object with a "logger" member. 40 | """ 41 | @functools.wraps(func) 42 | def wrapped(self, *args): 43 | try: 44 | return func(self, *args) 45 | except Exception as exc: 46 | self.logger.error(traceback.format_exc()) 47 | raise exc 48 | return wrapped 49 | 50 | 51 | def log_exceptions_and_return(default_value): 52 | """If an exception occurs, print it and return default_value. 53 | 54 | Attached function must be on an object with a "logger" member. 55 | """ 56 | def decorator(func): 57 | @functools.wraps(func) 58 | def wrapped(self, *args): 59 | try: 60 | return func(self, *args) 61 | except Exception: 62 | self.logger.error(traceback.format_exc()) 63 | return default_value 64 | return wrapped 65 | return decorator 66 | -------------------------------------------------------------------------------- /templates/python-service/app/scripts/stk/runner.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.runner.py 3 | 4 | A helper library for making simple standalone python scripts as apps. 5 | 6 | Wraps some NAOqi and system stuff, you could do all this by directly using the 7 | Python SDK, these helper functions just isolate some frequently used/hairy 8 | bits so you don't have them mixed in your logic. 9 | """ 10 | 11 | __version__ = "0.1.3" 12 | 13 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 14 | __author__ = 'ekroeger' 15 | __email__ = 'ekroeger@aldebaran.com' 16 | 17 | import sys 18 | import qi 19 | from distutils.version import LooseVersion 20 | 21 | # 22 | # Helpers for making sure we have a robot to connect to 23 | # 24 | 25 | 26 | def check_commandline_args(description): 27 | "Checks whether command-line parameters are enough" 28 | import argparse 29 | parser = argparse.ArgumentParser(description=description) 30 | parser.add_argument('--qi-url', help='connect to specific NAOqi instance') 31 | 32 | args = parser.parse_args() 33 | return args 34 | 35 | 36 | def is_on_robot(): 37 | "Returns whether this is being executed on an Aldebaran robot." 38 | import platform 39 | return "aldebaran" in platform.platform() 40 | 41 | 42 | def get_debug_robot(): 43 | "Returns IP address of debug robot, complaining if not found" 44 | try: 45 | import qiq.config 46 | qiqrobot = qiq.config.defaultHost() 47 | if qiqrobot: 48 | robot = raw_input( 49 | "connect to which robot? (default is {0}) ".format(qiqrobot)) 50 | if robot: 51 | return robot 52 | else: 53 | return qiqrobot 54 | else: 55 | print "qiq found, but it has no default robot configured." 56 | except ImportError: 57 | # qiq not installed 58 | print "qiq not installed (you can use it to set a default robot)." 59 | return raw_input("connect to which robot? ") 60 | 61 | 62 | def init(qi_url=None): 63 | "Returns a QiApplication object, possibly with interactive input." 64 | if qi_url: 65 | sys.argv.extend(["--qi-url", qi_url]) 66 | else: 67 | args = check_commandline_args('Run the app.') 68 | if bool(args.qi_url): 69 | qi_url = args.qi_url 70 | elif not is_on_robot(): 71 | print "no --qi-url parameter given; interactively getting debug robot." 72 | debug_robot = get_debug_robot() 73 | if debug_robot: 74 | sys.argv.extend(["--qi-url", debug_robot]) 75 | qi_url = debug_robot 76 | else: 77 | raise RuntimeError("No robot, not running.") 78 | 79 | qiapp = None 80 | sys.argv[0] = str(sys.argv[0]) 81 | 82 | # In versions bellow 2.3, look for --qi-url in the arguemnts and call accordingly the Application 83 | if qi_url and hasattr(qi, "__version__") and LooseVersion(qi.__version__) < LooseVersion("2.3"): 84 | qiapp = qi.Application(url="tcp://"+qi_url+":9559") 85 | # In versions greater than 2.3 the ip can simply be passed through argv[0] 86 | else: 87 | # In some environments sys.argv[0] has unicode, which qi rejects 88 | qiapp = qi.Application() 89 | 90 | qiapp.start() 91 | return qiapp 92 | 93 | 94 | # Main runner 95 | 96 | def run_activity(activity_class, service_name=None): 97 | """Instantiate the given class, and runs it. 98 | 99 | The given class must take a qiapplication object as parameter, and may also 100 | have on_start and on_stop methods, that will be called before and after 101 | running it.""" 102 | qiapp = init() 103 | activity = activity_class(qiapp) 104 | service_id = None 105 | 106 | try: 107 | # if it's a service, register it 108 | if service_name: 109 | # Note: this will fail if there is already a service. Unregistering 110 | # it would not be a good practice, because it's process would still 111 | # be running. 112 | service_id = qiapp.session.registerService(service_name, activity) 113 | 114 | if hasattr(activity, "on_start"): 115 | def handle_on_start_done(on_start_future): 116 | "Custom callback, for checking errors" 117 | if on_start_future.hasError(): 118 | try: 119 | msg = "Error in on_start(), stopping application: %s" \ 120 | % on_start_future.error() 121 | if hasattr(activity, "logger"): 122 | activity.logger.error(msg) 123 | else: 124 | print msg 125 | finally: 126 | qiapp.stop() 127 | qi.async(activity.on_start).addCallback(handle_on_start_done) 128 | 129 | # Run the QiApplication, which runs until someone calls qiapp.stop() 130 | qiapp.run() 131 | 132 | finally: 133 | # Cleanup 134 | if hasattr(activity, "on_stop"): 135 | # We need a qi.async call so that if the class is single threaded, 136 | # it will wait for callbacks to be finished. 137 | qi.async(activity.on_stop).wait() 138 | if service_id: 139 | qiapp.session.unregisterService(service_id) 140 | 141 | 142 | def run_service(service_class, service_name=None): 143 | """Instantiate the given class, and registers it as a NAOqi service. 144 | 145 | The given class must take a qiapplication object as parameter, and may also 146 | have on_start and on_stop methods, that will be called before and after 147 | running it. 148 | 149 | If the service_name parameter is not given, the classes' name will be used. 150 | """ 151 | if not service_name: 152 | service_name = service_class.__name__ 153 | run_activity(service_class, service_name) 154 | -------------------------------------------------------------------------------- /templates/python-service/app/scripts/stk/services.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.services.py 3 | 4 | Syntactic sugar for accessing NAOqi services. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | 14 | class ServiceCache(object): 15 | "A helper for accessing NAOqi services." 16 | 17 | def __init__(self, session=None): 18 | self.session = None 19 | self.services = {} 20 | if session: 21 | self.init(session) 22 | 23 | def init(self, session): 24 | "Sets the session object, if it wasn't passed to constructor." 25 | self.session = session 26 | 27 | def __getattr__(self, servicename): 28 | "We overload this so (instance).ALMotion returns the service, or None." 29 | if (not servicename in self.services) or ( 30 | servicename == "ALTabletService"): 31 | # ugly hack: never cache ALtabletService, always ask for a new one 32 | if servicename.startswith("__"): 33 | # Behave like a normal python object for those 34 | raise AttributeError 35 | try: 36 | self.services[servicename] = self.session.service(servicename) 37 | except RuntimeError: # Cannot find service 38 | self.services[servicename] = None 39 | return self.services[servicename] 40 | -------------------------------------------------------------------------------- /templates/python-service/testrun.py: -------------------------------------------------------------------------------- 1 | """ 2 | A test runner for a NAOqi service (to test the service packaged in the app) 3 | 4 | Edit it to set your robot's IP to run it (or use arv etc.) 5 | 6 | So far it's pretty hard-coded 7 | """ 8 | 9 | __version__ = "0.0.1" 10 | 11 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 12 | __author__ = 'ekroeger' 13 | __email__ = 'ekroeger@aldebaran.com' 14 | 15 | 16 | import subprocess 17 | import time 18 | import sys 19 | import traceback 20 | 21 | import qi 22 | 23 | ######################################### 24 | # Helper base class 25 | ######################################### 26 | 27 | class ServiceTester: 28 | _app = None 29 | _testresults = [] 30 | 31 | @classmethod 32 | def configure(cls, robot): 33 | sys.argv.extend(["--qi-url", robot]) 34 | cls.robot = robot 35 | cls._app = qi.Application() 36 | cls._app.start() 37 | 38 | @classmethod 39 | def recap(cls): 40 | succeeded = 0 41 | failed = [] 42 | for name, result in cls._testresults: 43 | if result: 44 | succeeded += 1 45 | else: 46 | failed.append(name) 47 | print "================================================" 48 | print "Successes: {0}/{1}".format(succeeded, len(cls._testresults)) 49 | if failed: 50 | print "Failed tests:", ", ".join(failed) 51 | else: 52 | print "All OK :)" 53 | 54 | def service(self, service_name): 55 | return self._app.session.service(service_name) 56 | 57 | def __init__(self): 58 | self.name = self.__class__.__name__ 59 | self.start() 60 | 61 | def start(self): 62 | command = ["/usr/bin/python", self.script, "--qi-url", self.robot] 63 | self.popen = subprocess.Popen(command, stdout=subprocess.PIPE, 64 | stderr=subprocess.STDOUT) 65 | self.running = True 66 | #print "Spawned", self.script, "with ID", self.popen.pid 67 | #print 68 | 69 | def finish_and_dump(self, verbose=True): 70 | if self.running: 71 | self.running = False 72 | self.popen.terminate() 73 | if verbose: 74 | print 75 | print "Killed. Output:" 76 | lines_iterator = iter(self.popen.stdout.readline, b"") 77 | for line in lines_iterator: 78 | print ">", line.strip("\n") # yield line 79 | print 80 | 81 | def run_wrapped(self): 82 | succeeded = False 83 | try: 84 | print 85 | print "--------------------" 86 | print "starting", self.name 87 | succeeded = self.run() 88 | print "finished", self.name 89 | except Exception as e: 90 | print "error in", self.name 91 | print traceback.format_exc() 92 | finally: 93 | self.finish_and_dump(verbose=(not succeeded)) 94 | self._testresults.append((self.name, succeeded)) 95 | 96 | ######################################### 97 | # Specific test classes 98 | ######################################### 99 | 100 | class SetGetTest(ServiceTester): 101 | script = "app/scripts/myservice.py" 102 | def run(self): 103 | time.sleep(1) 104 | 105 | ALMyService = self.service("ALMyService") 106 | ALMyService.set(0) 107 | assert ALMyService.get() == 0 108 | ALMyService.set(2) 109 | assert ALMyService.get() == 2 110 | return True 111 | 112 | 113 | class ResetTest(ServiceTester): 114 | script = "app/scripts/myservice.py" 115 | def run(self): 116 | time.sleep(1) 117 | 118 | ALMyService = self.service("ALMyService") 119 | ALMyService.reset() 120 | assert ALMyService.get() == 0 121 | return True 122 | 123 | 124 | class StopTest(ServiceTester): 125 | script = "app/scripts/myservice.py" 126 | def run(self): 127 | time.sleep(1) 128 | 129 | ALMyService = self.service("ALMyService") 130 | goterror = False 131 | try: 132 | ALMyService.stop() 133 | time.sleep(1) 134 | ALMyService.set(1) 135 | except RuntimeError: 136 | print "Got error after exit, as expected" 137 | goterror = True 138 | assert goterror, "Expected an exception after exit!" 139 | return True 140 | 141 | def run_tests(robotname, *testclasses): 142 | ServiceTester.configure(robotname) 143 | for cls in testclasses: 144 | cls().run_wrapped() 145 | ServiceTester.recap() 146 | 147 | 148 | def test_all(robotname): 149 | run_tests(robotname, 150 | SetGetTest, 151 | ResetTest, 152 | StopTest, 153 | ) 154 | 155 | if __name__ == "__main__": 156 | ROBOTNAME = "yourrobot.local" # Adapt this depending of your robot 157 | ROBOTNAME = "citadelle.local" 158 | test_all(ROBOTNAME) 159 | -------------------------------------------------------------------------------- /templates/pythonapp/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | -------------------------------------------------------------------------------- /templates/pythonapp/app/.metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/pythonapp/app/.metadata -------------------------------------------------------------------------------- /templates/pythonapp/app/behavior.xar: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | media/images/box/root.png 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | media/images/box/box-python-script.png 20 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /templates/pythonapp/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/pythonapp/app/icon.png -------------------------------------------------------------------------------- /templates/pythonapp/app/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pythonapp 5 | 6 | 7 | en_US 8 | 9 | 10 | en_US 11 | 12 | 13 | 14 | 15 | interactive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /templates/pythonapp/app/pythonapp.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /templates/pythonapp/app/scripts/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | A sample showing how to make a Python script as an app. 3 | """ 4 | 5 | __version__ = "0.0.8" 6 | 7 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 8 | __author__ = 'YOURNAME' 9 | __email__ = 'YOUREMAIL@aldebaran.com' 10 | 11 | import stk.runner 12 | import stk.events 13 | import stk.services 14 | import stk.logging 15 | 16 | class Activity(object): 17 | "A sample standalone app, that demonstrates simple Python usage" 18 | APP_ID = "com.aldebaran.pythonapp" 19 | def __init__(self, qiapp): 20 | self.qiapp = qiapp 21 | self.events = stk.events.EventHelper(qiapp.session) 22 | self.s = stk.services.ServiceCache(qiapp.session) 23 | self.logger = stk.logging.get_logger(qiapp.session, self.APP_ID) 24 | 25 | def on_touched(self, *args): 26 | "Callback for tablet touched." 27 | if args: 28 | self.events.disconnect("ALTabletService.onTouchDown") 29 | self.logger.info("Tablet touched: " + str(args)) 30 | self.s.ALTextToSpeech.say("Yay!") 31 | self.stop() 32 | 33 | def on_start(self): 34 | "Ask to be touched, waits, and exits." 35 | # Two ways of waiting for events 36 | # 1) block until it's called 37 | self.s.ALTextToSpeech.say("Touch my forehead.") 38 | self.logger.warning("Listening for touch...") 39 | while not self.events.wait_for("FrontTactilTouched"): 40 | pass 41 | 42 | # 2) explicitly connect a callback 43 | if self.s.ALTabletService: 44 | self.events.connect("ALTabletService.onTouchDown", self.on_touched) 45 | self.s.ALTextToSpeech.say("okay, now touch my tablet.") 46 | # (this allows to simltaneously speak and watch an event) 47 | else: 48 | self.s.ALTextToSpeech.say("touch my tablet ... oh. " + \ 49 | "I don't haave one.") 50 | self.stop() 51 | 52 | def stop(self): 53 | "Standard way of stopping the application." 54 | self.qiapp.stop() 55 | 56 | def on_stop(self): 57 | "Cleanup" 58 | self.logger.info("Application finished.") 59 | self.events.clear() 60 | 61 | if __name__ == "__main__": 62 | stk.runner.run_activity(Activity) 63 | -------------------------------------------------------------------------------- /templates/pythonapp/app/scripts/stk/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | STK - A collection of libraries useful for making apps with NAOqi. 3 | """ 4 | -------------------------------------------------------------------------------- /templates/pythonapp/app/scripts/stk/events.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.events.py 3 | 4 | Provides misc. wrappers for ALMemory and Signals (using the same syntax for 5 | handling both). 6 | """ 7 | 8 | __version__ = "0.1.1" 9 | 10 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 11 | __author__ = 'ekroeger' 12 | __email__ = 'ekroeger@aldebaran.com' 13 | 14 | import qi 15 | 16 | 17 | def on(*keys): 18 | """Decorator for connecting a callback to one or several events. 19 | 20 | Usage: 21 | 22 | class O: 23 | @on("MyMemoryKey") 24 | def my_callback(self,value): 25 | print "I was called!", value 26 | 27 | o = O() 28 | events = EventsHelper() 29 | events.connect_decorators(o) 30 | 31 | After that, whenever MyMemoryKey is raised, o.my_callback will be called 32 | with the value. 33 | """ 34 | def decorator(func): 35 | func.__event_keys__ = keys 36 | return func 37 | return decorator 38 | 39 | 40 | class EventHelper(object): 41 | "Helper for ALMemory; takes care of event connections so you don't have to" 42 | 43 | def __init__(self, session=None): 44 | self.session = None 45 | self.almemory = None 46 | if session: 47 | self.init(session) 48 | self.handlers = {} # a handler is (subscriber, connections) 49 | self.subscriber_names = {} 50 | self.wait_value = None 51 | self.wait_promise = None 52 | 53 | def init(self, session): 54 | "Sets the NAOqi session, if it wasn't passed to the constructor" 55 | self.session = session 56 | self.almemory = session.service("ALMemory") 57 | 58 | def connect_decorators(self, obj): 59 | "Connects all decorated methods of target object." 60 | for membername in dir(obj): 61 | member = getattr(obj, membername) 62 | if hasattr(member, "__event_keys__"): 63 | for event in member.__event_keys__: 64 | self.connect(event, member) 65 | 66 | def connect(self, event, callback): 67 | """Connects an ALMemory event or signal to a callback. 68 | 69 | Note that some events trigger side effects in services when someone 70 | subscribes to them (such as WordRecognized). Those will *not* be 71 | triggered by this function, for those, use .subscribe(). 72 | """ 73 | if event not in self.handlers: 74 | if "." in event: 75 | # if we have more than one ".": 76 | service_name, signal_name = event.split(".") 77 | service = self.session.service(service_name) 78 | self.handlers[event] = (getattr(service, signal_name), []) 79 | else: 80 | # It's a "normal" ALMemory event. 81 | self.handlers[event] = ( 82 | self.almemory.subscriber(event).signal, []) 83 | signal, connections = self.handlers[event] 84 | connection_id = signal.connect(callback) 85 | connections.append(connection_id) 86 | return connection_id 87 | 88 | def subscribe(self, event, attachedname, callback): 89 | """Subscribes to an ALMemory event so as to notify providers. 90 | 91 | This is necessary for things like WordRecognized.""" 92 | connection_id = self.connect(event, callback) 93 | dummyname = "on_" + event.replace("/", "") 94 | self.almemory.subscribeToEvent(event, attachedname, dummyname) 95 | self.subscriber_names[event] = attachedname 96 | return connection_id 97 | 98 | def disconnect(self, event, connection_id=None): 99 | "Disconnects a connection, or all if no connection is specified." 100 | if event in self.handlers: 101 | signal, connections = self.handlers[event] 102 | if connection_id: 103 | if connection_id in connections: 104 | signal.disconnect(connection_id) 105 | connections.remove(connection_id) 106 | else: 107 | # Didn't specify a connection ID: remove all 108 | for connection_id in connections: 109 | signal.disconnect(connection_id) 110 | del connections[:] 111 | if event in self.subscriber_names: 112 | name = self.subscriber_names[event] 113 | self.almemory.unsubscribeToEvent(event, name) 114 | del self.subscriber_names[event] 115 | 116 | def clear(self): 117 | "Disconnect all connections" 118 | for event in list(self.handlers): 119 | self.disconnect(event) 120 | 121 | def get(self, key): 122 | "Gets ALMemory value." 123 | return self.almemory.getData(key) 124 | 125 | def get_int(self, key): 126 | "Gets ALMemory value, cast as int." 127 | try: 128 | return int(self.get(key)) 129 | except RuntimeError: 130 | # Key doesn't exist 131 | return 0 132 | except ValueError: 133 | # Key exists, but can't be parsed to int 134 | return 0 135 | 136 | def set(self, key, value): 137 | "Sets value of ALMemory key." 138 | return self.almemory.raiseEvent(key, value) 139 | 140 | def remove(self, key): 141 | "Remove key from ALMemory." 142 | try: 143 | self.almemory.removeData(key) 144 | except RuntimeError: 145 | pass 146 | 147 | def _on_wait_event(self, value): 148 | "Internal - callback for an event." 149 | if self.wait_promise: 150 | self.wait_promise.setValue(value) 151 | self.wait_promise = None 152 | 153 | def _on_wait_signal(self, *args): 154 | "Internal - callback for a signal." 155 | if self.wait_promise: 156 | self.wait_promise.setValue(args) 157 | self.wait_promise = None 158 | 159 | def cancel_wait(self): 160 | "Cancel the current wait (raises an exception in the waiting thread)" 161 | if self.wait_promise: 162 | self.wait_promise.setCanceled() 163 | self.wait_promise = None 164 | 165 | def wait_for(self, event, subscribe=False): 166 | """Block until a certain event is raised, and returns it's value. 167 | 168 | If you pass subscribe=True, ALMemory.subscribeToEvent will be called 169 | (sometimes necessary for side effects, i.e. WordRecognized). 170 | 171 | This will block a thread so you should avoid doing this too often! 172 | """ 173 | if self.wait_promise: 174 | # there was already a wait in progress, cancel it! 175 | self.wait_promise.setCanceled() 176 | self.wait_promise = qi.Promise() 177 | if subscribe: 178 | connection_id = self.subscribe(event, "EVENTHELPER", 179 | self._on_wait_event) 180 | elif "." in event: # it's a signal 181 | connection_id = self.connect(event, self._on_wait_signal) 182 | else: 183 | connection_id = self.connect(event, self._on_wait_event) 184 | try: 185 | result = self.wait_promise.future().value() 186 | finally: 187 | self.disconnect(event, connection_id) 188 | return result 189 | -------------------------------------------------------------------------------- /templates/pythonapp/app/scripts/stk/logging.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.logging.py 3 | 4 | Utility library for logging with qi. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | import functools 14 | import traceback 15 | 16 | import qi 17 | 18 | 19 | def get_logger(session, app_id): 20 | """Returns a qi logger object.""" 21 | logger = qi.logging.Logger(app_id) 22 | try: 23 | qicore = qi.module("qicore") 24 | log_manager = session.service("LogManager") 25 | provider = qicore.createObject("LogProvider", log_manager) 26 | log_manager.addProvider(provider) 27 | except RuntimeError: 28 | # no qicore, we're not running on a robot, it doesn't matter 29 | pass 30 | except AttributeError: 31 | # old version of NAOqi - logging will probably not work. 32 | pass 33 | return logger 34 | 35 | 36 | def log_exceptions(func): 37 | """Catches all exceptions in decorated method, and prints them. 38 | 39 | Attached function must be on an object with a "logger" member. 40 | """ 41 | @functools.wraps(func) 42 | def wrapped(self, *args): 43 | try: 44 | return func(self, *args) 45 | except Exception as exc: 46 | self.logger.error(traceback.format_exc()) 47 | raise exc 48 | return wrapped 49 | 50 | 51 | def log_exceptions_and_return(default_value): 52 | """If an exception occurs, print it and return default_value. 53 | 54 | Attached function must be on an object with a "logger" member. 55 | """ 56 | def decorator(func): 57 | @functools.wraps(func) 58 | def wrapped(self, *args): 59 | try: 60 | return func(self, *args) 61 | except Exception: 62 | self.logger.error(traceback.format_exc()) 63 | return default_value 64 | return wrapped 65 | return decorator 66 | -------------------------------------------------------------------------------- /templates/pythonapp/app/scripts/stk/runner.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.runner.py 3 | 4 | A helper library for making simple standalone python scripts as apps. 5 | 6 | Wraps some NAOqi and system stuff, you could do all this by directly using the 7 | Python SDK, these helper functions just isolate some frequently used/hairy 8 | bits so you don't have them mixed in your logic. 9 | """ 10 | 11 | __version__ = "0.1.3" 12 | 13 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 14 | __author__ = 'ekroeger' 15 | __email__ = 'ekroeger@aldebaran.com' 16 | 17 | import sys 18 | import qi 19 | from distutils.version import LooseVersion 20 | 21 | # 22 | # Helpers for making sure we have a robot to connect to 23 | # 24 | 25 | 26 | def check_commandline_args(description): 27 | "Checks whether command-line parameters are enough" 28 | import argparse 29 | parser = argparse.ArgumentParser(description=description) 30 | parser.add_argument('--qi-url', help='connect to specific NAOqi instance') 31 | 32 | args = parser.parse_args() 33 | return args 34 | 35 | 36 | def is_on_robot(): 37 | "Returns whether this is being executed on an Aldebaran robot." 38 | import platform 39 | return "aldebaran" in platform.platform() 40 | 41 | 42 | def get_debug_robot(): 43 | "Returns IP address of debug robot, complaining if not found" 44 | try: 45 | import qiq.config 46 | qiqrobot = qiq.config.defaultHost() 47 | if qiqrobot: 48 | robot = raw_input( 49 | "connect to which robot? (default is {0}) ".format(qiqrobot)) 50 | if robot: 51 | return robot 52 | else: 53 | return qiqrobot 54 | else: 55 | print "qiq found, but it has no default robot configured." 56 | except ImportError: 57 | # qiq not installed 58 | print "qiq not installed (you can use it to set a default robot)." 59 | return raw_input("connect to which robot? ") 60 | 61 | 62 | def init(qi_url=None): 63 | "Returns a QiApplication object, possibly with interactive input." 64 | if qi_url: 65 | sys.argv.extend(["--qi-url", qi_url]) 66 | else: 67 | args = check_commandline_args('Run the app.') 68 | if bool(args.qi_url): 69 | qi_url = args.qi_url 70 | elif not is_on_robot(): 71 | print "no --qi-url parameter given; interactively getting debug robot." 72 | debug_robot = get_debug_robot() 73 | if debug_robot: 74 | sys.argv.extend(["--qi-url", debug_robot]) 75 | qi_url = debug_robot 76 | else: 77 | raise RuntimeError("No robot, not running.") 78 | 79 | qiapp = None 80 | sys.argv[0] = str(sys.argv[0]) 81 | 82 | # In versions bellow 2.3, look for --qi-url in the arguemnts and call accordingly the Application 83 | if qi_url and hasattr(qi, "__version__") and LooseVersion(qi.__version__) < LooseVersion("2.3"): 84 | qiapp = qi.Application(url="tcp://"+qi_url+":9559") 85 | # In versions greater than 2.3 the ip can simply be passed through argv[0] 86 | else: 87 | # In some environments sys.argv[0] has unicode, which qi rejects 88 | qiapp = qi.Application() 89 | 90 | qiapp.start() 91 | return qiapp 92 | 93 | 94 | # Main runner 95 | 96 | def run_activity(activity_class, service_name=None): 97 | """Instantiate the given class, and runs it. 98 | 99 | The given class must take a qiapplication object as parameter, and may also 100 | have on_start and on_stop methods, that will be called before and after 101 | running it.""" 102 | qiapp = init() 103 | activity = activity_class(qiapp) 104 | service_id = None 105 | 106 | try: 107 | # if it's a service, register it 108 | if service_name: 109 | # Note: this will fail if there is already a service. Unregistering 110 | # it would not be a good practice, because it's process would still 111 | # be running. 112 | service_id = qiapp.session.registerService(service_name, activity) 113 | 114 | if hasattr(activity, "on_start"): 115 | def handle_on_start_done(on_start_future): 116 | "Custom callback, for checking errors" 117 | if on_start_future.hasError(): 118 | try: 119 | msg = "Error in on_start(), stopping application: %s" \ 120 | % on_start_future.error() 121 | if hasattr(activity, "logger"): 122 | activity.logger.error(msg) 123 | else: 124 | print msg 125 | finally: 126 | qiapp.stop() 127 | qi.async(activity.on_start).addCallback(handle_on_start_done) 128 | 129 | # Run the QiApplication, which runs until someone calls qiapp.stop() 130 | qiapp.run() 131 | 132 | finally: 133 | # Cleanup 134 | if hasattr(activity, "on_stop"): 135 | # We need a qi.async call so that if the class is single threaded, 136 | # it will wait for callbacks to be finished. 137 | qi.async(activity.on_stop).wait() 138 | if service_id: 139 | qiapp.session.unregisterService(service_id) 140 | 141 | 142 | def run_service(service_class, service_name=None): 143 | """Instantiate the given class, and registers it as a NAOqi service. 144 | 145 | The given class must take a qiapplication object as parameter, and may also 146 | have on_start and on_stop methods, that will be called before and after 147 | running it. 148 | 149 | If the service_name parameter is not given, the classes' name will be used. 150 | """ 151 | if not service_name: 152 | service_name = service_class.__name__ 153 | run_activity(service_class, service_name) 154 | -------------------------------------------------------------------------------- /templates/pythonapp/app/scripts/stk/services.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.services.py 3 | 4 | Syntactic sugar for accessing NAOqi services. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | 14 | class ServiceCache(object): 15 | "A helper for accessing NAOqi services." 16 | 17 | def __init__(self, session=None): 18 | self.session = None 19 | self.services = {} 20 | if session: 21 | self.init(session) 22 | 23 | def init(self, session): 24 | "Sets the session object, if it wasn't passed to constructor." 25 | self.session = session 26 | 27 | def __getattr__(self, servicename): 28 | "We overload this so (instance).ALMotion returns the service, or None." 29 | if (not servicename in self.services) or ( 30 | servicename == "ALTabletService"): 31 | # ugly hack: never cache ALtabletService, always ask for a new one 32 | if servicename.startswith("__"): 33 | # Behave like a normal python object for those 34 | raise AttributeError 35 | try: 36 | self.services[servicename] = self.session.service(servicename) 37 | except RuntimeError: # Cannot find service 38 | self.services[servicename] = None 39 | return self.services[servicename] 40 | -------------------------------------------------------------------------------- /templates/service-tabletpage/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/.metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/service-tabletpage/app/.metadata -------------------------------------------------------------------------------- /templates/service-tabletpage/app/behavior.xar: -------------------------------------------------------------------------------- 1 | media/images/box/root.pngmedia/images/box/root.png -------------------------------------------------------------------------------- /templates/service-tabletpage/app/html/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 3 | } 4 | 5 | body{ 6 | margin: 0px; 7 | background-color: black; 8 | } 9 | 10 | .centered { 11 | position: absolute; 12 | top: 50%; 13 | left: 50%; 14 | margin-right: -50%; 15 | transform: translate(-50%, -50%); 16 | text-align: center; 17 | } 18 | 19 | .levelbutton { 20 | border-radius: 25px; 21 | display: inline-block; 22 | width: 250px; 23 | height: 100px; 24 | background-color: blue; 25 | color: white; 26 | text-align: center; 27 | font-family: Arial, Helvetica, sans-serif; 28 | font-size: 35px; 29 | font-style: normal; 30 | font-weight: bold; 31 | line-height: 50px; 32 | } 33 | 34 | .levelbutton.clicked { 35 | background-color: grey; 36 | } 37 | 38 | .levelbutton.clicked.highlighted { 39 | background-color: darkblue; 40 | } 41 | 42 | .tabletsized { 43 | background-color: lightblue; 44 | width: 1280px; 45 | height: 800px; 46 | position: absolute; 47 | } 48 | 49 | #exit { 50 | font-size: 100px; 51 | position: absolute; 52 | top: 0px; 53 | right: 15px; 54 | } 55 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 20 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/html/js/main.js: -------------------------------------------------------------------------------- 1 | var application = function(){ 2 | RobotUtils.onService(function (ALMyService) { 3 | $("#noservice").hide(); 4 | ALMyService.get().then(function(level) { 5 | // Find the button with the right level: 6 | $(".levelbutton").each(function() { 7 | var button = $(this); 8 | if (button.data("level") == level) { 9 | button.addClass("highlighted"); 10 | button.addClass("clicked"); 11 | } 12 | }); 13 | // ... and show all buttons: 14 | $("#buttons").show(); 15 | }); 16 | $(".levelbutton").click(function() { 17 | // grey out the button, until we hear back that the click worked. 18 | var button = $(this); 19 | var level = button.data("level"); 20 | $(".levelbutton").removeClass("highlighted"); 21 | $(".levelbutton").removeClass("clicked"); 22 | button.addClass("clicked"); 23 | ALMyService.set(level).then(function(){ 24 | button.addClass("highlighted"); 25 | }); 26 | }) 27 | }, function() { 28 | console.log("Failed to get the service.") 29 | $("#noservice").show(); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/service-tabletpage/app/icon.png -------------------------------------------------------------------------------- /templates/service-tabletpage/app/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | service-tabletpage 5 | 6 | 7 | en_US 8 | 9 | 10 | en_US 11 | 12 | 13 | 14 | 15 | interactive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/scripts/myservice.py: -------------------------------------------------------------------------------- 1 | """ 2 | A sample showing how to have a NAOqi service as a Python app. 3 | """ 4 | 5 | __version__ = "0.0.3" 6 | 7 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 8 | __author__ = 'ekroeger' 9 | __email__ = 'ekroeger@aldebaran.com' 10 | 11 | 12 | import qi 13 | 14 | import stk.runner 15 | import stk.events 16 | import stk.services 17 | import stk.logging 18 | 19 | class ALMyService(object): 20 | "NAOqi service example (set/get on a simple value)." 21 | APP_ID = "com.aldebaran.ALMyService" 22 | def __init__(self, qiapp): 23 | # generic activity boilerplate 24 | self.qiapp = qiapp 25 | self.events = stk.events.EventHelper(qiapp.session) 26 | self.s = stk.services.ServiceCache(qiapp.session) 27 | self.logger = stk.logging.get_logger(qiapp.session, self.APP_ID) 28 | # Internal variables 29 | self.level = 0 30 | 31 | @qi.bind(returnType=qi.Void, paramsType=[qi.Int8]) 32 | def set(self, level): 33 | "Set level" 34 | self.level = level 35 | 36 | @qi.bind(returnType=qi.Int8, paramsType=[]) 37 | def get(self): 38 | "Get level" 39 | return self.level 40 | 41 | @qi.bind(returnType=qi.Void, paramsType=[]) 42 | def reset(self): 43 | "Reset level to default value" 44 | return self.set(0) 45 | 46 | @qi.bind(returnType=qi.Void, paramsType=[]) 47 | def stop(self): 48 | "Stop the service." 49 | self.logger.info("ALMyService stopped by user request.") 50 | self.qiapp.stop() 51 | 52 | @qi.nobind 53 | def on_stop(self): 54 | "Cleanup (add yours if needed)" 55 | self.logger.info("ALMyService finished.") 56 | 57 | #################### 58 | # Setup and Run 59 | #################### 60 | 61 | if __name__ == "__main__": 62 | stk.runner.run_service(ALMyService) 63 | 64 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/scripts/stk/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | STK - A collection of libraries useful for making apps with NAOqi. 3 | """ 4 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/scripts/stk/logging.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.logging.py 3 | 4 | Utility library for logging with qi. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | import functools 14 | import traceback 15 | 16 | import qi 17 | 18 | 19 | def get_logger(session, app_id): 20 | """Returns a qi logger object.""" 21 | logger = qi.logging.Logger(app_id) 22 | try: 23 | qicore = qi.module("qicore") 24 | log_manager = session.service("LogManager") 25 | provider = qicore.createObject("LogProvider", log_manager) 26 | log_manager.addProvider(provider) 27 | except RuntimeError: 28 | # no qicore, we're not running on a robot, it doesn't matter 29 | pass 30 | except AttributeError: 31 | # old version of NAOqi - logging will probably not work. 32 | pass 33 | return logger 34 | 35 | 36 | def log_exceptions(func): 37 | """Catches all exceptions in decorated method, and prints them. 38 | 39 | Attached function must be on an object with a "logger" member. 40 | """ 41 | @functools.wraps(func) 42 | def wrapped(self, *args): 43 | try: 44 | return func(self, *args) 45 | except Exception as exc: 46 | self.logger.error(traceback.format_exc()) 47 | raise exc 48 | return wrapped 49 | 50 | 51 | def log_exceptions_and_return(default_value): 52 | """If an exception occurs, print it and return default_value. 53 | 54 | Attached function must be on an object with a "logger" member. 55 | """ 56 | def decorator(func): 57 | @functools.wraps(func) 58 | def wrapped(self, *args): 59 | try: 60 | return func(self, *args) 61 | except Exception: 62 | self.logger.error(traceback.format_exc()) 63 | return default_value 64 | return wrapped 65 | return decorator 66 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/scripts/stk/runner.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.runner.py 3 | 4 | A helper library for making simple standalone python scripts as apps. 5 | 6 | Wraps some NAOqi and system stuff, you could do all this by directly using the 7 | Python SDK, these helper functions just isolate some frequently used/hairy 8 | bits so you don't have them mixed in your logic. 9 | """ 10 | 11 | __version__ = "0.1.3" 12 | 13 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 14 | __author__ = 'ekroeger' 15 | __email__ = 'ekroeger@aldebaran.com' 16 | 17 | import sys 18 | import qi 19 | from distutils.version import LooseVersion 20 | 21 | # 22 | # Helpers for making sure we have a robot to connect to 23 | # 24 | 25 | 26 | def check_commandline_args(description): 27 | "Checks whether command-line parameters are enough" 28 | import argparse 29 | parser = argparse.ArgumentParser(description=description) 30 | parser.add_argument('--qi-url', help='connect to specific NAOqi instance') 31 | 32 | args = parser.parse_args() 33 | return args 34 | 35 | 36 | def is_on_robot(): 37 | "Returns whether this is being executed on an Aldebaran robot." 38 | import platform 39 | return "aldebaran" in platform.platform() 40 | 41 | 42 | def get_debug_robot(): 43 | "Returns IP address of debug robot, complaining if not found" 44 | try: 45 | import qiq.config 46 | qiqrobot = qiq.config.defaultHost() 47 | if qiqrobot: 48 | robot = raw_input( 49 | "connect to which robot? (default is {0}) ".format(qiqrobot)) 50 | if robot: 51 | return robot 52 | else: 53 | return qiqrobot 54 | else: 55 | print "qiq found, but it has no default robot configured." 56 | except ImportError: 57 | # qiq not installed 58 | print "qiq not installed (you can use it to set a default robot)." 59 | return raw_input("connect to which robot? ") 60 | 61 | 62 | def init(qi_url=None): 63 | "Returns a QiApplication object, possibly with interactive input." 64 | if qi_url: 65 | sys.argv.extend(["--qi-url", qi_url]) 66 | else: 67 | args = check_commandline_args('Run the app.') 68 | if bool(args.qi_url): 69 | qi_url = args.qi_url 70 | elif not is_on_robot(): 71 | print "no --qi-url parameter given; interactively getting debug robot." 72 | debug_robot = get_debug_robot() 73 | if debug_robot: 74 | sys.argv.extend(["--qi-url", debug_robot]) 75 | qi_url = debug_robot 76 | else: 77 | raise RuntimeError("No robot, not running.") 78 | 79 | qiapp = None 80 | sys.argv[0] = str(sys.argv[0]) 81 | 82 | # In versions bellow 2.3, look for --qi-url in the arguemnts and call accordingly the Application 83 | if qi_url and hasattr(qi, "__version__") and LooseVersion(qi.__version__) < LooseVersion("2.3"): 84 | qiapp = qi.Application(url="tcp://"+qi_url+":9559") 85 | # In versions greater than 2.3 the ip can simply be passed through argv[0] 86 | else: 87 | # In some environments sys.argv[0] has unicode, which qi rejects 88 | qiapp = qi.Application() 89 | 90 | qiapp.start() 91 | return qiapp 92 | 93 | 94 | # Main runner 95 | 96 | def run_activity(activity_class, service_name=None): 97 | """Instantiate the given class, and runs it. 98 | 99 | The given class must take a qiapplication object as parameter, and may also 100 | have on_start and on_stop methods, that will be called before and after 101 | running it.""" 102 | qiapp = init() 103 | activity = activity_class(qiapp) 104 | service_id = None 105 | 106 | try: 107 | # if it's a service, register it 108 | if service_name: 109 | # Note: this will fail if there is already a service. Unregistering 110 | # it would not be a good practice, because it's process would still 111 | # be running. 112 | service_id = qiapp.session.registerService(service_name, activity) 113 | 114 | if hasattr(activity, "on_start"): 115 | def handle_on_start_done(on_start_future): 116 | "Custom callback, for checking errors" 117 | if on_start_future.hasError(): 118 | try: 119 | msg = "Error in on_start(), stopping application: %s" \ 120 | % on_start_future.error() 121 | if hasattr(activity, "logger"): 122 | activity.logger.error(msg) 123 | else: 124 | print msg 125 | finally: 126 | qiapp.stop() 127 | qi.async(activity.on_start).addCallback(handle_on_start_done) 128 | 129 | # Run the QiApplication, which runs until someone calls qiapp.stop() 130 | qiapp.run() 131 | 132 | finally: 133 | # Cleanup 134 | if hasattr(activity, "on_stop"): 135 | # We need a qi.async call so that if the class is single threaded, 136 | # it will wait for callbacks to be finished. 137 | qi.async(activity.on_stop).wait() 138 | if service_id: 139 | qiapp.session.unregisterService(service_id) 140 | 141 | 142 | def run_service(service_class, service_name=None): 143 | """Instantiate the given class, and registers it as a NAOqi service. 144 | 145 | The given class must take a qiapplication object as parameter, and may also 146 | have on_start and on_stop methods, that will be called before and after 147 | running it. 148 | 149 | If the service_name parameter is not given, the classes' name will be used. 150 | """ 151 | if not service_name: 152 | service_name = service_class.__name__ 153 | run_activity(service_class, service_name) 154 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/scripts/stk/services.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.services.py 3 | 4 | Syntactic sugar for accessing NAOqi services. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | 14 | class ServiceCache(object): 15 | "A helper for accessing NAOqi services." 16 | 17 | def __init__(self, session=None): 18 | self.session = None 19 | self.services = {} 20 | if session: 21 | self.init(session) 22 | 23 | def init(self, session): 24 | "Sets the session object, if it wasn't passed to constructor." 25 | self.session = session 26 | 27 | def __getattr__(self, servicename): 28 | "We overload this so (instance).ALMotion returns the service, or None." 29 | if (not servicename in self.services) or ( 30 | servicename == "ALTabletService"): 31 | # ugly hack: never cache ALtabletService, always ask for a new one 32 | if servicename.startswith("__"): 33 | # Behave like a normal python object for those 34 | raise AttributeError 35 | try: 36 | self.services[servicename] = self.session.service(servicename) 37 | except RuntimeError: # Cannot find service 38 | self.services[servicename] = None 39 | return self.services[servicename] 40 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/service-tabletpage.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/service-tabletpage/app/translations/translation_en_US.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /templates/service-tabletpage/debug/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

App debug interface!

7 |

Pick the robot to use

8 | Robot IP: 9 |
10 | 11 | 12 |
13 |

The app's page will be displayed as if it was on a tablet connected to that robot

14 | 15 | 16 | 17 | 18 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/service-tabletpage/debug/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.4.1 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2006, 2014 Klaus Hartl 6 | * Released under the MIT license 7 | */ 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD (Register as an anonymous module) 11 | define(['jquery'], factory); 12 | } else if (typeof exports === 'object') { 13 | // Node/CommonJS 14 | module.exports = factory(require('jquery')); 15 | } else { 16 | // Browser globals 17 | factory(jQuery); 18 | } 19 | }(function ($) { 20 | 21 | var pluses = /\+/g; 22 | 23 | function encode(s) { 24 | return config.raw ? s : encodeURIComponent(s); 25 | } 26 | 27 | function decode(s) { 28 | return config.raw ? s : decodeURIComponent(s); 29 | } 30 | 31 | function stringifyCookieValue(value) { 32 | return encode(config.json ? JSON.stringify(value) : String(value)); 33 | } 34 | 35 | function parseCookieValue(s) { 36 | if (s.indexOf('"') === 0) { 37 | // This is a quoted cookie as according to RFC2068, unescape... 38 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); 39 | } 40 | 41 | try { 42 | // Replace server-side written pluses with spaces. 43 | // If we can't decode the cookie, ignore it, it's unusable. 44 | // If we can't parse the cookie, ignore it, it's unusable. 45 | s = decodeURIComponent(s.replace(pluses, ' ')); 46 | return config.json ? JSON.parse(s) : s; 47 | } catch(e) {} 48 | } 49 | 50 | function read(s, converter) { 51 | var value = config.raw ? s : parseCookieValue(s); 52 | return $.isFunction(converter) ? converter(value) : value; 53 | } 54 | 55 | var config = $.cookie = function (key, value, options) { 56 | 57 | // Write 58 | 59 | if (arguments.length > 1 && !$.isFunction(value)) { 60 | options = $.extend({}, config.defaults, options); 61 | 62 | if (typeof options.expires === 'number') { 63 | var days = options.expires, t = options.expires = new Date(); 64 | t.setMilliseconds(t.getMilliseconds() + days * 864e+5); 65 | } 66 | 67 | return (document.cookie = [ 68 | encode(key), '=', stringifyCookieValue(value), 69 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 70 | options.path ? '; path=' + options.path : '', 71 | options.domain ? '; domain=' + options.domain : '', 72 | options.secure ? '; secure' : '' 73 | ].join('')); 74 | } 75 | 76 | // Read 77 | 78 | var result = key ? undefined : {}, 79 | // To prevent the for loop in the first place assign an empty array 80 | // in case there are no cookies at all. Also prevents odd result when 81 | // calling $.cookie(). 82 | cookies = document.cookie ? document.cookie.split('; ') : [], 83 | i = 0, 84 | l = cookies.length; 85 | 86 | for (; i < l; i++) { 87 | var parts = cookies[i].split('='), 88 | name = decode(parts.shift()), 89 | cookie = parts.join('='); 90 | 91 | if (key === name) { 92 | // If second argument (value) is a function it's a converter... 93 | result = read(cookie, value); 94 | break; 95 | } 96 | 97 | // Prevent storing a cookie that we couldn't decode. 98 | if (!key && (cookie = read(cookie)) !== undefined) { 99 | result[name] = cookie; 100 | } 101 | } 102 | 103 | return result; 104 | }; 105 | 106 | config.defaults = {}; 107 | 108 | $.removeCookie = function (key, options) { 109 | // Must not alter options, thus extending a fresh object... 110 | $.cookie(key, '', $.extend({}, options, { expires: -1 })); 111 | return !$.cookie(key); 112 | }; 113 | 114 | })); 115 | -------------------------------------------------------------------------------- /templates/service-tabletpage/serve.py: -------------------------------------------------------------------------------- 1 | """ 2 | A helper script for testing your app's local html files, with no robot install. 3 | 4 | Usage: 5 | Run serve.py, a browser will open on a debug page, enter your robot's IP address 6 | and your local pages will be served as if they were on the robot. 7 | 8 | Using a server like this (as opposed to just file:// etc.) is only necessary 9 | if you do ajax calls in your page. 10 | """ 11 | 12 | __version__ = "0.0.3" 13 | 14 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 15 | __author__ = 'ekroeger' 16 | __email__ = 'ekroeger@aldebaran.com' 17 | 18 | import os 19 | import threading 20 | import webbrowser 21 | import BaseHTTPServer 22 | import SimpleHTTPServer 23 | import urlparse 24 | import urllib 25 | 26 | USE_SERVER = False 27 | 28 | PORT = 8081 29 | 30 | def open_browser(): 31 | """Start a browser after waiting for half a second.""" 32 | if USE_SERVER: 33 | def _open_browser(): 34 | url = 'http://localhost:%s/debug/index.html' % PORT 35 | webbrowser.open(url) 36 | thread = threading.Timer(0.5, _open_browser) 37 | thread.start() 38 | else: 39 | indexpath = os.path.join(os.getcwd(), "debug/index.html") 40 | url = urlparse.urljoin('file:', urllib.pathname2url(indexpath)) 41 | webbrowser.open(url) 42 | 43 | def start_server(): 44 | """Start the server.""" 45 | if USE_SERVER: 46 | server_address = ("", PORT) 47 | handler_class = SimpleHTTPServer.SimpleHTTPRequestHandler 48 | handler_class.extensions_map['.png'] = 'image/png' 49 | server = BaseHTTPServer.HTTPServer(server_address, handler_class) 50 | server.serve_forever() 51 | 52 | def run(): 53 | open_browser() # Comment out this line if you don't want to open a browser 54 | start_server() 55 | 56 | if __name__ == "__main__": 57 | run() 58 | 59 | -------------------------------------------------------------------------------- /templates/service-tabletpage/testrun.py: -------------------------------------------------------------------------------- 1 | """ 2 | A test runner for a NAOqi service (to test the service packaged in the app) 3 | 4 | Edit it to set your robot's IP to run it (or use arv etc.) 5 | 6 | So far it's pretty hard-coded 7 | """ 8 | 9 | __version__ = "0.0.1" 10 | 11 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 12 | __author__ = 'ekroeger' 13 | __email__ = 'ekroeger@aldebaran.com' 14 | 15 | 16 | import subprocess 17 | import time 18 | import sys 19 | import traceback 20 | 21 | import qi 22 | 23 | ######################################### 24 | # Helper base class 25 | ######################################### 26 | 27 | class ServiceTester: 28 | _app = None 29 | _testresults = [] 30 | 31 | @classmethod 32 | def configure(cls, robot): 33 | sys.argv.extend(["--qi-url", robot]) 34 | cls.robot = robot 35 | cls._app = qi.Application() 36 | cls._app.start() 37 | 38 | @classmethod 39 | def recap(cls): 40 | succeeded = 0 41 | failed = [] 42 | for name, result in cls._testresults: 43 | if result: 44 | succeeded += 1 45 | else: 46 | failed.append(name) 47 | print "================================================" 48 | print "Successes: {0}/{1}".format(succeeded, len(cls._testresults)) 49 | if failed: 50 | print "Failed tests:", ", ".join(failed) 51 | else: 52 | print "All OK :)" 53 | 54 | def service(self, service_name): 55 | return self._app.session.service(service_name) 56 | 57 | def __init__(self): 58 | self.name = self.__class__.__name__ 59 | self.start() 60 | 61 | def start(self): 62 | command = ["/usr/bin/python", self.script, "--qi-url", self.robot] 63 | self.popen = subprocess.Popen(command, stdout=subprocess.PIPE, 64 | stderr=subprocess.STDOUT) 65 | self.running = True 66 | #print "Spawned", self.script, "with ID", self.popen.pid 67 | #print 68 | 69 | def finish_and_dump(self, verbose=True): 70 | if self.running: 71 | self.running = False 72 | self.popen.terminate() 73 | if verbose: 74 | print 75 | print "Killed. Output:" 76 | lines_iterator = iter(self.popen.stdout.readline, b"") 77 | for line in lines_iterator: 78 | print ">", line.strip("\n") # yield line 79 | print 80 | 81 | def run_wrapped(self): 82 | succeeded = False 83 | try: 84 | print 85 | print "--------------------" 86 | print "starting", self.name 87 | succeeded = self.run() 88 | print "finished", self.name 89 | except Exception as e: 90 | print "error in", self.name 91 | print traceback.format_exc() 92 | finally: 93 | self.finish_and_dump(verbose=(not succeeded)) 94 | self._testresults.append((self.name, succeeded)) 95 | 96 | ######################################### 97 | # Specific test classes 98 | ######################################### 99 | 100 | class SetGetTest(ServiceTester): 101 | script = "app/scripts/myservice.py" 102 | def run(self): 103 | time.sleep(1) 104 | 105 | ALMyService = self.service("ALMyService") 106 | ALMyService.set(0) 107 | assert ALMyService.get() == 0 108 | ALMyService.set(2) 109 | assert ALMyService.get() == 2 110 | return True 111 | 112 | 113 | class ResetTest(ServiceTester): 114 | script = "app/scripts/myservice.py" 115 | def run(self): 116 | time.sleep(1) 117 | 118 | ALMyService = self.service("ALMyService") 119 | ALMyService.reset() 120 | assert ALMyService.get() == 0 121 | return True 122 | 123 | 124 | class ExitTest(ServiceTester): 125 | script = "app/scripts/myservice.py" 126 | def run(self): 127 | time.sleep(1) 128 | 129 | ALMyService = self.service("ALMyService") 130 | goterror = False 131 | try: 132 | ALMyService.exit() 133 | ALMyService.set(1) 134 | except RuntimeError: 135 | print "Got error after exit, as expected" 136 | goterror = True 137 | assert goterror, "Expected an exception after exit!" 138 | return True 139 | 140 | def run_tests(robotname, *testclasses): 141 | ServiceTester.configure(robotname) 142 | for cls in testclasses: 143 | cls().run_wrapped() 144 | ServiceTester.recap() 145 | 146 | 147 | def test_all(robotname): 148 | run_tests(robotname, 149 | SetGetTest, 150 | ResetTest, 151 | ExitTest, 152 | ) 153 | 154 | if __name__ == "__main__": 155 | ROBOTNAME = "yourrobot.local" # Adapt this depending of your robot 156 | ROBOTNAME = "citadelle.local" 157 | test_all(ROBOTNAME) 158 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/.metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/service-webpage-nao/app/.metadata -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/html/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 3 | } 4 | 5 | body{ 6 | margin: 0px; 7 | background-color: black; 8 | } 9 | 10 | .centered { 11 | position: absolute; 12 | top: 50%; 13 | left: 50%; 14 | margin-right: -50%; 15 | transform: translate(-50%, -50%); 16 | text-align: center; 17 | } 18 | 19 | .levelbutton { 20 | border-radius: 25px; 21 | display: inline-block; 22 | width: 250px; 23 | height: 100px; 24 | background-color: blue; 25 | color: white; 26 | text-align: center; 27 | font-family: Arial, Helvetica, sans-serif; 28 | font-size: 35px; 29 | font-style: normal; 30 | font-weight: bold; 31 | line-height: 50px; 32 | } 33 | 34 | .levelbutton.clicked { 35 | background-color: grey; 36 | } 37 | 38 | .levelbutton.clicked.highlighted { 39 | background-color: darkblue; 40 | } 41 | 42 | .tabletsized { 43 | background-color: lightblue; 44 | width: 1280px; 45 | height: 800px; 46 | position: absolute; 47 | } 48 | 49 | #exit { 50 | font-size: 100px; 51 | position: absolute; 52 | top: 0px; 53 | right: 15px; 54 | } 55 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 17 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/html/js/main.js: -------------------------------------------------------------------------------- 1 | var application = function(){ 2 | RobotUtils.onService(function (ALMyService) { 3 | $("#noservice").hide(); 4 | ALMyService.get().then(function(level) { 5 | // Find the button with the right level: 6 | $(".levelbutton").each(function() { 7 | var button = $(this); 8 | if (button.data("level") == level) { 9 | button.addClass("highlighted"); 10 | button.addClass("clicked"); 11 | } 12 | }); 13 | // ... and show all buttons: 14 | $("#buttons").show(); 15 | }); 16 | $(".levelbutton").click(function() { 17 | // grey out the button, until we hear back that the click worked. 18 | var button = $(this); 19 | var level = button.data("level"); 20 | $(".levelbutton").removeClass("highlighted"); 21 | $(".levelbutton").removeClass("clicked"); 22 | button.addClass("clicked"); 23 | ALMyService.set(level).then(function(){ 24 | button.addClass("highlighted"); 25 | }); 26 | }) 27 | }, function() { 28 | console.log("Failed to get the service.") 29 | $("#noservice").show(); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/service-webpage-nao/app/icon.png -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | service-webpage-nao 5 | 6 | 7 | en_US 8 | 9 | 10 | en_US 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/scripts/myservice.py: -------------------------------------------------------------------------------- 1 | """ 2 | A sample showing how to have a NAOqi service as a Python app. 3 | """ 4 | 5 | __version__ = "0.0.3" 6 | 7 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 8 | __author__ = 'ekroeger' 9 | __email__ = 'ekroeger@aldebaran.com' 10 | 11 | 12 | import qi 13 | 14 | import stk.runner 15 | import stk.events 16 | import stk.services 17 | import stk.logging 18 | 19 | class ALMyService(object): 20 | "NAOqi service example (set/get on a simple value)." 21 | APP_ID = "com.aldebaran.ALMyService" 22 | def __init__(self, qiapp): 23 | # generic activity boilerplate 24 | self.qiapp = qiapp 25 | self.events = stk.events.EventHelper(qiapp.session) 26 | self.s = stk.services.ServiceCache(qiapp.session) 27 | self.logger = stk.logging.get_logger(qiapp.session, self.APP_ID) 28 | # Internal variables 29 | self.level = 0 30 | 31 | @qi.bind(returnType=qi.Void, paramsType=[qi.Int8]) 32 | def set(self, level): 33 | "Set level" 34 | self.level = level 35 | 36 | @qi.bind(returnType=qi.Int8, paramsType=[]) 37 | def get(self): 38 | "Get level" 39 | return self.level 40 | 41 | @qi.bind(returnType=qi.Void, paramsType=[]) 42 | def reset(self): 43 | "Reset level to default value" 44 | return self.set(0) 45 | 46 | @qi.bind(returnType=qi.Void, paramsType=[]) 47 | def stop(self): 48 | "Stop the service." 49 | self.logger.info("ALMyService stopped by user request.") 50 | self.qiapp.stop() 51 | 52 | @qi.nobind 53 | def on_stop(self): 54 | "Cleanup (add yours if needed)" 55 | self.logger.info("ALMyService finished.") 56 | 57 | #################### 58 | # Setup and Run 59 | #################### 60 | 61 | if __name__ == "__main__": 62 | stk.runner.run_service(ALMyService) 63 | 64 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/scripts/stk/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | STK - A collection of libraries useful for making apps with NAOqi. 3 | """ 4 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/scripts/stk/logging.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.logging.py 3 | 4 | Utility library for logging with qi. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | import functools 14 | import traceback 15 | 16 | import qi 17 | 18 | 19 | def get_logger(session, app_id): 20 | """Returns a qi logger object.""" 21 | logger = qi.logging.Logger(app_id) 22 | try: 23 | qicore = qi.module("qicore") 24 | log_manager = session.service("LogManager") 25 | provider = qicore.createObject("LogProvider", log_manager) 26 | log_manager.addProvider(provider) 27 | except RuntimeError: 28 | # no qicore, we're not running on a robot, it doesn't matter 29 | pass 30 | except AttributeError: 31 | # old version of NAOqi - logging will probably not work. 32 | pass 33 | return logger 34 | 35 | 36 | def log_exceptions(func): 37 | """Catches all exceptions in decorated method, and prints them. 38 | 39 | Attached function must be on an object with a "logger" member. 40 | """ 41 | @functools.wraps(func) 42 | def wrapped(self, *args): 43 | try: 44 | return func(self, *args) 45 | except Exception as exc: 46 | self.logger.error(traceback.format_exc()) 47 | raise exc 48 | return wrapped 49 | 50 | 51 | def log_exceptions_and_return(default_value): 52 | """If an exception occurs, print it and return default_value. 53 | 54 | Attached function must be on an object with a "logger" member. 55 | """ 56 | def decorator(func): 57 | @functools.wraps(func) 58 | def wrapped(self, *args): 59 | try: 60 | return func(self, *args) 61 | except Exception: 62 | self.logger.error(traceback.format_exc()) 63 | return default_value 64 | return wrapped 65 | return decorator 66 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/scripts/stk/runner.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.runner.py 3 | 4 | A helper library for making simple standalone python scripts as apps. 5 | 6 | Wraps some NAOqi and system stuff, you could do all this by directly using the 7 | Python SDK, these helper functions just isolate some frequently used/hairy 8 | bits so you don't have them mixed in your logic. 9 | """ 10 | 11 | __version__ = "0.1.3" 12 | 13 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 14 | __author__ = 'ekroeger' 15 | __email__ = 'ekroeger@aldebaran.com' 16 | 17 | import sys 18 | import qi 19 | from distutils.version import LooseVersion 20 | 21 | # 22 | # Helpers for making sure we have a robot to connect to 23 | # 24 | 25 | 26 | def check_commandline_args(description): 27 | "Checks whether command-line parameters are enough" 28 | import argparse 29 | parser = argparse.ArgumentParser(description=description) 30 | parser.add_argument('--qi-url', help='connect to specific NAOqi instance') 31 | 32 | args = parser.parse_args() 33 | return args 34 | 35 | 36 | def is_on_robot(): 37 | "Returns whether this is being executed on an Aldebaran robot." 38 | import platform 39 | return "aldebaran" in platform.platform() 40 | 41 | 42 | def get_debug_robot(): 43 | "Returns IP address of debug robot, complaining if not found" 44 | try: 45 | import qiq.config 46 | qiqrobot = qiq.config.defaultHost() 47 | if qiqrobot: 48 | robot = raw_input( 49 | "connect to which robot? (default is {0}) ".format(qiqrobot)) 50 | if robot: 51 | return robot 52 | else: 53 | return qiqrobot 54 | else: 55 | print "qiq found, but it has no default robot configured." 56 | except ImportError: 57 | # qiq not installed 58 | print "qiq not installed (you can use it to set a default robot)." 59 | return raw_input("connect to which robot? ") 60 | 61 | 62 | def init(qi_url=None): 63 | "Returns a QiApplication object, possibly with interactive input." 64 | if qi_url: 65 | sys.argv.extend(["--qi-url", qi_url]) 66 | else: 67 | args = check_commandline_args('Run the app.') 68 | if bool(args.qi_url): 69 | qi_url = args.qi_url 70 | elif not is_on_robot(): 71 | print "no --qi-url parameter given; interactively getting debug robot." 72 | debug_robot = get_debug_robot() 73 | if debug_robot: 74 | sys.argv.extend(["--qi-url", debug_robot]) 75 | qi_url = debug_robot 76 | else: 77 | raise RuntimeError("No robot, not running.") 78 | 79 | qiapp = None 80 | sys.argv[0] = str(sys.argv[0]) 81 | 82 | # In versions bellow 2.3, look for --qi-url in the arguemnts and call accordingly the Application 83 | if qi_url and hasattr(qi, "__version__") and LooseVersion(qi.__version__) < LooseVersion("2.3"): 84 | qiapp = qi.Application(url="tcp://"+qi_url+":9559") 85 | # In versions greater than 2.3 the ip can simply be passed through argv[0] 86 | else: 87 | # In some environments sys.argv[0] has unicode, which qi rejects 88 | qiapp = qi.Application() 89 | 90 | qiapp.start() 91 | return qiapp 92 | 93 | 94 | # Main runner 95 | 96 | def run_activity(activity_class, service_name=None): 97 | """Instantiate the given class, and runs it. 98 | 99 | The given class must take a qiapplication object as parameter, and may also 100 | have on_start and on_stop methods, that will be called before and after 101 | running it.""" 102 | qiapp = init() 103 | activity = activity_class(qiapp) 104 | service_id = None 105 | 106 | try: 107 | # if it's a service, register it 108 | if service_name: 109 | # Note: this will fail if there is already a service. Unregistering 110 | # it would not be a good practice, because it's process would still 111 | # be running. 112 | service_id = qiapp.session.registerService(service_name, activity) 113 | 114 | if hasattr(activity, "on_start"): 115 | def handle_on_start_done(on_start_future): 116 | "Custom callback, for checking errors" 117 | if on_start_future.hasError(): 118 | try: 119 | msg = "Error in on_start(), stopping application: %s" \ 120 | % on_start_future.error() 121 | if hasattr(activity, "logger"): 122 | activity.logger.error(msg) 123 | else: 124 | print msg 125 | finally: 126 | qiapp.stop() 127 | qi.async(activity.on_start).addCallback(handle_on_start_done) 128 | 129 | # Run the QiApplication, which runs until someone calls qiapp.stop() 130 | qiapp.run() 131 | 132 | finally: 133 | # Cleanup 134 | if hasattr(activity, "on_stop"): 135 | # We need a qi.async call so that if the class is single threaded, 136 | # it will wait for callbacks to be finished. 137 | qi.async(activity.on_stop).wait() 138 | if service_id: 139 | qiapp.session.unregisterService(service_id) 140 | 141 | 142 | def run_service(service_class, service_name=None): 143 | """Instantiate the given class, and registers it as a NAOqi service. 144 | 145 | The given class must take a qiapplication object as parameter, and may also 146 | have on_start and on_stop methods, that will be called before and after 147 | running it. 148 | 149 | If the service_name parameter is not given, the classes' name will be used. 150 | """ 151 | if not service_name: 152 | service_name = service_class.__name__ 153 | run_activity(service_class, service_name) 154 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/scripts/stk/services.py: -------------------------------------------------------------------------------- 1 | """ 2 | stk.services.py 3 | 4 | Syntactic sugar for accessing NAOqi services. 5 | """ 6 | 7 | __version__ = "0.1.2" 8 | 9 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 10 | __author__ = 'ekroeger' 11 | __email__ = 'ekroeger@aldebaran.com' 12 | 13 | 14 | class ServiceCache(object): 15 | "A helper for accessing NAOqi services." 16 | 17 | def __init__(self, session=None): 18 | self.session = None 19 | self.services = {} 20 | if session: 21 | self.init(session) 22 | 23 | def init(self, session): 24 | "Sets the session object, if it wasn't passed to constructor." 25 | self.session = session 26 | 27 | def __getattr__(self, servicename): 28 | "We overload this so (instance).ALMotion returns the service, or None." 29 | if (not servicename in self.services) or ( 30 | servicename == "ALTabletService"): 31 | # ugly hack: never cache ALtabletService, always ask for a new one 32 | if servicename.startswith("__"): 33 | # Behave like a normal python object for those 34 | raise AttributeError 35 | try: 36 | self.services[servicename] = self.session.service(servicename) 37 | except RuntimeError: # Cannot find service 38 | self.services[servicename] = None 39 | return self.services[servicename] 40 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/app/service-webpage-nao.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/debug/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

App debug interface!

7 |

Pick the robot to use

8 | Robot IP: 9 |
10 | 11 | 12 |
13 |

The app's page will be displayed as if it was on a tablet connected to that robot

14 | 15 | 16 | 17 | 18 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/debug/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.4.1 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2006, 2014 Klaus Hartl 6 | * Released under the MIT license 7 | */ 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD (Register as an anonymous module) 11 | define(['jquery'], factory); 12 | } else if (typeof exports === 'object') { 13 | // Node/CommonJS 14 | module.exports = factory(require('jquery')); 15 | } else { 16 | // Browser globals 17 | factory(jQuery); 18 | } 19 | }(function ($) { 20 | 21 | var pluses = /\+/g; 22 | 23 | function encode(s) { 24 | return config.raw ? s : encodeURIComponent(s); 25 | } 26 | 27 | function decode(s) { 28 | return config.raw ? s : decodeURIComponent(s); 29 | } 30 | 31 | function stringifyCookieValue(value) { 32 | return encode(config.json ? JSON.stringify(value) : String(value)); 33 | } 34 | 35 | function parseCookieValue(s) { 36 | if (s.indexOf('"') === 0) { 37 | // This is a quoted cookie as according to RFC2068, unescape... 38 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); 39 | } 40 | 41 | try { 42 | // Replace server-side written pluses with spaces. 43 | // If we can't decode the cookie, ignore it, it's unusable. 44 | // If we can't parse the cookie, ignore it, it's unusable. 45 | s = decodeURIComponent(s.replace(pluses, ' ')); 46 | return config.json ? JSON.parse(s) : s; 47 | } catch(e) {} 48 | } 49 | 50 | function read(s, converter) { 51 | var value = config.raw ? s : parseCookieValue(s); 52 | return $.isFunction(converter) ? converter(value) : value; 53 | } 54 | 55 | var config = $.cookie = function (key, value, options) { 56 | 57 | // Write 58 | 59 | if (arguments.length > 1 && !$.isFunction(value)) { 60 | options = $.extend({}, config.defaults, options); 61 | 62 | if (typeof options.expires === 'number') { 63 | var days = options.expires, t = options.expires = new Date(); 64 | t.setMilliseconds(t.getMilliseconds() + days * 864e+5); 65 | } 66 | 67 | return (document.cookie = [ 68 | encode(key), '=', stringifyCookieValue(value), 69 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 70 | options.path ? '; path=' + options.path : '', 71 | options.domain ? '; domain=' + options.domain : '', 72 | options.secure ? '; secure' : '' 73 | ].join('')); 74 | } 75 | 76 | // Read 77 | 78 | var result = key ? undefined : {}, 79 | // To prevent the for loop in the first place assign an empty array 80 | // in case there are no cookies at all. Also prevents odd result when 81 | // calling $.cookie(). 82 | cookies = document.cookie ? document.cookie.split('; ') : [], 83 | i = 0, 84 | l = cookies.length; 85 | 86 | for (; i < l; i++) { 87 | var parts = cookies[i].split('='), 88 | name = decode(parts.shift()), 89 | cookie = parts.join('='); 90 | 91 | if (key === name) { 92 | // If second argument (value) is a function it's a converter... 93 | result = read(cookie, value); 94 | break; 95 | } 96 | 97 | // Prevent storing a cookie that we couldn't decode. 98 | if (!key && (cookie = read(cookie)) !== undefined) { 99 | result[name] = cookie; 100 | } 101 | } 102 | 103 | return result; 104 | }; 105 | 106 | config.defaults = {}; 107 | 108 | $.removeCookie = function (key, options) { 109 | // Must not alter options, thus extending a fresh object... 110 | $.cookie(key, '', $.extend({}, options, { expires: -1 })); 111 | return !$.cookie(key); 112 | }; 113 | 114 | })); 115 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/serve.py: -------------------------------------------------------------------------------- 1 | """ 2 | A helper script for testing your app's local html files, with no robot install. 3 | 4 | Usage: 5 | Run serve.py, a browser will open on a debug page, enter your robot's IP address 6 | and your local pages will be served as if they were on the robot. 7 | 8 | Using a server like this (as opposed to just file:// etc.) is only necessary 9 | if you do ajax calls in your page. 10 | """ 11 | 12 | __version__ = "0.0.3" 13 | 14 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 15 | __author__ = 'ekroeger' 16 | __email__ = 'ekroeger@aldebaran.com' 17 | 18 | import os 19 | import threading 20 | import webbrowser 21 | import BaseHTTPServer 22 | import SimpleHTTPServer 23 | import urlparse 24 | import urllib 25 | 26 | USE_SERVER = False 27 | 28 | PORT = 8081 29 | 30 | def open_browser(): 31 | """Start a browser after waiting for half a second.""" 32 | if USE_SERVER: 33 | def _open_browser(): 34 | url = 'http://localhost:%s/debug/index.html' % PORT 35 | webbrowser.open(url) 36 | thread = threading.Timer(0.5, _open_browser) 37 | thread.start() 38 | else: 39 | indexpath = os.path.join(os.getcwd(), "debug/index.html") 40 | url = urlparse.urljoin('file:', urllib.pathname2url(indexpath)) 41 | webbrowser.open(url) 42 | 43 | def start_server(): 44 | """Start the server.""" 45 | if USE_SERVER: 46 | server_address = ("", PORT) 47 | handler_class = SimpleHTTPServer.SimpleHTTPRequestHandler 48 | handler_class.extensions_map['.png'] = 'image/png' 49 | server = BaseHTTPServer.HTTPServer(server_address, handler_class) 50 | server.serve_forever() 51 | 52 | def run(): 53 | open_browser() # Comment out this line if you don't want to open a browser 54 | start_server() 55 | 56 | if __name__ == "__main__": 57 | run() 58 | 59 | -------------------------------------------------------------------------------- /templates/service-webpage-nao/testrun.py: -------------------------------------------------------------------------------- 1 | """ 2 | A test runner for a NAOqi service (to test the service packaged in the app) 3 | 4 | Edit it to set your robot's IP to run it (or use arv etc.) 5 | 6 | So far it's pretty hard-coded 7 | """ 8 | 9 | __version__ = "0.0.1" 10 | 11 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 12 | __author__ = 'ekroeger' 13 | __email__ = 'ekroeger@aldebaran.com' 14 | 15 | 16 | import subprocess 17 | import time 18 | import sys 19 | import traceback 20 | 21 | import qi 22 | 23 | ######################################### 24 | # Helper base class 25 | ######################################### 26 | 27 | class ServiceTester: 28 | _app = None 29 | _testresults = [] 30 | 31 | @classmethod 32 | def configure(cls, robot): 33 | sys.argv.extend(["--qi-url", robot]) 34 | cls.robot = robot 35 | cls._app = qi.Application() 36 | cls._app.start() 37 | 38 | @classmethod 39 | def recap(cls): 40 | succeeded = 0 41 | failed = [] 42 | for name, result in cls._testresults: 43 | if result: 44 | succeeded += 1 45 | else: 46 | failed.append(name) 47 | print "================================================" 48 | print "Successes: {0}/{1}".format(succeeded, len(cls._testresults)) 49 | if failed: 50 | print "Failed tests:", ", ".join(failed) 51 | else: 52 | print "All OK :)" 53 | 54 | def service(self, service_name): 55 | return self._app.session.service(service_name) 56 | 57 | def __init__(self): 58 | self.name = self.__class__.__name__ 59 | self.start() 60 | 61 | def start(self): 62 | command = ["/usr/bin/python", self.script, "--qi-url", self.robot] 63 | self.popen = subprocess.Popen(command, stdout=subprocess.PIPE, 64 | stderr=subprocess.STDOUT) 65 | self.running = True 66 | #print "Spawned", self.script, "with ID", self.popen.pid 67 | #print 68 | 69 | def finish_and_dump(self, verbose=True): 70 | if self.running: 71 | self.running = False 72 | self.popen.terminate() 73 | if verbose: 74 | print 75 | print "Killed. Output:" 76 | lines_iterator = iter(self.popen.stdout.readline, b"") 77 | for line in lines_iterator: 78 | print ">", line.strip("\n") # yield line 79 | print 80 | 81 | def run_wrapped(self): 82 | succeeded = False 83 | try: 84 | print 85 | print "--------------------" 86 | print "starting", self.name 87 | succeeded = self.run() 88 | print "finished", self.name 89 | except Exception as e: 90 | print "error in", self.name 91 | print traceback.format_exc() 92 | finally: 93 | self.finish_and_dump(verbose=(not succeeded)) 94 | self._testresults.append((self.name, succeeded)) 95 | 96 | ######################################### 97 | # Specific test classes 98 | ######################################### 99 | 100 | class SetGetTest(ServiceTester): 101 | script = "app/scripts/myservice.py" 102 | def run(self): 103 | time.sleep(1) 104 | 105 | ALMyService = self.service("ALMyService") 106 | ALMyService.set(0) 107 | assert ALMyService.get() == 0 108 | ALMyService.set(2) 109 | assert ALMyService.get() == 2 110 | return True 111 | 112 | 113 | class ResetTest(ServiceTester): 114 | script = "app/scripts/myservice.py" 115 | def run(self): 116 | time.sleep(1) 117 | 118 | ALMyService = self.service("ALMyService") 119 | ALMyService.reset() 120 | assert ALMyService.get() == 0 121 | return True 122 | 123 | 124 | class ExitTest(ServiceTester): 125 | script = "app/scripts/myservice.py" 126 | def run(self): 127 | time.sleep(1) 128 | 129 | ALMyService = self.service("ALMyService") 130 | goterror = False 131 | try: 132 | ALMyService.exit() 133 | ALMyService.set(1) 134 | except RuntimeError: 135 | print "Got error after exit, as expected" 136 | goterror = True 137 | assert goterror, "Expected an exception after exit!" 138 | return True 139 | 140 | def run_tests(robotname, *testclasses): 141 | ServiceTester.configure(robotname) 142 | for cls in testclasses: 143 | cls().run_wrapped() 144 | ServiceTester.recap() 145 | 146 | 147 | def test_all(robotname): 148 | run_tests(robotname, 149 | SetGetTest, 150 | ResetTest, 151 | ExitTest, 152 | ) 153 | 154 | if __name__ == "__main__": 155 | ROBOTNAME = "yourrobot.local" # Adapt this depending of your robot 156 | ROBOTNAME = "citadelle.local" 157 | test_all(ROBOTNAME) 158 | -------------------------------------------------------------------------------- /templates/simple-tabletpage/app/.metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/simple-tabletpage/app/.metadata -------------------------------------------------------------------------------- /templates/simple-tabletpage/app/behavior.xar: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | media/images/box/root.png 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | media/images/box/root.png 20 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /templates/simple-tabletpage/app/html/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 3 | } 4 | 5 | body{ 6 | margin: 0px; 7 | background-color: lightblue; 8 | } 9 | 10 | .centered { 11 | position: absolute; 12 | top: 50%; 13 | left: 50%; 14 | margin-right: -50%; 15 | transform: translate(-50%, -50%); 16 | text-align: center; 17 | } 18 | 19 | .bigfriendlybutton { 20 | border-radius: 25px; 21 | display: inline-block; 22 | width: 300px; 23 | height: 100px; 24 | background-color: blue; 25 | color: white; 26 | text-align: center; 27 | font-family: Arial, Helvetica, sans-serif; 28 | font-size: 35px; 29 | font-style: normal; 30 | font-weight: bold; 31 | line-height: 50px; 32 | } 33 | -------------------------------------------------------------------------------- /templates/simple-tabletpage/app/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /templates/simple-tabletpage/app/html/js/main.js: -------------------------------------------------------------------------------- 1 | var application = function(){ 2 | RobotUtils.onService(function(ALTextToSpeech) { 3 | // Bind button callbacks 4 | $(".bleeper").click(function() { 5 | ALTextToSpeech.say($(this).html()); 6 | }); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /templates/simple-tabletpage/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/simple-tabletpage/app/icon.png -------------------------------------------------------------------------------- /templates/simple-tabletpage/app/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | simple-tabletpage 5 | servicedriven 6 | 7 | 8 | en_US 9 | 10 | 11 | en_US 12 | 13 | 14 | 15 | 16 | interactive 17 | 18 | simple-tabletpage 19 | 20 | 21 | Shows a page on the tablet 22 | 23 | 24 | simple tablet page 25 | Lance servicedriven 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /templates/simple-tabletpage/app/simple-tabletpage.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /templates/simple-tabletpage/debug/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

App debug interface!

7 |

Pick the robot to use

8 | Robot IP: 9 |
10 | 11 | 12 |
13 |

The app's page will be displayed as if it was on a tablet connected to that robot

14 | 15 | 16 | 17 | 18 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/simple-tabletpage/debug/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.4.1 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2006, 2014 Klaus Hartl 6 | * Released under the MIT license 7 | */ 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD (Register as an anonymous module) 11 | define(['jquery'], factory); 12 | } else if (typeof exports === 'object') { 13 | // Node/CommonJS 14 | module.exports = factory(require('jquery')); 15 | } else { 16 | // Browser globals 17 | factory(jQuery); 18 | } 19 | }(function ($) { 20 | 21 | var pluses = /\+/g; 22 | 23 | function encode(s) { 24 | return config.raw ? s : encodeURIComponent(s); 25 | } 26 | 27 | function decode(s) { 28 | return config.raw ? s : decodeURIComponent(s); 29 | } 30 | 31 | function stringifyCookieValue(value) { 32 | return encode(config.json ? JSON.stringify(value) : String(value)); 33 | } 34 | 35 | function parseCookieValue(s) { 36 | if (s.indexOf('"') === 0) { 37 | // This is a quoted cookie as according to RFC2068, unescape... 38 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); 39 | } 40 | 41 | try { 42 | // Replace server-side written pluses with spaces. 43 | // If we can't decode the cookie, ignore it, it's unusable. 44 | // If we can't parse the cookie, ignore it, it's unusable. 45 | s = decodeURIComponent(s.replace(pluses, ' ')); 46 | return config.json ? JSON.parse(s) : s; 47 | } catch(e) {} 48 | } 49 | 50 | function read(s, converter) { 51 | var value = config.raw ? s : parseCookieValue(s); 52 | return $.isFunction(converter) ? converter(value) : value; 53 | } 54 | 55 | var config = $.cookie = function (key, value, options) { 56 | 57 | // Write 58 | 59 | if (arguments.length > 1 && !$.isFunction(value)) { 60 | options = $.extend({}, config.defaults, options); 61 | 62 | if (typeof options.expires === 'number') { 63 | var days = options.expires, t = options.expires = new Date(); 64 | t.setMilliseconds(t.getMilliseconds() + days * 864e+5); 65 | } 66 | 67 | return (document.cookie = [ 68 | encode(key), '=', stringifyCookieValue(value), 69 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 70 | options.path ? '; path=' + options.path : '', 71 | options.domain ? '; domain=' + options.domain : '', 72 | options.secure ? '; secure' : '' 73 | ].join('')); 74 | } 75 | 76 | // Read 77 | 78 | var result = key ? undefined : {}, 79 | // To prevent the for loop in the first place assign an empty array 80 | // in case there are no cookies at all. Also prevents odd result when 81 | // calling $.cookie(). 82 | cookies = document.cookie ? document.cookie.split('; ') : [], 83 | i = 0, 84 | l = cookies.length; 85 | 86 | for (; i < l; i++) { 87 | var parts = cookies[i].split('='), 88 | name = decode(parts.shift()), 89 | cookie = parts.join('='); 90 | 91 | if (key === name) { 92 | // If second argument (value) is a function it's a converter... 93 | result = read(cookie, value); 94 | break; 95 | } 96 | 97 | // Prevent storing a cookie that we couldn't decode. 98 | if (!key && (cookie = read(cookie)) !== undefined) { 99 | result[name] = cookie; 100 | } 101 | } 102 | 103 | return result; 104 | }; 105 | 106 | config.defaults = {}; 107 | 108 | $.removeCookie = function (key, options) { 109 | // Must not alter options, thus extending a fresh object... 110 | $.cookie(key, '', $.extend({}, options, { expires: -1 })); 111 | return !$.cookie(key); 112 | }; 113 | 114 | })); 115 | -------------------------------------------------------------------------------- /templates/simple-tabletpage/serve.py: -------------------------------------------------------------------------------- 1 | """ 2 | A helper script for testing your app's local html files, with no robot install. 3 | 4 | Usage: 5 | Run serve.py, a browser will open on a debug page, enter your robot's IP address 6 | and your local pages will be served as if they were on the robot. 7 | 8 | Using a server like this (as opposed to just file:// etc.) is only necessary 9 | if you do ajax calls in your page. 10 | """ 11 | 12 | __version__ = "0.0.3" 13 | 14 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 15 | __author__ = 'ekroeger' 16 | __email__ = 'ekroeger@aldebaran.com' 17 | 18 | import os 19 | import threading 20 | import webbrowser 21 | import BaseHTTPServer 22 | import SimpleHTTPServer 23 | import urlparse 24 | import urllib 25 | 26 | USE_SERVER = False 27 | 28 | PORT = 8081 29 | 30 | def open_browser(): 31 | """Start a browser after waiting for half a second.""" 32 | if USE_SERVER: 33 | def _open_browser(): 34 | url = 'http://localhost:%s/debug/index.html' % PORT 35 | webbrowser.open(url) 36 | thread = threading.Timer(0.5, _open_browser) 37 | thread.start() 38 | else: 39 | indexpath = os.path.join(os.getcwd(), "debug/index.html") 40 | url = urlparse.urljoin('file:', urllib.pathname2url(indexpath)) 41 | webbrowser.open(url) 42 | 43 | def start_server(): 44 | """Start the server.""" 45 | if USE_SERVER: 46 | server_address = ("", PORT) 47 | handler_class = SimpleHTTPServer.SimpleHTTPRequestHandler 48 | handler_class.extensions_map['.png'] = 'image/png' 49 | server = BaseHTTPServer.HTTPServer(server_address, handler_class) 50 | server.serve_forever() 51 | 52 | def run(): 53 | open_browser() # Comment out this line if you don't want to open a browser 54 | start_server() 55 | 56 | if __name__ == "__main__": 57 | run() 58 | 59 | -------------------------------------------------------------------------------- /templates/simple-webpage-nao/app/.metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/simple-webpage-nao/app/.metadata -------------------------------------------------------------------------------- /templates/simple-webpage-nao/app/html/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 3 | } 4 | 5 | body{ 6 | margin: 0px; 7 | background-color: lightblue; 8 | } 9 | 10 | .centered { 11 | position: absolute; 12 | top: 50%; 13 | left: 50%; 14 | margin-right: -50%; 15 | transform: translate(-50%, -50%); 16 | text-align: center; 17 | } 18 | 19 | .bigfriendlybutton { 20 | border-radius: 25px; 21 | display: inline-block; 22 | width: 300px; 23 | height: 100px; 24 | background-color: blue; 25 | color: white; 26 | text-align: center; 27 | font-family: Arial, Helvetica, sans-serif; 28 | font-size: 35px; 29 | font-style: normal; 30 | font-weight: bold; 31 | line-height: 50px; 32 | } 33 | -------------------------------------------------------------------------------- /templates/simple-webpage-nao/app/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /templates/simple-webpage-nao/app/html/js/main.js: -------------------------------------------------------------------------------- 1 | var application = function(){ 2 | RobotUtils.onService(function(ALTextToSpeech) { 3 | // Bind button callbacks 4 | $(".bleeper").click(function() { 5 | ALTextToSpeech.say($(this).html()); 6 | }); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /templates/simple-webpage-nao/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aldebaran/robot-jumpstarter/13c18abb69673dd25d3fc64cf80f65b740bbb2c1/templates/simple-webpage-nao/app/icon.png -------------------------------------------------------------------------------- /templates/simple-webpage-nao/app/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | simple-webpage-nao 5 | servicedriven 6 | 7 | 8 | en_US 9 | 10 | 11 | en_US 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /templates/simple-webpage-nao/app/simple-webpage-nao.pml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /templates/simple-webpage-nao/debug/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

App debug interface!

7 |

Pick the robot to use

8 | Robot IP: 9 |
10 | 11 | 12 |
13 |

The app's page will be displayed as if it was on a tablet connected to that robot

14 | 15 | 16 | 17 | 18 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /templates/simple-webpage-nao/debug/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.4.1 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2006, 2014 Klaus Hartl 6 | * Released under the MIT license 7 | */ 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD (Register as an anonymous module) 11 | define(['jquery'], factory); 12 | } else if (typeof exports === 'object') { 13 | // Node/CommonJS 14 | module.exports = factory(require('jquery')); 15 | } else { 16 | // Browser globals 17 | factory(jQuery); 18 | } 19 | }(function ($) { 20 | 21 | var pluses = /\+/g; 22 | 23 | function encode(s) { 24 | return config.raw ? s : encodeURIComponent(s); 25 | } 26 | 27 | function decode(s) { 28 | return config.raw ? s : decodeURIComponent(s); 29 | } 30 | 31 | function stringifyCookieValue(value) { 32 | return encode(config.json ? JSON.stringify(value) : String(value)); 33 | } 34 | 35 | function parseCookieValue(s) { 36 | if (s.indexOf('"') === 0) { 37 | // This is a quoted cookie as according to RFC2068, unescape... 38 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); 39 | } 40 | 41 | try { 42 | // Replace server-side written pluses with spaces. 43 | // If we can't decode the cookie, ignore it, it's unusable. 44 | // If we can't parse the cookie, ignore it, it's unusable. 45 | s = decodeURIComponent(s.replace(pluses, ' ')); 46 | return config.json ? JSON.parse(s) : s; 47 | } catch(e) {} 48 | } 49 | 50 | function read(s, converter) { 51 | var value = config.raw ? s : parseCookieValue(s); 52 | return $.isFunction(converter) ? converter(value) : value; 53 | } 54 | 55 | var config = $.cookie = function (key, value, options) { 56 | 57 | // Write 58 | 59 | if (arguments.length > 1 && !$.isFunction(value)) { 60 | options = $.extend({}, config.defaults, options); 61 | 62 | if (typeof options.expires === 'number') { 63 | var days = options.expires, t = options.expires = new Date(); 64 | t.setMilliseconds(t.getMilliseconds() + days * 864e+5); 65 | } 66 | 67 | return (document.cookie = [ 68 | encode(key), '=', stringifyCookieValue(value), 69 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 70 | options.path ? '; path=' + options.path : '', 71 | options.domain ? '; domain=' + options.domain : '', 72 | options.secure ? '; secure' : '' 73 | ].join('')); 74 | } 75 | 76 | // Read 77 | 78 | var result = key ? undefined : {}, 79 | // To prevent the for loop in the first place assign an empty array 80 | // in case there are no cookies at all. Also prevents odd result when 81 | // calling $.cookie(). 82 | cookies = document.cookie ? document.cookie.split('; ') : [], 83 | i = 0, 84 | l = cookies.length; 85 | 86 | for (; i < l; i++) { 87 | var parts = cookies[i].split('='), 88 | name = decode(parts.shift()), 89 | cookie = parts.join('='); 90 | 91 | if (key === name) { 92 | // If second argument (value) is a function it's a converter... 93 | result = read(cookie, value); 94 | break; 95 | } 96 | 97 | // Prevent storing a cookie that we couldn't decode. 98 | if (!key && (cookie = read(cookie)) !== undefined) { 99 | result[name] = cookie; 100 | } 101 | } 102 | 103 | return result; 104 | }; 105 | 106 | config.defaults = {}; 107 | 108 | $.removeCookie = function (key, options) { 109 | // Must not alter options, thus extending a fresh object... 110 | $.cookie(key, '', $.extend({}, options, { expires: -1 })); 111 | return !$.cookie(key); 112 | }; 113 | 114 | })); 115 | -------------------------------------------------------------------------------- /templates/simple-webpage-nao/serve.py: -------------------------------------------------------------------------------- 1 | """ 2 | A helper script for testing your app's local html files, with no robot install. 3 | 4 | Usage: 5 | Run serve.py, a browser will open on a debug page, enter your robot's IP address 6 | and your local pages will be served as if they were on the robot. 7 | 8 | Using a server like this (as opposed to just file:// etc.) is only necessary 9 | if you do ajax calls in your page. 10 | """ 11 | 12 | __version__ = "0.0.3" 13 | 14 | __copyright__ = "Copyright 2015, Aldebaran Robotics" 15 | __author__ = 'ekroeger' 16 | __email__ = 'ekroeger@aldebaran.com' 17 | 18 | import os 19 | import threading 20 | import webbrowser 21 | import BaseHTTPServer 22 | import SimpleHTTPServer 23 | import urlparse 24 | import urllib 25 | 26 | USE_SERVER = False 27 | 28 | PORT = 8081 29 | 30 | def open_browser(): 31 | """Start a browser after waiting for half a second.""" 32 | if USE_SERVER: 33 | def _open_browser(): 34 | url = 'http://localhost:%s/debug/index.html' % PORT 35 | webbrowser.open(url) 36 | thread = threading.Timer(0.5, _open_browser) 37 | thread.start() 38 | else: 39 | indexpath = os.path.join(os.getcwd(), "debug/index.html") 40 | url = urlparse.urljoin('file:', urllib.pathname2url(indexpath)) 41 | webbrowser.open(url) 42 | 43 | def start_server(): 44 | """Start the server.""" 45 | if USE_SERVER: 46 | server_address = ("", PORT) 47 | handler_class = SimpleHTTPServer.SimpleHTTPRequestHandler 48 | handler_class.extensions_map['.png'] = 'image/png' 49 | server = BaseHTTPServer.HTTPServer(server_address, handler_class) 50 | server.serve_forever() 51 | 52 | def run(): 53 | open_browser() # Comment out this line if you don't want to open a browser 54 | start_server() 55 | 56 | if __name__ == "__main__": 57 | run() 58 | 59 | --------------------------------------------------------------------------------