1#!/usr/bin/env python 2 3import os 4import json 5import logging 6import argparse 7import socket 8from threading import Timer 9import subprocess 10 11from bottle import Bottle, run, template, static_file, request, response, BaseRequest, default_app 12from .indi_server import IndiServer, INDI_PORT, INDI_FIFO, INDI_CONFIG_DIR 13from .driver import DeviceDriver, DriverCollection, INDI_DATA_DIR 14from .database import Database 15from .device import Device 16from .indihub_agent import IndiHubAgent 17 18# default settings 19WEB_HOST = '0.0.0.0' 20WEB_PORT = 8624 21 22# Make it 10MB 23BaseRequest.MEMFILE_MAX = 50 * 1024 * 1024 24 25pkg_path, _ = os.path.split(os.path.abspath(__file__)) 26views_path = os.path.join(pkg_path, 'views') 27 28parser = argparse.ArgumentParser( 29 description='INDI Web Manager. ' 30 'A simple web application to manage an INDI server') 31 32parser.add_argument('--indi-port', '-p', type=int, default=INDI_PORT, 33 help='indiserver port (default: %d)' % INDI_PORT) 34parser.add_argument('--port', '-P', type=int, default=WEB_PORT, 35 help='Web server port (default: %d)' % WEB_PORT) 36parser.add_argument('--host', '-H', default=WEB_HOST, 37 help='Bind web server to this interface (default: %s)' % 38 WEB_HOST) 39parser.add_argument('--fifo', '-f', default=INDI_FIFO, 40 help='indiserver FIFO path (default: %s)' % INDI_FIFO) 41parser.add_argument('--conf', '-c', default=INDI_CONFIG_DIR, 42 help='INDI config. directory (default: %s)' % INDI_CONFIG_DIR) 43parser.add_argument('--xmldir', '-x', default=INDI_DATA_DIR, 44 help='INDI XML directory (default: %s)' % INDI_DATA_DIR) 45parser.add_argument('--verbose', '-v', action='store_true', 46 help='Print more messages') 47parser.add_argument('--logfile', '-l', help='log file name') 48parser.add_argument('--server', '-s', default='standalone', 49 help='HTTP server [standalone|apache] (default: standalone') 50 51args = parser.parse_args() 52 53 54logging_level = logging.WARNING 55 56if args.verbose: 57 logging_level = logging.DEBUG 58 59if args.logfile: 60 logging.basicConfig(filename=args.logfile, 61 format='%(asctime)s - %(levelname)s: %(message)s', 62 level=logging_level) 63 64else: 65 logging.basicConfig(format='%(asctime)s - %(levelname)s: %(message)s', 66 level=logging_level) 67 68logging.debug("command line arguments: " + str(vars(args))) 69 70hostname = socket.gethostname() 71 72collection = DriverCollection(args.xmldir) 73indi_server = IndiServer(args.fifo, args.conf) 74indi_device = Device() 75 76indihub_agent = IndiHubAgent('%s:%d' % (args.host, args.port), hostname, args.port) 77 78db_path = os.path.join(args.conf, 'profiles.db') 79db = Database(db_path) 80 81collection.parse_custom_drivers(db.get_custom_drivers()) 82 83if args.server == 'standalone': 84 app = Bottle() 85 logging.info('using Bottle as standalone server') 86else: 87 app = default_app() 88 logging.info('using Apache web server') 89 90saved_profile = None 91active_profile = "" 92 93 94def start_profile(profile): 95 info = db.get_profile(profile) 96 97 profile_drivers = db.get_profile_drivers_labels(profile) 98 all_drivers = [collection.by_label(d['label']) for d in profile_drivers] 99 100 # Find if we have any remote drivers 101 remote_drivers = db.get_profile_remote_drivers(profile) 102 if remote_drivers: 103 drivers = remote_drivers['drivers'].split(',') 104 for drv in drivers: 105 all_drivers.append(DeviceDriver(drv, drv, "1.0", drv, "Remote")) 106 107 if all_drivers: 108 indi_server.start(info['port'], all_drivers) 109 # Auto connect drivers in 3 seconds if required. 110 if info['autoconnect'] == 1: 111 t = Timer(3, indi_server.auto_connect) 112 t.start() 113 114 115@app.route('/static/<path:path>') 116def callback(path): 117 """Serve static files""" 118 return static_file(path, root=views_path) 119 120 121@app.route('/favicon.ico', method='GET') 122def get_favicon(): 123 """Serve favicon""" 124 return static_file('favicon.ico', root=views_path) 125 126 127@app.route('/') 128def main_form(): 129 """Main page""" 130 global saved_profile 131 drivers = collection.get_families() 132 133 if not saved_profile: 134 saved_profile = request.get_cookie('indiserver_profile') or 'Simulators' 135 136 profiles = db.get_profiles() 137 return template(os.path.join(views_path, 'form.tpl'), profiles=profiles, 138 drivers=drivers, saved_profile=saved_profile, 139 hostname=hostname) 140 141############################################################################### 142# Profile endpoints 143############################################################################### 144 145 146@app.get('/api/profiles') 147def get_json_profiles(): 148 """Get all profiles (JSON)""" 149 results = db.get_profiles() 150 return json.dumps(results) 151 152 153@app.get('/api/profiles/<item>') 154def get_json_profile(item): 155 """Get one profile info""" 156 results = db.get_profile(item) 157 return json.dumps(results) 158 159 160@app.post('/api/profiles/<name>') 161def add_profile(name): 162 """Add new profile""" 163 db.add_profile(name) 164 165 166@app.delete('/api/profiles/<name>') 167def delete_profile(name): 168 """Delete Profile""" 169 db.delete_profile(name) 170 171 172@app.put('/api/profiles/<name>') 173def update_profile(name): 174 """Update profile info (port & autostart & autoconnect)""" 175 response.set_cookie("indiserver_profile", name, 176 None, max_age=3600000, path='/') 177 data = request.json 178 port = data.get('port', args.indi_port) 179 autostart = bool(data.get('autostart', 0)) 180 autoconnect = bool(data.get('autoconnect', 0)) 181 db.update_profile(name, port, autostart, autoconnect) 182 183 184@app.post('/api/profiles/<name>/drivers') 185def save_profile_drivers(name): 186 """Add drivers to existing profile""" 187 data = request.json 188 db.save_profile_drivers(name, data) 189 190 191@app.post('/api/profiles/custom') 192def save_profile_custom_driver(): 193 """Add custom driver to existing profile""" 194 data = request.json 195 db.save_profile_custom_driver(data) 196 collection.clear_custom_drivers() 197 collection.parse_custom_drivers(db.get_custom_drivers()) 198 199 200@app.get('/api/profiles/<item>/labels') 201def get_json_profile_labels(item): 202 """Get driver labels of specific profile""" 203 results = db.get_profile_drivers_labels(item) 204 return json.dumps(results) 205 206 207@app.get('/api/profiles/<item>/remote') 208def get_remote_drivers(item): 209 """Get remote drivers of specific profile""" 210 results = db.get_profile_remote_drivers(item) 211 if results is None: 212 results = {} 213 return json.dumps(results) 214 215 216############################################################################### 217# Server endpoints 218############################################################################### 219 220@app.get('/api/server/status') 221def get_server_status(): 222 """Server status""" 223 status = [{'status': str(indi_server.is_running()), 'active_profile': active_profile}] 224 return json.dumps(status) 225 226 227@app.get('/api/server/drivers') 228def get_server_drivers(): 229 """List server drivers""" 230 # status = [] 231 # for driver in indi_server.get_running_drivers(): 232 # status.append({'driver': driver}) 233 # return json.dumps(status) 234 # labels = [] 235 # for label in sorted(indi_server.get_running_drivers().keys()): 236 # labels.append({'driver': label}) 237 # return json.dumps(labels) 238 drivers = [] 239 if indi_server.is_running() is True: 240 for driver in indi_server.get_running_drivers().values(): 241 drivers.append(driver.__dict__) 242 return json.dumps(drivers) 243 244 245@app.post('/api/server/start/<profile>') 246def start_server(profile): 247 """Start INDI server for a specific profile""" 248 global saved_profile 249 saved_profile = profile 250 global active_profile 251 active_profile = profile 252 response.set_cookie("indiserver_profile", profile, 253 None, max_age=3600000, path='/') 254 start_profile(profile) 255 256 257@app.post('/api/server/stop') 258def stop_server(): 259 """Stop INDI Server""" 260 indihub_agent.stop() 261 indi_server.stop() 262 263 global active_profile 264 active_profile = "" 265 266 # If there is saved_profile already let's try to reset it 267 global saved_profile 268 if saved_profile: 269 saved_profile = request.get_cookie("indiserver_profile") or "Simulators" 270 271 272############################################################################### 273# Driver endpoints 274############################################################################### 275 276@app.get('/api/drivers/groups') 277def get_json_groups(): 278 """Get all driver families (JSON)""" 279 response.content_type = 'application/json' 280 families = collection.get_families() 281 return json.dumps(sorted(families.keys())) 282 283 284@app.get('/api/drivers') 285def get_json_drivers(): 286 """Get all drivers (JSON)""" 287 response.content_type = 'application/json' 288 return json.dumps([ob.__dict__ for ob in collection.drivers]) 289 290 291@app.post('/api/drivers/start/<label>') 292def start_driver(label): 293 """Start INDI driver""" 294 driver = collection.by_label(label) 295 indi_server.start_driver(driver) 296 logging.info('Driver "%s" started.' % label) 297 298 299@app.post('/api/drivers/stop/<label>') 300def stop_driver(label): 301 """Stop INDI driver""" 302 driver = collection.by_label(label) 303 indi_server.stop_driver(driver) 304 logging.info('Driver "%s" stopped.' % label) 305 306 307@app.post('/api/drivers/restart/<label>') 308def restart_driver(label): 309 """Restart INDI driver""" 310 driver = collection.by_label(label) 311 indi_server.stop_driver(driver) 312 indi_server.start_driver(driver) 313 logging.info('Driver "%s" restarted.' % label) 314 315############################################################################### 316# Device endpoints 317############################################################################### 318 319 320@app.get('/api/devices') 321def get_devices(): 322 return json.dumps(indi_device.get_devices()) 323 324############################################################################### 325# System control endpoints 326############################################################################### 327 328 329@app.post('/api/system/reboot') 330def system_reboot(): 331 """reboot the system running indi-web""" 332 logging.info('System reboot, stopping server...') 333 stop_server() 334 logging.info('rebooting...') 335 subprocess.call('reboot') 336 337 338@app.post('/api/system/poweroff') 339def system_poweroff(): 340 """poweroff the system""" 341 logging.info('System poweroff, stopping server...') 342 stop_server() 343 logging.info('poweroff...') 344 subprocess.run("poweroff") 345 346############################################################################### 347# INDIHUB Agent control endpoints 348############################################################################### 349 350 351@app.get('/api/indihub/status') 352def get_indihub_status(): 353 """INDIHUB Agent status""" 354 mode = indihub_agent.get_mode() 355 is_running = indihub_agent.is_running() 356 response.content_type = 'application/json' 357 status = [{'status': str(is_running), 'mode': mode, 'active_profile': active_profile}] 358 return json.dumps(status) 359 360 361@app.post('/api/indihub/mode/<mode>') 362def change_indihub_agent_mode(mode): 363 """Change INDIHUB Agent mode with a current INDI-profile""" 364 365 if active_profile == "" or not indi_server.is_running(): 366 response.content_type = 'application/json' 367 response.status = 500 368 return json.dumps({'message': 'INDI-server is not running. You need to run INDI-server first.'}) 369 370 indihub_agent.stop() 371 372 if mode == 'off': 373 return 374 375 indihub_agent.start(active_profile, mode) 376 377 378############################################################################### 379# Startup standalone server 380############################################################################### 381 382 383def main(): 384 """Start autostart profile if any""" 385 global active_profile 386 387 for profile in db.get_profiles(): 388 if profile['autostart']: 389 start_profile(profile['name']) 390 active_profile = profile['name'] 391 break 392 393 run(app, host=args.host, port=args.port, quiet=args.verbose) 394 logging.info("Exiting") 395 396 397# JM 2018-12-24: Added __main__ so I can debug this as a module in PyCharm 398# Otherwise, I couldn't get it to run main as all 399if __name__ == '__init__' or __name__ == '__main__': 400 main() 401