This commit is contained in:
gac-S_Changer
2018-11-07 11:36:38 +01:00
parent bc78e8fd40
commit 92574c1341
23 changed files with 749 additions and 63 deletions

View 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

167
script/client/TellClient.py Normal file
View File

@@ -0,0 +1,167 @@
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 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):
#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)
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(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 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 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 ("")
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()))

42
script/client/tell.py Normal file
View File

@@ -0,0 +1,42 @@
import sys
from TellClient import TellClient
import code
tell = TellClient("http://Alexandres-MBP.psi.ch:8080")
def info():
tell.print_info()
def help():
print ("Commands: help(), info(), mount(segment, puck, sample), unmount(), scan(segment, puck, sample=None), move_cold(), trash(), abort()\n")
def mount(segment, puck, sample):
tell.mount(segment, puck, sample, True, True)
def move_cold():
tell.mount(force=True)
def unmount():
tell.move_cold()
def trash():
tell.trash()
def abort():
tell.abort_cmd()
def scan(segment, puck, sample=None):
if sample is None:
tell.scan_pin(segment, puck, True)
else:
tell.scan_pin(segment, puck, sample, True)
info()
help()
#for line in sys.stdin:
# tell.print_info()
#print ("", end='\r> ')
code.interact(local=locals())