Startup
This commit is contained in:
369
script/client/PShellClient.py
Normal file
369
script/client/PShellClient.py
Normal file
@@ -0,0 +1,369 @@
|
||||
import threading
|
||||
import time
|
||||
import sys
|
||||
import requests
|
||||
import json
|
||||
|
||||
try:
|
||||
from urllib import quote # Python 2
|
||||
except ImportError:
|
||||
from urllib.parse import quote # Python 3
|
||||
|
||||
try:
|
||||
from sseclient import SSEClient
|
||||
except:
|
||||
SSEClient = None
|
||||
|
||||
|
||||
class PShellClient:
|
||||
def __init__(self, url):
|
||||
self.url = url
|
||||
self.sse_event_loop_thread = None
|
||||
self.subscribed_events = None
|
||||
self.event_callback = None
|
||||
|
||||
def _get_response(self, response, is_json=True):
|
||||
if response.status_code != 200:
|
||||
raise Exception(response.text)
|
||||
return json.loads(response.text) if is_json else response.text
|
||||
|
||||
def _get_binary_response(self, response):
|
||||
if response.status_code != 200:
|
||||
raise Exception(response.text)
|
||||
return response.raw.read()
|
||||
|
||||
def get_version(self):
|
||||
"""Return application version.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
String with application version.
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url+"/version"), False)
|
||||
|
||||
def get_config(self):
|
||||
"""Return application configuration.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
Dictionary.
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url+"/config"))
|
||||
|
||||
def get_state(self):
|
||||
"""Return application state.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
String: Invalid, Initializing,Ready, Paused, Busy, Disabled, Closing, Fault, Offline
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url+"/state"))
|
||||
|
||||
def get_logs(self):
|
||||
"""Return application logs.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
List of logs.
|
||||
Format of each log: [date, time, origin, level, description]
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url+"/logs"))
|
||||
|
||||
def get_history(self, index):
|
||||
"""Access console command history.
|
||||
|
||||
Args:
|
||||
index(int): Index of history entry (0 is the most recent)
|
||||
|
||||
Returns:
|
||||
History entry
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url+"/history/"+str(index)), False)
|
||||
|
||||
def get_script(self, path):
|
||||
"""Return script.
|
||||
|
||||
Args:
|
||||
path(str): Script path (absolute or relative to script folder)
|
||||
|
||||
Returns:
|
||||
String with file contents.
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url+"/script/"+str(path)), False)
|
||||
|
||||
def get_devices(self):
|
||||
"""Return global devices.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
List of devices.
|
||||
Format of each device record: [name, type, state, value, age]
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url+"/devices"))
|
||||
|
||||
def abort(self, command_id=None):
|
||||
"""Abort execution of command
|
||||
|
||||
Args:
|
||||
command_id(optional, int): id of the command to be aborted.
|
||||
if None (default), aborts the foreground execution.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if command_id is None:
|
||||
requests.get(url=self.url+"/abort")
|
||||
else:
|
||||
return requests.get(url=self.url+"/abort/"+str(command_id))
|
||||
|
||||
def reinit(self):
|
||||
"""Reinitialize the software.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
requests.get(url=self.url+"/reinit")
|
||||
|
||||
def stop(self):
|
||||
"""Stop all devices implementing the 'Stoppable' interface.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
requests.get(url=self.url+"/stop")
|
||||
|
||||
def update(self):
|
||||
"""Update all global devices.
|
||||
|
||||
Args:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
requests.get(url=self.url+"/update")
|
||||
|
||||
def eval(self,statement):
|
||||
"""Evaluates a statement in the interpreter.
|
||||
If the statement finishes by '&', it is executed in background.
|
||||
Otherwise statement is executed in foreground (exclusive).
|
||||
|
||||
Args:
|
||||
statement(str): input statement
|
||||
|
||||
Returns:
|
||||
String containing the console return.
|
||||
If an exception is produces in the interpretor, it is re-thrown here.
|
||||
"""
|
||||
statement = quote(statement)
|
||||
return self._get_response(requests.get(url=self.url+"/eval/"+statement), False)
|
||||
|
||||
def run(self,script, pars=None, background=False):
|
||||
"""Executes script in the interpreter.
|
||||
|
||||
Args:
|
||||
script(str): name of the script (absolute or relative to the script base folder). Extension may be omitted.
|
||||
pars(optional, list or dict): if a list is given, it sets sys.argv for the script.
|
||||
If a dict is given, it sets global variable for the script.
|
||||
background(optional, bool): if True script is executed in background.
|
||||
|
||||
Returns:
|
||||
Return value of the script.
|
||||
If an exception is produces in the interpretor, it is re-thrown here.
|
||||
"""
|
||||
return self._get_response(requests.put(url=self.url+"/run", json={"script":script, "pars":pars, "background":background, "async":False }))
|
||||
|
||||
def start_eval(self,statement):
|
||||
"""Starts evaluation of a statement in the interpreter.
|
||||
If the statement finishes by '&', it is executed in background.
|
||||
Otherwise statement is executed in foreground (exclusive).
|
||||
|
||||
Args:
|
||||
statement(str): input statement
|
||||
|
||||
Returns:
|
||||
Command id (int), which is used to retrieve command execution status/result (get_result).
|
||||
"""
|
||||
statement = quote(statement)
|
||||
return int(self._get_response(requests.get(url=self.url+"/evalAsync/"+statement), False))
|
||||
|
||||
def start_run(self,script, pars=None, background=False):
|
||||
"""Starts execution of a script in the interpreter.
|
||||
|
||||
Args:
|
||||
script(str): name of the script (absolute or relative to the script base folder). Extension may be omitted.
|
||||
pars(optional, list or dict): if a list is given, it sets sys.argv for the script.
|
||||
If a dict is given, it sets global variable for the script.
|
||||
background(optional, bool): if True script is executed in background.
|
||||
|
||||
Returns:
|
||||
Command id (int), which is used to retrieve command execution status/result (get_result).
|
||||
"""
|
||||
return int(self._get_response(requests.put(url=self.url+"/run", json={"script":script, "pars":pars, "background":background, "async":True })))
|
||||
|
||||
def get_result(self, command_id=-1):
|
||||
"""Gets status/result of a command executed asynchronously (start_eval and start_run).
|
||||
|
||||
Args:
|
||||
command_id(optional, int): command id. If equals to -1 (default) return status/result of the foreground task.
|
||||
|
||||
Returns:
|
||||
Dictionary with the fields: 'id' (int): command id
|
||||
'status' (str): unlaunched, invalid, removed, running, aborted, failed or completed.
|
||||
'exception' (str): if status equals 'failed', holds exception string.
|
||||
'return' (obj): if status equals 'completed', holds return value of script (start_run)
|
||||
or console return (start_eval)
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url+"/result/"+str(command_id)))
|
||||
|
||||
def help(self, input = "<builtins>"):
|
||||
"""Returns help or auto-completion strings.
|
||||
|
||||
Args:
|
||||
input(optional, str): - ":" for control commands
|
||||
- "<builtins>" for builtin functions
|
||||
- "devices" for device names
|
||||
- builtin function name for function help
|
||||
- else contains entry for auto-completion
|
||||
|
||||
Returns:
|
||||
List
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url+"/autocompletion/" + input))
|
||||
|
||||
def get_contents(self, path=None):
|
||||
"""Returns contents of data path.
|
||||
|
||||
Args:
|
||||
path(optional, str): Path to data relative to data home path.
|
||||
- Folder
|
||||
- File
|
||||
- File (data root) | internal path
|
||||
- internal path (on currently open data root)
|
||||
|
||||
Returns:
|
||||
List of contents
|
||||
|
||||
"""
|
||||
return self._get_response(requests.get(url=self.url+ "/contents" + ("" if path is None else ( "/"+path))), False)
|
||||
|
||||
def get_data(self, path, type="txt"):
|
||||
"""Returns data on a given path.
|
||||
|
||||
Args:
|
||||
path(str): Path to data relative to data home path.
|
||||
- File (data root) | internal path
|
||||
- internal path (on currently open data root)
|
||||
type(optional, str): txt, "json", "bin", "bs"
|
||||
|
||||
Returns:
|
||||
Data accordind to selected format/.
|
||||
|
||||
"""
|
||||
if type == "json":
|
||||
return self._get_response(requests.get(url=self.url+ "/data-json/"+path), True)
|
||||
elif type == "bin":
|
||||
return self._get_binary_response(requests.get(url=self.url+"/data-bin/"+path, stream=True))
|
||||
elif type == "bs":
|
||||
from collections import OrderedDict
|
||||
bs = self._get_binary_response(requests.get(url=self.url+"/data-bs/"+path, stream=True))
|
||||
index=0
|
||||
msg = []
|
||||
for i in range(4):
|
||||
size =int.from_bytes(bs[index:index+4], byteorder='big', signed=False)
|
||||
index=index+4
|
||||
msg.append(bs[index:index+size])
|
||||
index=index+size
|
||||
[main_header, data_header, data, timestamp] = msg
|
||||
main_header = json.loads(main_header, object_pairs_hook=OrderedDict)
|
||||
data_header = json.loads(data_header, object_pairs_hook=OrderedDict)
|
||||
channel = data_header["channels"][0]
|
||||
channel["encoding"] = "<" if channel.get("encoding", "little") else ">"
|
||||
from bsread.data.helpers import get_channel_reader
|
||||
channel_value_reader = get_channel_reader(channel)
|
||||
return channel_value_reader(data)
|
||||
|
||||
return self._get_response(requests.get(url=self.url+ "/data" + ("" if path is None else ( "/"+path))), False)
|
||||
|
||||
def print_logs(self):
|
||||
for l in self.get_logs():
|
||||
print ("%s %s %-20s %-8s %s" % tuple(l))
|
||||
|
||||
def print_devices(self):
|
||||
for l in self.get_devices():
|
||||
print ("%-16s %-32s %-10s %-32s %s" % tuple(l))
|
||||
|
||||
def print_help(self, input = "<builtins>"):
|
||||
for l in self.help(input):
|
||||
print (l)
|
||||
|
||||
#Events
|
||||
def _sse_event_loop_task(self):
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
messages = SSEClient(self.url+"/events")
|
||||
for msg in messages:
|
||||
if (self.subscribed_events is None) or (msg.event in self.subscribed_events):
|
||||
try:
|
||||
value = json.loads(msg.data)
|
||||
except:
|
||||
value = str(msg.data)
|
||||
self.event_callback(msg.event, value)
|
||||
except IOError as e:
|
||||
#print(e)
|
||||
pass
|
||||
except:
|
||||
print("Error:", sys.exc_info()[1])
|
||||
#raise
|
||||
finally:
|
||||
print ("Exit SSE loop task")
|
||||
self.sse_event_loop_thread = None
|
||||
|
||||
|
||||
def start_sse_event_loop_task(self, subscribed_events = None, event_callback = None):
|
||||
"""
|
||||
Initializes server event loop task.
|
||||
Args:
|
||||
subscribed_events: list of event names to substribe to. If None subscribes to all.
|
||||
event_callback: callback function. If None, self.on_event is called instead.
|
||||
|
||||
Usage example:
|
||||
def on_event(name, value):
|
||||
if name == "state":
|
||||
print ("State changed: ", value)
|
||||
elif name == "record":
|
||||
print ("Received scan record: ", value)
|
||||
|
||||
pc.start_sse_event_loop_task(["state", "record"], on_event)
|
||||
|
||||
"""
|
||||
self.event_callback = event_callback if event_callback is not None else self.on_event
|
||||
self.subscribed_events = subscribed_events
|
||||
if SSEClient is not None:
|
||||
if self.sse_event_loop_thread is None:
|
||||
self.sse_event_loop_thread = threading.Thread(target=self._sse_event_loop_task, \
|
||||
args = (), \
|
||||
kwargs={}, \
|
||||
daemon=True)
|
||||
self.sse_event_loop_thread.start()
|
||||
else:
|
||||
raise Exception ("sseclient library is not instlled: server events are not available")
|
||||
|
||||
def on_event(self, name, value):
|
||||
pass
|
||||
|
||||
184
script/client/TellClient.py
Normal file
184
script/client/TellClient.py
Normal file
@@ -0,0 +1,184 @@
|
||||
from PShellClient import PShellClient
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
class TellClient(PShellClient):
|
||||
def __init__(self, url):
|
||||
PShellClient.__init__(self, url)
|
||||
self.start_sse_event_loop_task(["state", "shell"])
|
||||
self.state = self.get_state()
|
||||
self.debug=False
|
||||
|
||||
def on_event(self, name, value):
|
||||
if name == "state":
|
||||
self.state = value
|
||||
print ("State: ", value)
|
||||
elif name == "shell":
|
||||
if self.debug:
|
||||
print ("> ", value)
|
||||
|
||||
def get_state(self):
|
||||
self.state = PShellClient.get_state(self)
|
||||
return self.state
|
||||
|
||||
def wait_ready(self):
|
||||
count = 0
|
||||
#Monitors event but polls every second just n case an event is missed
|
||||
while (True):
|
||||
if self.state != "Busy":
|
||||
break
|
||||
time.sleep(0.01)
|
||||
count = count + 1
|
||||
if count>=100:
|
||||
count=0
|
||||
self.get_state()
|
||||
if self.state != "Ready":
|
||||
raise Exception("Invalid state: " + str(self.state))
|
||||
|
||||
def set_in_mount_position(self, value):
|
||||
self.eval("in_mount_position = " + str(value) +"&")
|
||||
|
||||
def is_in_mount_position(self):
|
||||
return self.eval("in_mount_position&").lower()=="true"
|
||||
|
||||
def get_samples_info(self):
|
||||
return json.loads(self.eval("get_samples_info()&"))
|
||||
|
||||
def set_samples_info(self, info):
|
||||
#c.run("data/set_samples_info", pars= [info,], background=True)
|
||||
self.eval("set_samples_info(" + json.dumps(info) + ")&")
|
||||
|
||||
def start_cmd(self, cmd, *argv):
|
||||
cmd = cmd + "("
|
||||
for a in argv:
|
||||
cmd = cmd + (("'" + a + "'") if type(a) is str else str(a) ) + ", "
|
||||
cmd = cmd + ")"
|
||||
ret = self.start_eval(cmd)
|
||||
self.get_state()
|
||||
return ret
|
||||
|
||||
def wait_cmd(self, cmd):
|
||||
self.wait_ready()
|
||||
result = self.get_result(cmd)
|
||||
#print (result)
|
||||
if result["exception"] is not None:
|
||||
raise Exception(result["exception"] )
|
||||
return result["return"]
|
||||
|
||||
def mount(self, segment, puck, sample, force=False, read_dm=False, auto_unmount=False):
|
||||
#return self.run("motion/mount", pars= [segment,puck, sample, force, read_dm], background=True)
|
||||
return self.start_cmd("mount", segment, puck, sample, force, read_dm, auto_unmount)
|
||||
|
||||
def unmount(self, segment = None, puck = None, sample = None, force=False):
|
||||
return self.start_cmd("unmount", segment, puck, sample, force)
|
||||
|
||||
def scan_pin(self, segment, puck, sample, force=False):
|
||||
return self.start_cmd("scan_pin", segment, puck, sample, force)
|
||||
|
||||
def scan_puck(self, segment, puck, force=False):
|
||||
return self.start_cmd("scan_puck", segment, puck, force)
|
||||
|
||||
def dry(self, heat_time=30.0, speed=0.5, wait_cold = 30.0):
|
||||
return self.start_cmd("dry", heat_time, speed, wait_cold)
|
||||
|
||||
def move_cold(self):
|
||||
return self.start_cmd("move_cold")
|
||||
|
||||
def trash(self):
|
||||
return self.start_cmd("trash")
|
||||
|
||||
def abort_cmd(self):
|
||||
self.abort()
|
||||
self.eval("robot.stop_task()&")
|
||||
|
||||
def set_gonio_mount_position(homing = False):
|
||||
if homing:
|
||||
self.eval("home_fast_table()")
|
||||
self.eval("set_mount_position()")
|
||||
|
||||
def get_mounted_sample(self):
|
||||
ret = self.eval("get_setting('mounted_sample_position')&").strip()
|
||||
return None if len(ret)==0 else ret
|
||||
|
||||
def get_system_check(self):
|
||||
try:
|
||||
ret = self.eval("system_check()&")
|
||||
except Exception as ex:
|
||||
return ex
|
||||
return "Ok"
|
||||
|
||||
def get_robot_state(self):
|
||||
return self.eval("robot.state&")
|
||||
|
||||
|
||||
def get_robot_status(self):
|
||||
status = self.eval("robot.take()&")
|
||||
return status
|
||||
|
||||
def get_detected_pucks(self):
|
||||
return self.eval("get_detected_pucks()&")
|
||||
|
||||
def set_pin_offset(self, value):
|
||||
self.eval("set_pin_offset(" + str(value)+ ")&")
|
||||
|
||||
def get_pin_offset(self):
|
||||
return self.eval("get_pin_offset()&")
|
||||
|
||||
def print_info(self):
|
||||
print ("State: " + str(self.get_state()))
|
||||
print ("Mounted sample: " + str(self.get_mounted_sample()))
|
||||
print ("System check: " + str(self.get_system_check()))
|
||||
print ("Robot state: " + str(self.get_robot_state()))
|
||||
print ("Robot status: " + str(self.get_robot_status()))
|
||||
print ("Detected pucks: " + str(self.get_detected_pucks()))
|
||||
print ("Pin offset: " + str(self.get_pin_offset()))
|
||||
print ("Mount position: " + str(self.is_in_mount_position()))
|
||||
print ("")
|
||||
|
||||
if __name__ == "__main__":
|
||||
tell = TellClient("http://Alexandres-MBP.psi.ch:8080")
|
||||
tell.print_info()
|
||||
|
||||
info = [
|
||||
{
|
||||
"userName": "User",
|
||||
"dewarName": "Dewar",
|
||||
"puckName": "Puck",
|
||||
"puckBarcode": "XXX0001",
|
||||
"puckType": "Minispine",
|
||||
"puckAddress": "",
|
||||
"sampleName": "Sample",
|
||||
"sampleBarcode": "YYY0001",
|
||||
"samplePosition": "1",
|
||||
"sampleStatus": "Present",
|
||||
"sampleMountCount": "0" ,
|
||||
},
|
||||
]
|
||||
print (tell.get_samples_info())
|
||||
tell.set_samples_info(info)
|
||||
print (tell.get_samples_info())
|
||||
|
||||
tell.abort_cmd()
|
||||
|
||||
cmd = tell.move_cold()
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
cmd = tell.trash()
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
cmd = tell.scan_pin("A", 1, 1)
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
cmd = tell.scan_puck("A", 1, 1)
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
cmd = tell.mount("A", 1, 1)
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
print ("Mounted sample: " + str(tell.get_mounted_sample()))
|
||||
cmd = tell.unmount()
|
||||
print (tell.wait_cmd(cmd))
|
||||
print ("Mounted sample: " + str(tell.get_mounted_sample()))
|
||||
163
script/client/sseclient.py
Normal file
163
script/client/sseclient.py
Normal file
@@ -0,0 +1,163 @@
|
||||
import codecs
|
||||
import re
|
||||
import time
|
||||
import warnings
|
||||
|
||||
import six
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
# Technically, we should support streams that mix line endings. This regex,
|
||||
# however, assumes that a system will provide consistent line endings.
|
||||
end_of_field = re.compile(r'\r\n\r\n|\r\r|\n\n')
|
||||
|
||||
class SSEClient(object):
|
||||
def __init__(self, url, last_id=None, retry=3000, session=None, chunk_size=1024, **kwargs):
|
||||
self.url = url
|
||||
self.last_id = last_id
|
||||
self.retry = retry
|
||||
self.chunk_size = chunk_size
|
||||
|
||||
# Optional support for passing in a requests.Session()
|
||||
self.session = session
|
||||
|
||||
# Any extra kwargs will be fed into the requests.get call later.
|
||||
self.requests_kwargs = kwargs
|
||||
|
||||
# The SSE spec requires making requests with Cache-Control: nocache
|
||||
if 'headers' not in self.requests_kwargs:
|
||||
self.requests_kwargs['headers'] = {}
|
||||
self.requests_kwargs['headers']['Cache-Control'] = 'no-cache'
|
||||
|
||||
# The 'Accept' header is not required, but explicit > implicit
|
||||
self.requests_kwargs['headers']['Accept'] = 'text/event-stream'
|
||||
|
||||
# Keep data here as it streams in
|
||||
self.buf = u''
|
||||
|
||||
self._connect()
|
||||
|
||||
def _connect(self):
|
||||
if self.last_id:
|
||||
self.requests_kwargs['headers']['Last-Event-ID'] = self.last_id
|
||||
|
||||
# Use session if set. Otherwise fall back to requests module.
|
||||
requester = self.session or requests
|
||||
self.resp = requester.get(self.url, stream=True, **self.requests_kwargs)
|
||||
self.resp_iterator = self.resp.iter_content(chunk_size=self.chunk_size)
|
||||
|
||||
# TODO: Ensure we're handling redirects. Might also stick the 'origin'
|
||||
# attribute on Events like the Javascript spec requires.
|
||||
self.resp.raise_for_status()
|
||||
|
||||
def _event_complete(self):
|
||||
return re.search(end_of_field, self.buf) is not None
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
decoder = codecs.getincrementaldecoder(
|
||||
self.resp.encoding)(errors='replace')
|
||||
while not self._event_complete():
|
||||
try:
|
||||
next_chunk = next(self.resp_iterator)
|
||||
if not next_chunk:
|
||||
raise EOFError()
|
||||
self.buf += decoder.decode(next_chunk)
|
||||
|
||||
except (StopIteration, requests.RequestException, EOFError) as e:
|
||||
time.sleep(self.retry / 1000.0)
|
||||
self._connect()
|
||||
|
||||
# The SSE spec only supports resuming from a whole message, so
|
||||
# if we have half a message we should throw it out.
|
||||
head, sep, tail = self.buf.rpartition('\n')
|
||||
self.buf = head + sep
|
||||
continue
|
||||
|
||||
# Split the complete event (up to the end_of_field) into event_string,
|
||||
# and retain anything after the current complete event in self.buf
|
||||
# for next time.
|
||||
(event_string, self.buf) = re.split(end_of_field, self.buf, maxsplit=1)
|
||||
msg = Event.parse(event_string)
|
||||
|
||||
# If the server requests a specific retry delay, we need to honor it.
|
||||
if msg.retry:
|
||||
self.retry = msg.retry
|
||||
|
||||
# last_id should only be set if included in the message. It's not
|
||||
# forgotten if a message omits it.
|
||||
if msg.id:
|
||||
self.last_id = msg.id
|
||||
|
||||
return msg
|
||||
|
||||
if six.PY2:
|
||||
next = __next__
|
||||
|
||||
|
||||
class Event(object):
|
||||
|
||||
sse_line_pattern = re.compile('(?P<name>[^:]*):?( ?(?P<value>.*))?')
|
||||
|
||||
def __init__(self, data='', event='message', id=None, retry=None):
|
||||
self.data = data
|
||||
self.event = event
|
||||
self.id = id
|
||||
self.retry = retry
|
||||
|
||||
def dump(self):
|
||||
lines = []
|
||||
if self.id:
|
||||
lines.append('id: %s' % self.id)
|
||||
|
||||
# Only include an event line if it's not the default already.
|
||||
if self.event != 'message':
|
||||
lines.append('event: %s' % self.event)
|
||||
|
||||
if self.retry:
|
||||
lines.append('retry: %s' % self.retry)
|
||||
|
||||
lines.extend('data: %s' % d for d in self.data.split('\n'))
|
||||
return '\n'.join(lines) + '\n\n'
|
||||
|
||||
@classmethod
|
||||
def parse(cls, raw):
|
||||
"""
|
||||
Given a possibly-multiline string representing an SSE message, parse it
|
||||
and return a Event object.
|
||||
"""
|
||||
msg = cls()
|
||||
for line in raw.splitlines():
|
||||
m = cls.sse_line_pattern.match(line)
|
||||
if m is None:
|
||||
# Malformed line. Discard but warn.
|
||||
warnings.warn('Invalid SSE line: "%s"' % line, SyntaxWarning)
|
||||
continue
|
||||
|
||||
name = m.group('name')
|
||||
if name == '':
|
||||
# line began with a ":", so is a comment. Ignore
|
||||
continue
|
||||
value = m.group('value')
|
||||
|
||||
if name == 'data':
|
||||
# If we already have some data, then join to it with a newline.
|
||||
# Else this is it.
|
||||
if msg.data:
|
||||
msg.data = '%s\n%s' % (msg.data, value)
|
||||
else:
|
||||
msg.data = value
|
||||
elif name == 'event':
|
||||
msg.event = value
|
||||
elif name == 'id':
|
||||
msg.id = value
|
||||
elif name == 'retry':
|
||||
msg.retry = int(value)
|
||||
|
||||
return msg
|
||||
|
||||
def __str__(self):
|
||||
return self.data
|
||||
79
script/client/tell.py
Normal file
79
script/client/tell.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import sys
|
||||
import os
|
||||
import code
|
||||
import readline
|
||||
import rlcompleter
|
||||
import atexit
|
||||
|
||||
from TellClient import TellClient
|
||||
|
||||
tell = TellClient("http://PC12288:8080")
|
||||
|
||||
def info():
|
||||
tell.print_info()
|
||||
|
||||
def help():
|
||||
print ("Commands: \n\thelp()\n\tinfo()\n\tmount(segment, puck, sample)\n\tunmount() \n\tdry() " \
|
||||
"\n\tscan(segment, puck, sample=None) \n\tmove_cold() \n\ttrash() \n\tabort() " \
|
||||
"\n\tset_pin_offset(value)")
|
||||
|
||||
def assert_transfer_allowed():
|
||||
if not tell.is_in_mount_position():
|
||||
raise Exception("Gonio is not in mount position")
|
||||
|
||||
def mount(segment, puck, sample):
|
||||
assert_transfer_allowed()
|
||||
cmd = tell.mount(segment, puck, sample, True, True, True)
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
def unmount():
|
||||
assert_transfer_allowed()
|
||||
cmd=tell.unmount(force=True)
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
def move_cold():
|
||||
cmd=tell.move_cold()
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
def trash():
|
||||
cmd=tell.trash()
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
def dry():
|
||||
cmd=tell.dry()
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
def scan(segment, puck, sample=None):
|
||||
if sample is None:
|
||||
cmd=tell.scan_puck(segment, puck, True)
|
||||
else:
|
||||
cmd=tell.scan_pin(segment, puck, sample, True)
|
||||
print (tell.wait_cmd(cmd))
|
||||
|
||||
def abort():
|
||||
tell.abort_cmd()
|
||||
|
||||
def set_pin_offset(value):
|
||||
tell.set_pin_offset(value)
|
||||
|
||||
info()
|
||||
help()
|
||||
|
||||
#for line in sys.stdin:
|
||||
# tell.print_info()
|
||||
#print ("", end='\r> ')
|
||||
|
||||
historyPath = os.path.expanduser("./.history")
|
||||
def save_history(historyPath=historyPath):
|
||||
readline.write_history_file(historyPath)
|
||||
if os.path.exists(historyPath):
|
||||
readline.read_history_file(historyPath)
|
||||
atexit.register(save_history)
|
||||
|
||||
#vars = globals(); vars.update(locals())
|
||||
vars =locals()
|
||||
readline.set_completer(rlcompleter.Completer(vars).complete)
|
||||
readline.parse_and_bind("tab: complete")
|
||||
|
||||
code.interact(banner = "", local=vars)
|
||||
|
||||
Reference in New Issue
Block a user