'''Web interface for the Raspberry Pi Information Screen. by elParaguayo There are two parts to the web interface: - Web frontend (user-friendly means for customising screens etc) - API (for making changes to screens) Screens are able to provide their own web pages via the frontend. Once the screen is running, use a web browser to open the following URL: http://(IP address of Pi):(web port) ''' from threading import Thread from time import sleep import os import json import imp from kivy.app import App from bottle import Bottle, template, request, TEMPLATE_PATH, redirect import requests from getplugins import getPlugins from webapi import InfoScreenAPI HEADER = '''Raspberry Pi Information Screen<br />''' SCREEN_CONFIG = '''% rebase("base.tpl", title="Configuration Screen: {}".format(screen.capitalize())) <form action="/configure/{{screen}}" method="POST"> <br /> <textarea cols="60" rows="10" name="params" maxlength="2500">{{conf}}</textarea><br /> <br /> <button type="submit">Save Config</button></form>''' class InfoScreenWebServer(Bottle): """This is the web frontend for the Raspberry Pi Information Screen. The default screen lists all screens installed on the system. From there the user is able to customise screens directly. """ def __init__(self, infoscreen, folder, apiport): super(InfoScreenWebServer, self).__init__() # We need access to the infoscreen base object in order to manipulate it self.infoscreen = infoscreen.base # Get the folder path so we can build paths to templates etc. self.folder = folder # Set up the api self.api = "http://localhost:{}/api/".format(apiport) # Set up templates tpls = os.path.join(self.folder, "web", "templates") TEMPLATE_PATH.insert(0, tpls) # Initialise dictionary for custom web pages provided by screens self.custom_screens = {} # Build the dictionary of available screens self.process_plugins() # Define our routes self.route("/configure/<screen>", callback=self.update_config, method="GET") self.route("/configure/<screen>", callback=self.save_config, method="POST") self.route("/view/<screen>", callback=self.view) self.route("/", callback=self.list_screens, method=["GET", "POST"]) # See if there are any custom screens self.add_custom_routes() def process_plugins(self): # Build a dictionary of screens, their current state and whether or not # they provide a custom screen self.screens = {s["name"]: {"web": s["web"], "enabled": s["enabled"]} for s in getPlugins(True)} def add_custom_routes(self): # Get the custom screen dictionary sc = self.screens # Get a list of just those screens who have custom web pages addons = [(x, sc[x]["web"]) for x in sc if sc[x]["web"]] # Loop over the list for screen, addon in addons: # Load the module plugin = imp.load_module("web", *addon) # Loop over the list of web pages... for route in plugin.bindings: # ...and add to the available routes self.route(route[0], callback=getattr(plugin, route[1]), method=route[2]) # We also need to store the default page to make it accessible via # the list of installed screens self.custom_screens[screen] = plugin.bindings[0][0] def valid_screen(self, screen): """Returns True if screen is installed and enabled.""" return (screen is not None and screen in self.infoscreen.availablescreens) def list_screens(self): """Provides a list of all installed screens with various options.""" # Check if the form ws submitted form = request.forms.get("submit", False) # If so... if form: # ...find out what the user wanted to do and to which screen action, screen = form.split("+") # Call the relevant action if action == "view": r = requests.get("{}{}/view".format(self.api, screen)) elif action == "enable": r = requests.get("{}{}/enable".format(self.api, screen)) elif action == "disable": r = requests.get("{}{}/disable".format(self.api, screen)) elif action == "configure": redirect("/configure/{}".format(screen)) elif action == "custom": url = self.custom_screens.get(screen, "/") redirect(url) # Rebuild list of screens self.process_plugins() sc = self.screens # Return the web page return template("all_screens.tpl", screens=sc) def view(self, screen=None): """Method to switch screen.""" r = requests.get("{}{}/view".format(self.api, screen)) return template("all_screens.tpl", screens=self.screens) def update_config(self, screen=None): if screen in self.screens: # Build the path to our config file conffile = os.path.join(self.folder, "screens", screen, "conf.json") # Open the file and load the config with open(conffile, "r") as cfg_file: params = json.load(cfg_file) # We only want the user to edit the "params" section so just # retrieve that part conf = json.dumps(params.get("params", dict()), indent=4) # Build the web page return template(SCREEN_CONFIG, screen=screen, conf=conf) def save_config(self, screen): # Flag to indicate whether params have changed change_params = False # Get the new params from the web form try: params = json.loads(request.forms.get("params")) except ValueError: return "INVALID JSON" else: # Let's check if the params have changed # Build the path to our config file conffile = os.path.join(self.folder, "screens", screen, "conf.json") # Open the file and load the config with open(conffile, "r") as cfg_file: conf = json.load(cfg_file) # Check if the form is the same as the old one if conf.get("params", dict()) != params: # If not, we need to update change_params = True if change_params: # Submit the new params to the API r = requests.post("{}{}/configure".format(self.api, screen), json=params) redirect("/") def start_web(appdir, webport, apiport, debug=False): """Starts the webserver on "webport".""" infoapp = None while infoapp is None: infoapp = App.get_running_app() if getattr(infoapp, "base", None) is None: infoapp = None sleep(1) ws = InfoScreenWebServer(infoapp, appdir, apiport) ws.run(host="0.0.0.0", port=webport, debug=debug) def start_api(appdir, apiport, debug=False): """Starts the API server on "apiport".""" infoapp = None while infoapp is None: infoapp = App.get_running_app() if getattr(infoapp, "base", None) is None: infoapp = None sleep(1) ws = InfoScreenAPI(infoapp, appdir) ws.run(host="0.0.0.0", port=apiport, debug=debug) def start_web_server(appdir, webport=8088, apiport=8089, debug=False): # Create the webserver in a new thread t = Thread(target=start_web, args=(appdir, webport, apiport, debug)) # Daemonise it t.daemon = True # Go! t.start() # Create the API server in a new thread api = Thread(target=start_api, args=(appdir, apiport)) # Daemonise it api.daemon = True # Go! api.start()