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()