├── README.md ├── config.sh ├── functions.sh ├── hooks ├── entering_cmd ├── exiting_cmd ├── program_exit ├── program_startup ├── start_listening ├── start_speaking ├── stop_listening └── stop_speaking ├── install.sh ├── server.py ├── uninstall.sh └── update.sh /README.md: -------------------------------------------------------------------------------- 1 | 12 | ## Description 13 | This plugin embeds a lightweight RestAPI server to control Jarvis remotely. 14 | The HTTP Server is based on Python, so no dependency to install. 15 | There is no configuration needed, just install the plugin and start Jarvis normally. 16 | 17 | ## Usage 18 | ``` 19 | $> jarvis 20 | Starting RestAPI server on http://192.168.1.20:8080 21 | Jarvis: Bonjour Alex 22 | ... 23 | ``` 24 | Now you can control Jarvis from another device. 25 | 26 | From a browser, simply type in the URL to make Jarvis say something: 27 | ``` 28 | http://192.168.1.20:8080?say=Hello World 29 | ``` 30 | ![image](https://cloud.githubusercontent.com/assets/11017174/25438915/26d18d8a-2a9b-11e7-98b5-37e9b86ecfc8.png) 31 | 32 | In the above example, Jarvis will say "Hello World" out loud. 33 | 34 | From a terminal, you can use `curl`: 35 | ``` 36 | $> curl "http://192.168.1.20:8080?say=Hello%20World" 37 | [{"answer":"Hello World"}] 38 | ``` 39 | To have `curl` encoding the url for you, use two arguments: 40 | * `-G` to send data with GET method 41 | * `--data-urlencode` to encode special characters such as spaces in parameters 42 | ``` 43 | $> curl -G "http://192.168.1.20:8080" --data-urlencode "say=Hello World" 44 | [{"answer":"Hello World"}] 45 | ``` 46 | 47 | You can make Jarvis listening directly for a command by simulating a hotword recognition: 48 | ``` 49 | $> curl "http://192.168.1.20:8080?action=listen" 50 | [{"Success":"Ok"}] 51 | ``` 52 | 53 | You can also send orders to Jarvis: 54 | ``` 55 | $> curl -G "http://192.168.1.20:8080" --data-urlencode "order=bonjour Jarvis" 56 | [{"answer":"Bonjour Alex"}] 57 | ``` 58 | To prevent the remote Jarvis from speaking, use the mute option: 59 | ``` 60 | $> curl -G "http://192.168.1.20:8080" --data-urlencode "order=météo" --data-urlencode "mute=true" 61 | [{"answer":"je regarde..."},{"answer":"Ciel plutôt dégagé. Minimales : 4 degrés."}] 62 | ``` 63 | You can easily extract the answer from the `JSON`, ex using `jq`: 64 | ``` 65 | $> curl "http://192.168.1.20:8080?order=meteo&mute=true" | jq -r '.[1].Jarvis' 66 | Ciel plutôt dégagé. Minimales : 3 degrés. 67 | ``` 68 | If you have defined an API Key for security, you have to pass it like this: 69 | ``` 70 | $> curl -G "http://192.168.1.20:8080" --data-urlencode "say=I am secured" --data-urlencode "key=12345" 71 | [{"answer":"I am secured"}] 72 | ``` 73 | To retrieve the user commands: 74 | ``` 75 | $> curl "http://192.168.1.20:8080?action=get_commands" 76 | *MERCI*==say "De rien" 77 | *AIDE*==jv_display_commands 78 | *COMMENT*APPELLE*==say "Je m'appelle $trigger" 79 | [...] 80 | ``` 81 | To replace by a new set of commands (replace all): 82 | ``` 83 | $> curl -s -d '{"action":"set_commands","commands":"*MERCI*==say \"De rien\"\n*AIDE*==..."}' 84 | {"status":"ok"} 85 | ``` 86 | To retrieve the user events: 87 | ``` 88 | $> curl "http://192.168.1.20:8080?action=get_events" 89 | 2 9 * * 6,0 ~/jarvis/jarvis.sh -x "meteo" 90 | 0 7-20 * * 1-5 ~/jarvis/jarvis.sh -x "quelle heure est-il" 91 | [...] 92 | ``` 93 | To replace by a new set of events (replace all): 94 | ``` 95 | $> curl -s -d '{"action":"set_commands","commands":"2 9 * * 6,0..."}' 96 | {"status":"ok"} 97 | ``` 98 | To retrieve the user settings: 99 | ``` 100 | $> curl "http://192.168.1.20:8080?action=get_config" 101 | {"username": "Alex", "version": "16.11.20", [...] } 102 | ``` 103 | To change settings (replace all): 104 | ``` 105 | $> curl -s -d '{"action":"set_config","config":"{\"username\":\"Alexylem\", [...]}"}' http://192.168.1.20:8080 106 | {"status":"ok"} 107 | ``` 108 | 109 | ## Author 110 | [Alex](https://github.com/alexylem) 111 | -------------------------------------------------------------------------------- /config.sh: -------------------------------------------------------------------------------- 1 | jv_pg_api_port=8080 # port for Jarvis RestAPI Server 2 | jv_pg_api_key="" # optional secret key for additional security, "" to disable 3 | -------------------------------------------------------------------------------- /functions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Here you can create functions which will be available from the commands file 3 | # You can also use here user variables defined in your config file 4 | # To avoid conflicts, name your function like this 5 | # jv_pg_XX_myfunction () { } 6 | # jv for JarVis 7 | # pg for PluGin 8 | # XX can be a two letters code for your plugin, ex: ww for Weather Wunderground 9 | 10 | jv_pg_api_start () { 11 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 12 | jv_debug "Starting RestAPI server on http://$jv_ip:$jv_pg_api_port" 13 | python2 $DIR/server.py --port $jv_pg_api_port --key "$jv_pg_api_key" & # 2>&1 | jv_add_timestamps >>$jv_dir/jarvis.log & 14 | } 15 | -------------------------------------------------------------------------------- /hooks/entering_cmd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexylem/jarvis-api/ee7d0139b87846130d3f7833db89403b9f0086df/hooks/entering_cmd -------------------------------------------------------------------------------- /hooks/exiting_cmd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexylem/jarvis-api/ee7d0139b87846130d3f7833db89403b9f0086df/hooks/exiting_cmd -------------------------------------------------------------------------------- /hooks/program_exit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexylem/jarvis-api/ee7d0139b87846130d3f7833db89403b9f0086df/hooks/program_exit -------------------------------------------------------------------------------- /hooks/program_startup: -------------------------------------------------------------------------------- 1 | jv_pg_api_start 2 | -------------------------------------------------------------------------------- /hooks/start_listening: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexylem/jarvis-api/ee7d0139b87846130d3f7833db89403b9f0086df/hooks/start_listening -------------------------------------------------------------------------------- /hooks/start_speaking: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexylem/jarvis-api/ee7d0139b87846130d3f7833db89403b9f0086df/hooks/start_speaking -------------------------------------------------------------------------------- /hooks/stop_listening: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexylem/jarvis-api/ee7d0139b87846130d3f7833db89403b9f0086df/hooks/stop_listening -------------------------------------------------------------------------------- /hooks/stop_speaking: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexylem/jarvis-api/ee7d0139b87846130d3f7833db89403b9f0086df/hooks/stop_speaking -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Use only if you need to perform changes on the user system such as installing software 3 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 3 | import argparse # manage program arguments 4 | import json # read input json 5 | import os # build path 6 | import socket # port in use exception 7 | import sys # exit (1) 8 | #import ssl # https 9 | import signal # catch kill 10 | import urlparse # parse url parameters 11 | from subprocess import check_output, call # run shell commands 12 | 13 | class Jarvis(): 14 | def __init__(self): 15 | #self.path = os.path.dirname(os.path.realpath(__file__)) 16 | self.program = ["jarvis", "-j"] 17 | self.mute_mode = False 18 | self.verbose = False 19 | 20 | def _exec (self, args): 21 | flags = [] 22 | if self.mute_mode: 23 | flags.append ("-m") 24 | if self.verbose: 25 | flags.append ("-v") 26 | return check_output(self.program + flags + args) 27 | 28 | def get_config (self): 29 | config={} 30 | for config_filename in os.listdir ('config'): 31 | with open (os.path.join ('config', config_filename)) as config_file: 32 | config[config_filename]=config_file.read ().rstrip () 33 | return config 34 | 35 | def set_config (self, config): 36 | for config_filename in os.listdir ('config'): 37 | with open (os.path.join ('config', config_filename), 'w') as config_file: 38 | value=config[config_filename] 39 | if isinstance (value, bool): 40 | value="true" if value else "false" # to string would give "True" 41 | config_file.write (value.encode('utf-8')+'\n') 42 | 43 | def say (self, phrase): 44 | return json.loads(self._exec (["-s", phrase]), strict=False) 45 | 46 | def handle_order (self, order): 47 | return json.loads(self._exec (["-x", order]), strict=False) 48 | 49 | def listen (self): 50 | return json.loads(self._exec (["-l"])) 51 | 52 | def get_commands (self): 53 | with open('jarvis-commands') as the_file: 54 | return { 'commands' : the_file.read() } 55 | 56 | def set_commands (sef, commands): 57 | commands=commands.rstrip()+'\n' # add new line end of file if missing 58 | with open ('jarvis-commands', 'w') as the_file: 59 | the_file.write (commands.encode('utf-8')) 60 | 61 | def get_events (self): 62 | with open('jarvis-events') as the_file: 63 | return { 'events' : the_file.read() } 64 | 65 | def set_events (sef, events): 66 | events=events.rstrip()+'\n' # add new line end of file if missing 67 | with open ('jarvis-events', 'w') as the_file: 68 | the_file.write (events.encode('utf-8')) 69 | call(['crontab', 'jarvis-events', '-i']) 70 | 71 | def proper_exit (signum, frame): 72 | print 'Stopping HTTP server' 73 | http_server.server_close() 74 | sys.exit(0) 75 | 76 | def handle_request (self, data): 77 | # check api key if defined by user 78 | if args.key: 79 | if not ("key" in data): 80 | raise ValueError ("Missing API Key") 81 | elif (data["key"] == ""): 82 | raise ValueError ("Empty API Key") 83 | elif (data["key"] != args.key): 84 | raise ValueError ("Invalid API Key") 85 | 86 | if "mute" in data: 87 | mute=data["mute"] 88 | jarvis.mute_mode=mute if isinstance(mute, bool) else (mute.lower() != "false") 89 | if "verbose" in data: 90 | verbose=data["verbose"] 91 | jarvis.verbose=verbose if isinstance(verbose, bool) else (verbose.lower() != "false") 92 | 93 | response={"status":"ok"} 94 | if "action" in data: 95 | action = data ["action"] 96 | if action == "listen": 97 | response=jarvis.listen () 98 | elif action == "get_commands": 99 | response=jarvis.get_commands () 100 | elif action == "set_commands": 101 | if "commands" in data: 102 | jarvis.set_commands (data ["commands"]) 103 | else: 104 | raise ValueError ("Missing commands parameter") 105 | elif action == "get_events": 106 | response=jarvis.get_events () 107 | elif action == "set_events": 108 | if "events" in data: 109 | jarvis.set_events (data ["events"]) 110 | else: 111 | raise ValueError ("Missing events parameter") 112 | elif action == "get_config": 113 | response=jarvis.get_config () 114 | elif action == "set_config": 115 | jarvis.set_config (data ["config"]) 116 | else: 117 | raise ValueError ("Unsupported action: "+action) 118 | elif "order" in data: 119 | response=jarvis.handle_order (data ["order"]) 120 | elif "say" in data: 121 | response=jarvis.say (data ["say"]) 122 | else: 123 | raise ValueError ("Don't know what to do with: "+ json.dumps (data)) 124 | self.send_response(200) 125 | self.send_header("Access-Control-Allow-Origin", "*") 126 | self.send_header("Content-type", "application/json") 127 | self.end_headers() 128 | self.wfile.write(json.dumps (response)) 129 | 130 | class RESTRequestHandler(BaseHTTPRequestHandler): 131 | def do_HEAD(self): 132 | self._set_headers() 133 | 134 | def do_GET(self): 135 | url = urlparse.urlparse(self.path) 136 | data = dict(urlparse.parse_qsl(url.query)) 137 | try: 138 | handle_request (self, data) 139 | except Exception as e: 140 | self.send_response(400) 141 | self.send_header("Access-Control-Allow-Origin", "*") 142 | self.send_header("Content-type", "application/json") 143 | self.end_headers() 144 | print "ERROR:", e 145 | self.wfile.write(json.dumps ({"error":str(e)})) 146 | pass 147 | 148 | def do_POST(self): 149 | content_length = int(self.headers['Content-Length']) # <--- Gets the size of data 150 | post_data = self.rfile.read(content_length) # <--- Gets the data itself 151 | try: 152 | data = json.loads(post_data) 153 | handle_request (self, data) 154 | except Exception as e: 155 | self.send_response(400) 156 | self.send_header("Access-Control-Allow-Origin", "*") 157 | self.send_header("Content-type", "application/json") 158 | self.end_headers() 159 | print "ERROR:", e 160 | self.wfile.write(json.dumps ({"error":str(e)})) 161 | pass 162 | 163 | if __name__ == "__main__": 164 | parser = argparse.ArgumentParser(description='Jarvis HTTP RestAPI Server') 165 | parser.add_argument('-k', '--key', help='Optional secret key') 166 | parser.add_argument('-p', '--port', help='Listening port (default: 8080)', type=int, default=8080) 167 | #parser.add_argument('-s', '--ssl', help='Use SSL', action='store_true') 168 | args = parser.parse_args() 169 | 170 | jarvis = Jarvis () 171 | try: 172 | http_server = HTTPServer(('', args.port), RESTRequestHandler) 173 | #if args.ssl: 174 | # http_server.socket = ssl.wrap_socket (http_server.socket, certfile='./server.pem', server_side=True) 175 | for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGQUIT]: 176 | signal.signal(sig, proper_exit) 177 | http_server.serve_forever() 178 | except socket.error, msg: 179 | print 'ERROR: ', msg 180 | sys.exit(1) 181 | except KeyboardInterrupt: 182 | print # new line 183 | pass 184 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Use only if you need to undo changes on the user system such as removing software 3 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file will be automatically sourced each time your plugin is updated 3 | # Use only if you need to perform updates on the user systems to support evolution of your plugin 4 | --------------------------------------------------------------------------------