diff --git a/dstat_interface/dstat_comm.py b/dstat_interface/dstat_comm.py index ac58436aae39fa901301d1784cc6664da337b441..4060c30a10f005e8fb23f75b0125220191f9c2a5 100755 --- a/dstat_interface/dstat_comm.py +++ b/dstat_interface/dstat_comm.py @@ -26,23 +26,32 @@ import logging from errors import InputError, VarError +from simulator import SerialSim + logger = logging.getLogger("dstat.comm") dstat_logger = logging.getLogger("dstat.comm.DSTAT") exp_logger = logging.getLogger("dstat.comm.Experiment") -def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe): +def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe, simulate=False): ser_logger = logging.getLogger("dstat.comm._serial_process") - ser = delayedSerial(ser_port, baudrate=1000000, timeout=1) + if simulate is True: + ser_logger.warning("DStat Simulator enabled") + ser = SerialSim(ser_port, baudrate=1000000, timeout=1) + else: + ser = delayedSerial(ser_port, baudrate=1000000, timeout=1) ser_logger.info("Reattaching DStat udc") - ser.write("!R") # Send restart command + # ser.write("!R") # Send restart command ser.close() for i in range(5): time.sleep(1) # Give OS time to enumerate try: - ser = delayedSerial(ser_port, baudrate=1000000, timeout=1) + if simulate is True: + ser = SerialSim(ser_port, baudrate=1000000, timeout=1) + else: + ser = delayedSerial(ser_port, baudrate=1000000, timeout=1) ser_logger.info("Connecting") break except serial.SerialException: @@ -60,7 +69,7 @@ def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe): else: ser.write('V') break - + ser_logger.info("Done Init") while True: # These can only be called when no experiment is running if ctrl_pipe.poll(): @@ -92,14 +101,15 @@ def _serial_process(ser_port, proc_pipe, ctrl_pipe, data_pipe): class SerialConnection(object): - def __init__(self, ser_port): + def __init__(self, ser_port, simulate): self.proc_pipe_p, self.proc_pipe_c = mp.Pipe(duplex=True) self.ctrl_pipe_p, self.ctrl_pipe_c = mp.Pipe(duplex=True) self.data_pipe_p, self.data_pipe_c = mp.Pipe(duplex=True) self.proc = mp.Process(target=_serial_process, args=(ser_port, self.proc_pipe_c, self.ctrl_pipe_c, - self.data_pipe_c)) + self.data_pipe_c, simulate)) + logger.info("Process: {}".format(self.proc)) self.proc.start() @@ -150,7 +160,7 @@ class VersionCheck: finally: return status -def version_check(ser_port): +def version_check(ser_port, simulate=False): """Tries to contact DStat and get version. Returns a list of [(major, minor), serial instance]. If no response, returns empty tuple. @@ -159,7 +169,7 @@ def version_check(ser_port): """ try: global serial_instance - serial_instance = SerialConnection(ser_port) + serial_instance = SerialConnection(ser_port, simulate) serial_instance.proc_pipe_p.send(VersionCheck()) result = serial_instance.proc_pipe_p.recv() @@ -216,11 +226,11 @@ class Settings: dstat_logger.debug(line.lstrip().rstrip()) self.ser.flushInput() break - + parted = input.rstrip().split(':') - for i in range(len(parted)): - settings[parted[i].split('.')[0]] = [i, parted[i].split('.')[1]] + for n, i in enumerate(parted): + settings[i.split('.')[0]] = [n, i.split('.')[1]] return settings diff --git a/dstat_interface/interface/dstatinterface.glade b/dstat_interface/interface/dstatinterface.glade index e134224eedc6842d6e319acb34100b294d71a1cd..e0eedf7b627e63e9595e6621b3c12616216481a7 100644 --- a/dstat_interface/interface/dstatinterface.glade +++ b/dstat_interface/interface/dstatinterface.glade @@ -943,6 +943,19 @@ Thanks to Christian Fobel for help with Dropbot Plugin</property> </child> </object> </child> + <child> + <object class="GtkMenuItem" id="menu_developer"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Developer</property> + <child type="submenu"> + <object class="GtkMenu" id="menubox_dev"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + </object> + </child> </object> <packing> <property name="expand">False</property> diff --git a/dstat_interface/main.py b/dstat_interface/main.py index bca4a1e561c53c2b56e6882a3aed12913ca3da4f..8869ad95678e91cdc9c4442baddfae07d32202a2 100755 --- a/dstat_interface/main.py +++ b/dstat_interface/main.py @@ -59,6 +59,7 @@ import analysis import zmq import db from errors import InputError +import simulator from plugin import DstatPlugin, get_hub_uri @@ -82,7 +83,10 @@ class Main(object): self.builder.add_from_file('interface/dstatinterface.glade') self.builder.connect_signals(self) self.cell = Gtk.CellRendererText() - + + # Add simulator item to menu + self.simulate = simulator.menu_setup(self.builder) + # Create instance of interface components self.statusbar = self.builder.get_object('statusbar') self.ocp_disp = self.builder.get_object('ocp_disp') @@ -241,7 +245,8 @@ class Main(object): try: self.serial_connect.set_sensitive(False) self.version = comm.version_check(self.serial_liststore.get_value( - self.serial_combobox.get_active_iter(), 0)) + self.serial_combobox.get_active_iter(), 0), + self.simulate.get_active()) self.statusbar.remove_all(self.error_context_id) diff --git a/dstat_interface/simulator.py b/dstat_interface/simulator.py new file mode 100644 index 0000000000000000000000000000000000000000..8c8453bbb5c5113a74be539689a689073f6f76b2 --- /dev/null +++ b/dstat_interface/simulator.py @@ -0,0 +1,332 @@ +from __future__ import (division, absolute_import, print_function, + unicode_literals) +from collections import OrderedDict +from math import ceil, floor +import logging +import struct +from time import sleep + +logger = logging.getLogger("dstat.simulator") + +import serial +import numpy as np + +try: + import gi + gi.require_version('Gtk', '3.0') + from gi.repository import Gtk +except ImportError: + print("ERR: GTK not available") + sys.exit(1) + +def menu_setup(builder): + """Take Gtk.builder instance, append menu item, and return item.""" + + parent_menu_name = 'menu_developer' + menu_label = 'Simulate DStat' + menu_class = Gtk.CheckMenuItem + + menu = menu_class.new_with_label(menu_label) + menu.set_active(False) + + builder.get_object(parent_menu_name).get_submenu().append(menu) + return menu + +class Simulator(object): + def __init__(self): + self.current_state = self.main + self.output = b"" + + self.settings_dict = OrderedDict([('max5443_offset', 0), + ('tcs_enabled', 1), + ('tcs_clear_threshold', 10000), + ('r100_trim', 0), + ('r3k_trim', 0), + ('r30k_trim', 0), + ('r300k_trim', 0), + ('r3M_trim', 0), + ('r30M_trim', 0), + ('r100M_trim', 0) + ]) + + def _get_input(self, size=1): + string = self.input_str[0:size] + self.input_str = self.input_str[size:] + return string + + def _get_params(self, n): + params = self.input_str.split(None, n) + if len(params) > n: + self.input_str = params.pop() + else: + self.input_str = b"" + return params + + def input(self, string): + logger.debug("input: {}".format(string)) + self.input_str = string + while len(self.input_str) > 0: + self.current_state() + + def main(self): + char = self._get_input() + + if char == '!': + self.current_state = self.command + self.output += b'C\r\n' + + def command(self): + def restart(): + logger.info("USB restart received") + return + def version(): + logger.info("Version Check") + self.output += b'V1.2\n\r' + return + + command_map = {'E' : self.experiment, + 'S' : self.settings, + 'R' : restart, + 'V' : version} + + char = self._get_input() + + try: + command_map[char]() + except KeyError: + logger.warning("Unrecognized command {}".format(char)) + self.output += b"#ERR: Command {} not recognized\n\r".format(char) + + if self.current_state.__name__ != self.abort_wait.__name__: + self.output += b"no\n\r" + self.current_state = self.main + else: + logger.info("abort_wait state") + logger.info("Command {} Finished".format(char)) + + def abort_wait(self): + logger.info("abort_wait() called") + self.current_state = self.abort_wait + if self._get_input() == 'a': + logger.info("Abort signal received") + self.output += b"no\n\r" + self.current_state = self.main + + def settings(self): + def reset(): + logger.info("Settings reset") + self.settings_dict = OrderedDict([('max5443_offset', 0), + ('tcs_enabled', 1), + ('tcs_clear_threshold', 10000), + ('r100_trim', 0), + ('r3k_trim', 0), + ('r30k_trim', 0), + ('r300k_trim', 0), + ('r3M_trim', 0), + ('r30M_trim', 0), + ('r100M_trim', 0) + ]) + + def firmware(): + logger.info("Firmware update mode") + + def read(): + self.output += "S" + for key, value in self.settings_dict.items(): + self.output += "{}.{}:".format(key, value) + self.output = self.output.rstrip(':') + self.output += "\n\r" + + def write(): + params = self._get_params(10) + for n, i in enumerate(self.settings_dict): + i = int(params[n]) + + settings_map = {'D' : reset, + 'F' : firmware, + 'R' : read, + 'W' : write + } + + char = self._get_input() + + try: + settings_map[char]() + except KeyError: + logger.warning("Settings control {} not found".format(char)) + + def experiment(self): + def ads1255(): + params = self._get_params(3) + logger.info("ADS1255 params: {}".format(params)) + self.output += b"#A: {} {} {}\r\n".format(*params) + + def gain(): + params = self._get_params(2) + logger.info("IV gain : {}".format(params)) + self.output += b"#G: {} {}\r\n".format(*params) + + def lsv(): + params = self._get_params(7) + start = ceil(int(params[-3])*(65536/3000)+32768) + stop = ceil(int(params[-2])*(65536/3000)+32768) + slope = params[-1] + + logger.info("LSV params: {}".format(params)) + + for i in np.arange(start, stop, 1): + self.output += b"B\n" + self.output += struct.pack('<Hl', i, 5000*i) + self.output += b"\n" + + def cv(): + params = self._get_params(9) + start = ceil(int(params[-3])*(65536/3000)+32768) + v1 = ceil(int(params[-5])*(65536/3000)+32768) + v2 = ceil(int(params[-4])*(65536/3000)+32768) + scans = int(params[-2]) + slope = params[-1] + + logger.info("CV params: {}".format(params)) + + for scan in range(scans): + for i in np.arange(v1, v2, 1): + self.output += b"B\n" + self.output += struct.pack('<Hl', i, 5000+100*scan) + self.output += b"\n" + self.output += b"S\n\r" + self.output += b"D\n\r" + + def swv(): + params = self._get_params(10) + start = ceil(int(params[-6])*(65536/3000)+32768) + stop = ceil(int(params[-5])*(65536/3000)+32768) + scans = int(params[-1]) + + logger.info("SWV params: {}".format(params)) + + if scans < 1: + scans = 1 + + for scan in range(scans): + for i in np.arange(start, stop, 1): + self.output += b"B\n" + self.output += struct.pack('<Hll', i, 5000+100*scan, 0) + self.output += b"\n" + self.output += b"S\n\r" + self.output += b"D\n\r" + + def dpv(): + params = self._get_params(10) + start = ceil(int(params[-6])*(65536/3000)+32768) + stop = ceil(int(params[-5])*(65536/3000)+32768) + + logger.info("DPV params: {}".format(params)) + + for i in np.arange(start, stop, 1): + self.output += b"B\n" + self.output += struct.pack('<Hll', i, 5000+100*scan, 0) + self.output += b"\n" + + self.output += b"D\n\r" + + def pmt(): + logger.info("PMT Idle mode entered") + self.abort_wait() + + def pot(): + params = self._get_params(2) + seconds = int(params[0]) + logger.info("POT params: {}".format(params)) + + if seconds == 0: + self.abort_wait() + else: + for i in range(seconds): + self.output += b"B\n" + self.output += struct.pack('<Hll', i, 0, 100) + sleep(1) + + def ca(): + params = self._get_params(1) + steps = int(params[0]) + params = self._get_params(steps*2) + times = [int(i) for i in params[steps:]] + seconds = sum(times) + + for i in range(seconds): + logger.info(i) + self.output += b"B\n" + self.output += struct.pack('<HHl', i, 0, 100*i) + self.output += b"\n" + sleep(1) + + def sync(): + params = self._get_params(1) + logger.info("Shutter Sync {} Hz".format(params[0])) + + def shut_off(): + logger.info("Shutter Sync Off") + + def shut_close(): + logger.info("Shutter closed") + + def shut_open(): + logger.info("Shutter open") + + experiment_map = {'A' : ads1255, + 'G' : gain, + 'L' : lsv, + 'C' : cv, + 'S' : swv, + 'D' : dpv, + 'M' : pmt, + 'P' : pot, + 'R' : ca, + 'Z' : sync, + 'z' : shut_off, + '1' : shut_close, + '2' : shut_open + } + + char = self._get_input() + + try: + experiment_map[char]() + except KeyError: + logger.warning("Unrecognized exp command {}".format(char)) + self.output += b"#ERR: Command {} not recognized\n\r".format(char) + +class SerialSim(object): + def __init__(self, *args, **kwargs): + self.sim = Simulator() + self.is_open = True + def open(self): + self.is_open = True + def close(self): + self.is_open = False + self.flushInput() + def write(self, string): + self.sim.input(string) + def read(self, size=1): + output = self.sim.output[0:size] + self.sim.output = self.sim.output[size:] + return output + def flushInput(self): + self.sim.output = b"" + def next(self): + if self.sim.output == b"": + raise StopIteration + + parted = self.sim.output.partition(b'\n') + output = parted[0] + + if parted[2] == b"": + self.sim.output = b"" + else: + self.sim.output = parted[2] + + return output + + def __iter__(self): + return self \ No newline at end of file