#!/usr/bin/env python3
# Author: Sébastien Combéfis
# Author: Tom Selleslagh
# Version: June 13, 2016

import asyncio
import importlib
import inspect
import json
import os
import queue
import sched
import threading
import time

from bottle import *
import websockets

# Global configuration variables
HOST = '0.0.0.0'
WEBSERVER_PORT = int(os.environ.get('PORT', 5000))
WEBSOCKET_PORT = 8080


# Refresh a widget
def refresh(label, widget, refreshrate):
    if websocket:
        queue.put((label, widget()))
    sched.enter(refreshrate, 1, refresh, (label, widget, refreshrate))

# Load a list of modules
def loadmodules(modules):
    assets = {'js': set(), 'css': set()}
    components = {}
    for module in modules:
        path = 'modules/{}'.format(module['name'])
        if os.path.isdir(path):
            # Load CSS and JS files
            for entry in os.listdir(path):
                (name, ext) = os.path.splitext(entry)
                if ext == '.css':
                    assets['css'].add('{}/{}'.format(path, entry))
                elif ext == '.js':
                    assets['js'].add('{}/{}'.format(path, entry))
            # Load widgets and schedule automatic refresh
            mod = importlib.import_module('modules.{0}.{0}'.format(module['name']))
            for name, obj in inspect.getmembers(mod):
                if inspect.isclass(obj) and name != 'Module':
                    obj = obj(module['config']) if 'config' in module else obj()
                    widget = obj.widget()
                    components[module['label']] = {'object': obj, 'widget': widget}
                    if obj.refreshrate is not None:
                        sched.enter(obj.refreshrate, 1, refresh, (module['label'], widget, obj.refreshrate))
    return (assets, components)

# Initialise all the modules of the application
# and launch their automatic scheduled update
sched = sched.scheduler(time.time, time.sleep)
assets, components = loadmodules([
    {'label' : 'datetime', 'name': 'datetime'},
    {'label' : 'weather', 'name': 'weather'},
    {'label' : 'transport', 'name': 'transport'},
    {'label' : 'leftfiller', 'name': 'filler', 'config': {'height': 100}},
    {'label' : 'warning', 'name': 'warning'},
    {'label' : 'info', 'name': 'info'},
    {'label' : 'logo', 'name': 'logo', 'config': {'src': 'images/ecam-logo.png', 'alt': 'Logo ECAM'}},
    {'label' : 'rightfiller', 'name': 'filler', 'config': {'height': 600}}
])
threading.Thread(target=lambda: sched.run()).start()

# Initialise the websocket server endpoint
async def handle_webserver(ws, path):
    global websocket
    if path == '/websocket' and not websocket:
        websocket = True
        while True:
            label, content = queue.get()
            await ws.send(json.dumps({'label': label, 'content': content}))

websocket = False
queue = queue.Queue()
server = websockets.serve(handle_webserver, HOST, WEBSOCKET_PORT)
eventloop = asyncio.get_event_loop()
eventloop.run_until_complete(server)
threading.Thread(target=lambda: eventloop.run_forever()).start()

# Generic route for static files
@route('/<rep:re:(css|js|images|files)>/<filename>')
def get_static_file(rep, filename):
    return static_file(filename, root='./static/' + rep)

# Generic route for files from modules
@route('/modules/<name>/<file>')
def get_module_asset(name, file):
    return static_file(file, root='./modules/{}'.format(name))

# Main page
@route('/')
def main():
    widgets = {}
    for label in components:
        widgets[label] = components[label]['widget']
    return template('index.html', assets=assets, widgets=widgets)

# Launch the server
run(host=HOST, port=WEBSERVER_PORT, debug=True)
@@ -453,25 +453,32 @@
 %0A    if 
-websocket
+len(clients) %3E 0
 :%0A      
@@ -2559,122 +2559,187 @@
 int%0A
-async def handle_webserver(ws, path):%0A    global websocket%0A    if path == '/websocket' and not websocket:%0A        
+queue = queue.Queue()%0Aclients = set()%0A%0Adef launch_server():%0A    async def handle_websocket(websocket, path):%0A        print('=== Connexion received for', path)%0A        clients.add(
 webs
@@ -2747,15 +2747,9 @@
 cket
- = True
+)
 %0A   
@@ -2822,22 +2822,52 @@
     
-await ws.send(
+print('Update of', label)%0A            msg = 
 json
@@ -2914,51 +2914,105 @@
 nt%7D)
-)%0A%0Awebsocket = False%0Aqueue = queue.Queue()%0A
+%0A            await asyncio.wait(%5Bws.send(msg) for ws in clients%5D)%0A            print('SENT ')%0A    
 serv
@@ -3044,21 +3044,21 @@
 dle_webs
-erver
+ocket
 , HOST, 
@@ -3073,21 +3073,20 @@
 T_PORT)%0A
-event
+    
 loop = a
@@ -3092,19 +3092,19 @@
 asyncio.
-get
+new
 _event_l
@@ -3109,21 +3109,53 @@
 _loop()%0A
-event
+    asyncio.set_event_loop(loop)%0A    
 loop.run
@@ -3178,16 +3178,39 @@
 server)%0A
+    loop.run_forever()%0A
 threadin
@@ -3231,37 +3231,19 @@
 t=la
-mbda: eventloop.run_fore
+unch_ser
 ver
-()
 ).st

Add support for multiple WebSocket connexions