Skip to content
Snippets Groups Projects
server.py 5.37 KiB
import argparse
import os, sys

import tornado.ioloop
import tornado.web
import tornado.httpserver
import tornado.websocket
from tornado.ioloop import PeriodicCallback
import json

from wlm import WavelengthMeter

# all connected browsers will be here
clients = []

def send_data():
    """Gets wavelengths from the wavemeter and sends it to the client"""
    if len(clients)>0:
        data = wlmeter.wavelengths
        str = json.dumps(data)
        for c in clients:
            c.write_message(str)

class WsHandler(tornado.websocket.WebSocketHandler):
    """Websocket handler"""
    def open(self):
        """Subscribes to the updates by adding itself to the clients list"""
        clients.append(self)

    def on_close(self):
        """Removes itself from clients list"""
        clients.remove(self)
        print('connection closed')

    def check_origin(self, origin):
        """Allows cross origin connection if you want to embed wlm.js library in some page on another domain"""
        return True

class ApiHandler(tornado.web.RequestHandler):
    """Creates simple HTTP API if you don't like websockets"""
    def get(self, channel=None):
        w = wlmeter.wavelengths
        sw = wlmeter.switcher_mode
        if channel is None:
            self.write({ "wavelengths": w, "switcher_mode": sw })
        else:
            ch = int(channel)
            if ch >=0 and ch<len(w):
                self.write("%.8f" % w[ch])
            else:
                self.set_status(400)
                self.write({"error":"Wrong channel"})

class IndexHandler(tornado.web.RequestHandler):
    """Renders index.html page"""
    def get(self):
        self.render("index.html",
            wavelengths=wlmeter.wavelengths,
            **get_config()
        )


default_config_file = os.path.abspath(os.path.join(os.path.dirname(__file__), "config.json"))

def make_app(config):
    """All the routes are defined here"""
    static_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "static"))
    return tornado.web.Application([
            (r"%s/" % config["root"], IndexHandler),
            (r"%s/api/" % config["root"], ApiHandler),
            (r"%s/api/(\d)/" % config["root"], ApiHandler),
            (r"%s/ws/" % config["root"], WsHandler),
            (r"%s/static/(.*)" % config["root"], tornado.web.StaticFileHandler, {'path': static_path}),
    ], debug=True)

class config_action(argparse.Action):
    """Parses config file argument"""
    def __call__(self, parser, namespace, values, option_string=None):
        config_file = values
        if not os.path.isfile(config_file):
            raise argparse.ArgumentTypeError("config:{0} is not a valid file".format(config_file))
        if os.access(config_file, os.R_OK):
            setattr(namespace, self.dest, config_file)
        else:
            raise argparse.ArgumentTypeError("config:{0} is not a readable file".format(config_file))

def get_config():
    """Building configuration dictionary"""

    # command line arguments parsing
    parser = argparse.ArgumentParser(description='Starts a webserver with wavemeter interface.')
    parser.add_argument('--debug', dest='debug', action='store_const',
                        const=True,
                        help='runs the script in debug mode simulating wavelength values')
    parser.add_argument('-c', '--config', action=config_action, default=default_config_file,
                        help='path to config json file, default: config.json in the script folder')
    parser.add_argument('-r', '--root', default=None,
                        help='path where the interface will be, like localhost:8000/root/. Default is "/"')
    parser.add_argument('port', type=int, nargs='?',
                        help='server port, default: 8000')

    args = parser.parse_args()

    # default configuration
    config = {
        "port": 8000, # port
        "root": "/", # path
        "precision": 5, # number of decimals in wavelength display
        "update_rate": 0.1, # how often updates will be sent to the browsers
        "debug": False, # do you want to work with real wavemeter or to test run it?
        "channels": [{"i": i, "label": "Channel %d" % (i+1)} for i in range(8)] # channels to display
    }

    # configuration from the file
    with open(args.config, "r") as f:
        config.update(json.loads(f.read()))

    # configuration from command line
    config["port"] = (args.port or config["port"])
    config["root"] = (args.root or config["root"])
    config["debug"] = (args.debug or config["debug"])

    # add leading slash
    if len(config["root"]) > 0 and config["root"][0] != "/":
        config["root"] = "/"+config["root"]

    # remove trailing slash
    if config["root"][-1] == "/":
        config["root"] = config["root"][:-1]

    return config

if __name__ == "__main__":

    config = get_config()

    wlmeter = WavelengthMeter(debug=config["debug"])

    app = make_app(config)

    if "ssl" in config:
        # https and wss server
        server = tornado.httpserver.HTTPServer(app, xheaders=True, ssl_options=config["ssl"])
    else:
        # http and ws server
        server = tornado.httpserver.HTTPServer(app)

    server.listen(config["port"])
    print("Server started at http://localhost:%d%s/" % (config["port"], config["root"]))

    # periodic callback takes update rate in ms
    PeriodicCallback(send_data, config["update_rate"]*1000).start()

    tornado.ioloop.IOLoop.instance().start()