forked from microsoft/Qcodes
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* First monitor commit, now 🍔 ! * polish things up * types for the masses * chore: Refactor monitor * fix: Add todo remove space * refactor * silm api * better monitor * fix: Add static files to pacakge setup * fix: Add websocket dependency * fix: Use http with webbrowser open
- Loading branch information
1 parent
98e3b0a
commit 36836b4
Showing
8 changed files
with
222 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="x-ua-compatible" content="ie=edge"><title>QCoDeS montior</title><link rel="shortcut icon" href="/favicon.ico"><link href="/css/main.3cd7fa7f.css" rel="stylesheet"></head><body><div id="root"></div><script type="text/javascript" src="/js/main.6641e1a4.js"></script></body></html> |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"main":{"js":"/js/main.6641e1a4.js","css":"/css/main.3cd7fa7f.css"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
#! /usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
# vim:fenc=utf-8 | ||
# | ||
# Copyright © 2017 unga <[email protected]> | ||
# | ||
# Distributed under terms of the MIT license. | ||
""" | ||
Monitor a set of parameter in a background thread | ||
stream opuput over websocket | ||
""" | ||
|
||
import asyncio | ||
import logging | ||
import os | ||
import time | ||
import json | ||
import http.server | ||
import socketserver | ||
import webbrowser | ||
|
||
from threading import Thread | ||
from typing import Dict | ||
from concurrent.futures import Future | ||
from concurrent.futures import CancelledError | ||
import functools | ||
|
||
import websockets | ||
|
||
SERVER_PORT = 3000 | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
def _get_metadata(*parameters) -> Dict[float, list]: | ||
""" | ||
Return a dict that contains the paraemter metadata grouped by the | ||
instrument it belongs to. | ||
""" | ||
ts = time.time() | ||
# group meta data by instrument if any | ||
metas = {} | ||
for parameter in parameters: | ||
_meta = getattr(parameter, "_latest", None) | ||
if _meta: | ||
meta = _meta() | ||
else: | ||
raise ValueError("Input is not a paraemter; Refusing to proceed") | ||
# convert to string | ||
meta['value'] = str(meta['value']) | ||
if meta["ts"] is not None: | ||
meta["ts"] = time.mktime(meta["ts"].timetuple()) | ||
meta["name"] = parameter.label or parameter.name | ||
meta["unit"] = parameter.unit | ||
accumulator = metas.get(str(parameter._instrument), []) | ||
accumulator.append(meta) | ||
metas[str(parameter._instrument)] = accumulator | ||
parameters = [] | ||
for instrument in metas: | ||
temp = {"instrument": instrument, "parameters": metas[instrument]} | ||
parameters.append(temp) | ||
state = {"ts": ts, "parameters": parameters} | ||
return state | ||
|
||
|
||
def _handler(parameters, interval: int): | ||
|
||
async def serverFunc(websocket, path): | ||
while True: | ||
try: | ||
try: | ||
meta = _get_metadata(*parameters) | ||
except ValueError as e: | ||
log.exception(e) | ||
break | ||
log.debug("sending..") | ||
try: | ||
await websocket.send(json.dumps(meta)) | ||
# mute browser discconects | ||
except websockets.exceptions.ConnectionClosed as e: | ||
log.debug(e) | ||
pass | ||
await asyncio.sleep(interval) | ||
except CancelledError: | ||
break | ||
log.debug("closing sever") | ||
|
||
return serverFunc | ||
|
||
|
||
class Monitor(Thread): | ||
running = None | ||
server = None | ||
|
||
def __init__(self, *parameters, interval=1): | ||
""" | ||
Monitor qcodes parameters. | ||
Args: | ||
*parameters: Parameters to monitor | ||
interval: How often one wants to refresh the values | ||
""" | ||
super().__init__() | ||
self.loop = None | ||
# start the server to server monitor http/files | ||
if Monitor.server: | ||
self.show() | ||
else: | ||
Monitor.server = Server(port=SERVER_PORT) | ||
Monitor.server.start() | ||
self._monitor(*parameters, interval=1) | ||
|
||
def run(self): | ||
""" | ||
Start the event loop and run forever | ||
""" | ||
self.loop = asyncio.new_event_loop() | ||
asyncio.set_event_loop(self.loop) | ||
Monitor.running = self | ||
self.show() | ||
self.loop.run_forever() | ||
|
||
def stop(self): | ||
""" | ||
Shutodwn the server, close the event loop and join the thread | ||
""" | ||
# this contains the server | ||
# or any exception | ||
server = self.future_restult.result() | ||
# server.close() | ||
self.loop.call_soon_threadsafe(server.close) | ||
self.loop.call_soon_threadsafe(self.loop.stop) | ||
self.join() | ||
Monitor.running = None | ||
|
||
@staticmethod | ||
def show(): | ||
""" | ||
Overwrite this method to show/raise your monitor GUI | ||
F.ex. | ||
:: | ||
import webbrowser | ||
url = "localhost:3000" | ||
# Open URL in new window, raising the window if possible. | ||
webbrowser.open_new(url) | ||
""" | ||
webbrowser.open("http://localhost:{}".format(SERVER_PORT)) | ||
|
||
def _monitor(self, *parameters, interval=1): | ||
handler = _handler(parameters, interval=interval) | ||
# TODO (giulioungaretti) read from config | ||
server = websockets.serve(handler, '127.0.0.1', 5678) | ||
|
||
log.debug("Start monitoring thread") | ||
|
||
if Monitor.running: | ||
# stop the old server | ||
log.debug("Stoppging and restarting server") | ||
Monitor.running.stop() | ||
|
||
self.start() | ||
|
||
# let the thread start | ||
time.sleep(0.001) | ||
|
||
log.debug("Start monitoring server") | ||
self._add_task(server) | ||
|
||
def _create_task(self, future, coro): | ||
task = self.loop.create_task(coro) | ||
future.set_result(task) | ||
|
||
def _add_task(self, coro): | ||
future = Future() | ||
self.task = coro | ||
p = functools.partial(self._create_task, future, coro) | ||
self.loop.call_soon_threadsafe(p) | ||
# this stores the result of the future | ||
self.future_restult = future.result() | ||
self.future_restult.add_done_callback(_log_result) | ||
|
||
|
||
def _log_result(future): | ||
try: | ||
future.result() | ||
log.debug("Started server loop") | ||
except: | ||
log.exception("Could not start server loop") | ||
|
||
|
||
class Server(Thread): | ||
|
||
def __init__(self, port=3000): | ||
self.port = port | ||
self.handler = http.server.SimpleHTTPRequestHandler | ||
self.httpd = socketserver.TCPServer(("", self.port), self.handler) | ||
self.static_dir = os.path.join(os.path.dirname(__file__), 'dist') | ||
super().__init__() | ||
|
||
def run(self): | ||
os.chdir(self.static_dir) | ||
log.debug("serving directory %s", self.static_dir) | ||
log.debug("serving at port", self.port) | ||
self.httpd.serve_forever() | ||
|
||
def stop(self): | ||
self.httpd.shutdown() | ||
self.join() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters